From f484eed8d69c1e0d4e56858e1e3dc48d2c8d7d1f Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Wed, 3 Nov 2010 23:11:07 -0400 Subject: de-zcml-ify various chapters and move ZCML to the declarative chapter --- docs/narr/declarative.rst | 852 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 852 insertions(+) create mode 100644 docs/narr/declarative.rst (limited to 'docs/narr/declarative.rst') diff --git a/docs/narr/declarative.rst b/docs/narr/declarative.rst new file mode 100644 index 000000000..33fd89073 --- /dev/null +++ b/docs/narr/declarative.rst @@ -0,0 +1,852 @@ +.. _declarative_chapter: + +Declarative Configuration +========================= + +The mode of configuration most comprehensively detailed by examples in +narrative chapters in this book is "imperative" configuration. This is the +configuration mode in which a developer cedes the least amount of control to +the framework; it's "imperative" because you express the configuration +directly in Python code, and you have the full power of Python at your +disposal as you issue configuration statements. However, another mode of +configuration exists within :mod:`pyramid`, which often provides better +extensibility and configuration conflict detection. + +A complete listing of ZCML directives is available within +:ref:`zcml_directives`. This chapter provides an overview of how you might +get started with ZCML and highlights some common tasks performed when you use +ZCML. You can get a better understanding of when it's appropriate to use +ZCML from :ref:`extending_chapter`. + +.. index:: + single: declarative configuration + +.. _declarative_configuration: + +Declarative Configuration +------------------------- + +A :mod:`pyramid` application can be configured "declaratively", if so +desired. Declarative configuration relies on *declarations* made external to +the code in a configuration file format named :term:`ZCML` (Zope +Configuration Markup Language), an XML dialect. + +A :mod:`pyramid` application configured declaratively requires not +one, but two files: a Python file and a :term:`ZCML` file. + +In a file named ``helloworld.py``: + +.. code-block:: python + :linenos: + + from paste.httpserver import serve + from pyramid.response import Response + from pyramid.configuration import Configurator + + def hello_world(request): + return Response('Hello world!') + + if __name__ == '__main__': + config = Configurator() + config.begin() + config.load_zcml('configure.zcml') + config.end() + app = config.make_wsgi_app() + serve(app, host='0.0.0.0') + +In a file named ``configure.zcml`` in the same directory as the +previously created ``helloworld.py``: + +.. code-block:: xml + :linenos: + + + + + + + + + +This pair of files forms an application functionally equivalent to the +application we created earlier in :ref:`imperative_configuration`. +Let's examine the differences between that code listing and the code +above. + +In :ref:`imperative_configuration`, we had the following lines within +the ``if __name__ == '__main__'`` section of ``helloworld.py``: + +.. code-block:: python + :linenos: + + if __name__ == '__main__': + config = Configurator() + config.begin() + config.add_view(hello_world) + config.end() + app = config.make_wsgi_app() + serve(app, host='0.0.0.0') + +In our "declarative" code, we've removed the call to ``add_view`` and +replaced it with a call to the +:meth:`pyramid.configuration.Configurator.load_zcml` method so that +it now reads as: + +.. code-block:: python + :linenos: + + if __name__ == '__main__': + config = Configurator() + config.begin() + config.load_zcml('configure.zcml') + config.end() + app = config.make_wsgi_app() + serve(app, host='0.0.0.0') + +Everything else is much the same. + +The ``config.load_zcml('configure.zcml')`` line tells the configurator +to load configuration declarations from the file named +``configure.zcml`` which sits next to ``helloworld.py`` on the +filesystem. Let's take a look at that ``configure.zcml`` file again: + +.. code-block:: xml + :linenos: + + + + + + + + + +Note that this file contains some XML, and that the XML contains a +```` :term:`configuration declaration` tag that references a +:term:`dotted Python name`. This dotted name refers to the +``hello_world`` function that lives in our ``helloworld`` Python +module. + +This ```` declaration tag performs the same function as the +``add_view`` method that was employed within +:ref:`imperative_configuration`. In fact, the ```` tag is +effectively a "macro" which calls the +:meth:`pyramid.configuration.Configurator.add_view` method on your +behalf. + +The ```` tag is an example of a :mod:`pyramid` declaration +tag. Other such tags include ```` and ````. Each of +these tags is effectively a "macro" which calls methods of a +:class:`pyramid.configuration.Configurator` object on your behalf. + +Essentially, using a :term:`ZCML` file and loading it from the +filesystem allows us to put our configuration statements within this +XML file rather as declarations, rather than representing them as +method calls to a :term:`Configurator` object. Otherwise, declarative +and imperative configuration are functionally equivalent. + +Using declarative configuration has a number of benefits, the primary +benefit being that applications configured declaratively can be +*overridden* and *extended* by third parties without requiring the +third party to change application code. If you want to build a +framework or an extensible application, using declarative +configuration is a good idea. + +Declarative configuration has an obvious downside: you can't use +plain-old-Python syntax you probably already know and understand to +configure your application; instead you need to use :term:`ZCML`. + +.. index:: + single: ZCML conflict detection + +ZCML Conflict Detection +~~~~~~~~~~~~~~~~~~~~~~~ + +A minor additional feature of ZCML is *conflict detection*. If you +define two declaration tags within the same ZCML file which logically +"collide", an exception will be raised, and the application will not +start. For example, the following ZCML file has two conflicting +```` tags: + +.. code-block:: xml + :linenos: + + + + + + + + + + + +If you try to use this ZCML file as the source of ZCML for an +application, an error will be raised when you attempt to start the +application. This error will contain information about which tags +might have conflicted. + +.. index:: + single: helloworld (declarative) + +.. _helloworld_declarative: + +Hello World, Goodbye World (Declarative) +---------------------------------------- + +Another almost entirely equivalent mode of application configuration +exists named *declarative* configuration. :mod:`pyramid` can be +configured for the same "hello world" application "declaratively", if +so desired. + +To do so, first, create a file named ``helloworld.py``: + +.. code-block:: python + :linenos: + + from pyramid.configuration import Configurator + from pyramid.response import Response + from paste.httpserver import serve + + def hello_world(request): + return Response('Hello world!') + + def goodbye_world(request): + return Response('Goodbye world!') + + if __name__ == '__main__': + config = Configurator() + config.begin() + config.load_zcml('configure.zcml') + config.end() + app = config.make_wsgi_app() + serve(app, host='0.0.0.0') + +Then create a file named ``configure.zcml`` in the same directory as +the previously created ``helloworld.py``: + +.. code-block:: xml + :linenos: + + + + + + + + + + + +This pair of files forms an application functionally equivalent to the +application we created earlier in :ref:`helloworld_imperative`. We can run +it the same way. + +.. code-block:: bash + + $ python helloworld.py + serving on 0.0.0.0:8080 view at http://127.0.0.1:8080 + +Let's examine the differences between the code in that section and the code +above. In :ref:`helloworld_imperative_appconfig`, we had the following lines +within the ``if __name__ == '__main__'`` section of ``helloworld.py``: + +.. code-block:: python + :linenos: + + if __name__ == '__main__': + config = Configurator() + config.begin() + config.add_view(hello_world) + config.add_view(goodbye_world, name='goodbye') + config.end() + app = config.make_wsgi_app() + serve(app, host='0.0.0.0') + +In our "declarative" code, we've added a call to the +:meth:`pyramid.configuration.Configurator.load_zcml` method with +the value ``configure.zcml``, and we've removed the lines which read +``config.add_view(hello_world)`` and ``config.add_view(goodbye_world, +name='goodbye')``, so that it now reads as: + +.. code-block:: python + :linenos: + + if __name__ == '__main__': + config = Configurator() + config.begin() + config.load_zcml('configure.zcml') + config.end() + app = config.make_wsgi_app() + serve(app, host='0.0.0.0') + +Everything else is much the same. + +The ``config.load_zcml('configure.zcml')`` line tells the configurator +to load configuration declarations from the ``configure.zcml`` file +which sits next to ``helloworld.py``. Let's take a look at the +``configure.zcml`` file now: + +.. code-block:: xml + :linenos: + + + + + + + + + + + +We already understand what the view code does, because the application +is functionally equivalent to the application described in +:ref:`helloworld_imperative`, but use of :term:`ZCML` is new. Let's +break that down tag-by-tag. + +The ```` Tag +~~~~~~~~~~~~~~~~~~~~~~~ + +The ``configure.zcml`` ZCML file contains this bit of XML: + +.. code-block:: xml + :linenos: + + + + + + + +Because :term:`ZCML` is XML, and because XML requires a single root +tag for each document, every ZCML file used by :mod:`pyramid` must +contain a ``configure`` container directive, which acts as the root +XML tag. It is a "container" directive because its only job is to +contain other directives. + +See also :ref:`configure_directive` and :ref:`word_on_xml_namespaces`. + +The ```` Tag +~~~~~~~~~~~~~~~~~~~~~ + +The ``configure.zcml`` ZCML file contains this bit of XML within the +```` root tag: + +.. code-block:: xml + + + +This self-closing tag instructs :mod:`pyramid` to load a ZCML file +from the Python package with the :term:`dotted Python name` +``pyramid.includes``, as specified by its ``package`` attribute. +This particular ```` declaration is required because it +actually allows subsequent declaration tags (such as ````, which +we'll see shortly) to be recognized. The ```` tag +effectively just includes another ZCML file, causing its declarations +to be executed. In this case, we want to load the declarations from +the file named ``configure.zcml`` within the +:mod:`pyramid.includes` Python package. We know we want to load +the ``configure.zcml`` from this package because ``configure.zcml`` is +the default value for another attribute of the ```` tag named +``file``. We could have spelled the include tag more verbosely, but +equivalently as: + +.. code-block:: xml + :linenos: + + + +The ```` tag that includes the ZCML statements implied by the +``configure.zcml`` file from the Python package named +:mod:`pyramid.includes` is basically required to come before any +other named declaration in an application's ``configure.zcml``. If it +is not included, subsequent declaration tags will fail to be +recognized, and the configuration system will generate an error at +startup. However, the ```` +tag needs to exist only in a "top-level" ZCML file, it needn't also +exist in ZCML files *included by* a top-level ZCML file. + +See also :ref:`include_directive`. + +The ```` Tag +~~~~~~~~~~~~~~~~~~ + +The ``configure.zcml`` ZCML file contains these bits of XML *after* the +```` tag, but *within* the ```` root tag: + +.. code-block:: xml + :linenos: + + + + + +These ```` declaration tags direct :mod:`pyramid` to create +two :term:`view configuration` registrations. The first ```` +tag has an attribute (the attribute is also named ``view``), which +points at a :term:`dotted Python name`, referencing the +``hello_world`` function defined within the ``helloworld`` package. +The second ```` tag has a ``view`` attribute which points at a +:term:`dotted Python name`, referencing the ``goodbye_world`` function +defined within the ``helloworld`` package. The second ```` tag +also has an attribute called ``name`` with a value of ``goodbye``. + +These effect of the ```` tag declarations we've put into our +``configure.zcml`` is functionally equivalent to the effect of lines +we've already seen in an imperatively-configured application. We're +just spelling things differently, using XML instead of Python. + +In our previously defined application, in which we added view +configurations imperatively, we saw this code: + +.. ignore-next-block +.. code-block:: python + :linenos: + + config.add_view(hello_world) + config.add_view(goodbye_world, name='goodbye') + +Each ```` declaration tag encountered in a ZCML file effectively +invokes the :meth:`pyramid.configuration.Configurator.add_view` +method on the behalf of the developer. Various attributes can be +specified on the ```` tag which influence the :term:`view +configuration` it creates. + +Since the relative ordering of calls to +:meth:`pyramid.configuration.Configurator.add_view` doesn't matter +(see the sidebar entitled *View Dispatch and Ordering* within +:ref:`adding_configuration`), the relative order of ```` tags in +ZCML doesn't matter either. The following ZCML orderings are +completely equivalent: + +.. topic:: Hello Before Goodbye + + .. code-block:: xml + :linenos: + + + + + +.. topic:: Goodbye Before Hello + + .. code-block:: xml + :linenos: + + + + + +We've now configured a :mod:`pyramid` helloworld application +declaratively. More information about this mode of configuration is +available in :ref:`declarative_configuration` and within +:ref:`zcml_reference`. + +Scanning +-------- + +:term:`ZCML` can also invoke a :term:`scan` via its ```` +directive. If a ZCML file is processed that contains a scan +directive, the package the ZCML file points to is scanned. + +.. topic:: Declaratively Starting a Scan + + .. code-block:: python + :linenos: + + # helloworld.py + + from paste.httpserver import serve + from pyramid.response import Response + from pyramid.view import view_config + + @view_config() + def hello(request): + return Response('Hello') + + if __name__ == '__main__': + from pyramid.configuration import Configurator + config = Configurator() + config.begin() + config.load_zcml('configure.zcml') + config.end() + app = config.make_wsgi_app() + serve(app, host='0.0.0.0') + + .. code-block:: xml + :linenos: + + + + + + + + + + +Which Mode Should I Use? +------------------------ + +A combination of imperative configuration, declarative configuration +via ZCML and scanning can be used to configure any application. They +are not mutually exclusive. + +The :mod:`pyramid` authors often recommend using mostly declarative +configuration, because it's the more traditional form of configuration +used in :mod:`pyramid` applications, it can be overridden and +extended by third party deployers, and there are more examples for it +"in the wild". + +However, imperative mode configuration can be simpler to understand, +and the framework is not "opinionated" about the choice. This book +presents examples in both styles, mostly interchangeably. You can +choose the mode that best fits your brain as necessary. + +.. index:: + single: ZCML directive; route + +.. _zcml_route_configuration: + +Configuring a Route via ZCML +---------------------------- + +Instead of using the imperative +:meth:`pyramid.configuration.Configurator.add_route` method to add a new +route, you can alternately use :term:`ZCML`. :ref:`route_directive` +statements in a :term:`ZCML` file used by your application is a sign that +you're using :term:`URL dispatch`. For example, the following :term:`ZCML +declaration` causes a route to be added to the application. + +.. code-block:: xml + :linenos: + + + +.. note:: + + Values prefixed with a period (``.``) within the values of ZCML + attributes such as the ``view`` attribute of a ``route`` mean + "relative to the Python package directory in which this + :term:`ZCML` file is stored". So if the above ``route`` + declaration was made inside a ``configure.zcml`` file that lived in + the ``hello`` package, you could replace the relative + ``.views.myview`` with the absolute ``hello.views.myview`` Either + the relative or absolute form is functionally equivalent. It's + often useful to use the relative form, in case your package's name + changes. It's also shorter to type. + +The order that routes are evaluated when declarative configuration is used +is the order that they appear relative to each other in the ZCML file. + +See :ref:`route_directive` for full ``route`` ZCML directive +documentation. + +.. index:: + triple: view; zcml; static resource + +.. _zcml_static_resources_section: + +Serving Static Resources Using ZCML +------------------------------------ + +Use of the ``static`` ZCML directive makes static files available at a name +relative to the application root URL, e.g. ``/static``. + +Note that the ``path`` provided to the ``static`` ZCML directive may be a +fully qualified :term:`resource specification`, a package-relative path, or +an *absolute path*. The ``path`` with the value ``a/b/c/static`` of a +``static`` directive in a ZCML file that resides in the "mypackage" package +will resolve to a package-qualified resource such as +``some_package:a/b/c/static``. + +Here's an example of a ``static`` ZCML directive that will serve files +up under the ``/static`` URL from the ``/var/www/static`` directory of +the computer which runs the :mod:`pyramid` application using an +absolute path. + +.. code-block:: xml + :linenos: + + + +Here's an example of a ``static`` directive that will serve files up +under the ``/static`` URL from the ``a/b/c/static`` directory of the +Python package named ``some_package`` using a fully qualified +:term:`resource specification`. + +.. code-block:: xml + :linenos: + + + +Here's an example of a ``static`` directive that will serve files up +under the ``/static`` URL from the ``static`` directory of the Python +package in which the ``configure.zcml`` file lives using a +package-relative path. + +.. code-block:: xml + :linenos: + + + +Whether you use for ``path`` a fully qualified resource specification, +an absolute path, or a package-relative path, When you place your +static files on the filesystem in the directory represented as the +``path`` of the directive, you will then be able to view the static +files in this directory via a browser at URLs prefixed with the +directive's ``name``. For instance if the ``static`` directive's +``name`` is ``static`` and the static directive's ``path`` is +``/path/to/static``, ``http://localhost:6543/static/foo.js`` will +return the file ``/path/to/static/dir/foo.js``. The static directory +may contain subdirectories recursively, and any subdirectories may +hold files; these will be resolved by the static view as you would +expect. + +While the ``path`` argument can be a number of different things, the +``name`` argument of the ``static`` ZCML directive can also be one of +a number of things: a *view name* or a *URL*. The above examples have +shown usage of the ``name`` argument as a view name. When ``name`` is +a *URL* (or any string with a slash (``/``) in it), static resources +can be served from an external webserver. In this mode, the ``name`` +is used as the URL prefix when generating a URL using +:func:`pyramid.url.static_url`. + +For example, the ``static`` ZCML directive may be fed a ``name`` +argument which is ``http://example.com/images``: + +.. code-block:: xml + :linenos: + + + +Because the ``static`` ZCML directive is provided with a ``name`` argument +that is the URL prefix ``http://example.com/images``, subsequent calls to +:func:`pyramid.url.static_url` with paths that start with the ``path`` +argument passed to :meth:`pyramid.url.static_url` will generate a URL +something like ``http://example.com/logo.png``. The external webserver +listening on ``example.com`` must be itself configured to respond properly to +such a request. The :func:`pyramid.url.static_url` API is discussed in more +detail later in this chapter. + +The :meth:`pyramid.configuration.Configurator.add_static_view` method offers +an imperative equivalent to the ``static`` ZCML directive. Use of the +``add_static_view`` imperative configuration method is completely equivalent +to using ZCML for the same purpose. See :ref:`static_resources_section` for +more information. + +.. _zcml_authorization_policy: + +Enabling an Authorization Policy Via ZCML +----------------------------------------- + +If you'd rather use :term:`ZCML` to specify an authorization policy +than imperative configuration, modify the ZCML file loaded by your +application (usually named ``configure.zcml``) to enable an +authorization policy. + +For example, to enable a policy which compares the value of an "auth +ticket" cookie passed in the request's environment which contains a +reference to a single :term:`principal` against the principals present +in any :term:`ACL` found in model data when attempting to call some +:term:`view`, modify your ``configure.zcml`` to look something like +this: + +.. code-block:: xml + :linenos: + + + + + + + + + + + +"Under the hood", these statements cause an instance of the class +:class:`pyramid.authentication.AuthTktAuthenticationPolicy` to be +injected as the :term:`authentication policy` used by this application +and an instance of the class +:class:`pyramid.authorization.ACLAuthorizationPolicy` to be +injected as the :term:`authorization policy` used by this application. + +:mod:`pyramid` ships with a number of authorization and +authentication policy ZCML directives that should prove useful. See +:ref:`authentication_policies_directives_section` and +:ref:`authorization_policies_directives_section` for more information. + +.. index:: + pair: ZCML directive; authentication policy + +.. _authentication_policies_directives_section: + +Built-In Authentication Policy ZCML Directives +---------------------------------------------- + +Instead of configuring an authentication policy and authorization +policy imperatively, :mod:`pyramid` ships with a few "pre-chewed" +authentication policy ZCML directives that you can make use of within +your application. + +``authtktauthenticationpolicy`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When this directive is used, authentication information is obtained +from an "auth ticket" cookie value, assumed to be set by a custom +login form. + +An example of its usage, with all attributes fully expanded: + +.. code-block:: xml + :linenos: + + + +See :ref:`authtktauthenticationpolicy_directive` for details about +this directive. + +``remoteuserauthenticationpolicy`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When this directive is used, authentication information is obtained +from a ``REMOTE_USER`` key in the WSGI environment, assumed to +be set by a WSGI server or an upstream middleware component. + +An example of its usage, with all attributes fully expanded: + +.. code-block:: xml + :linenos: + + + +See :ref:`remoteuserauthenticationpolicy_directive` for detailed +information. + +``repozewho1authenticationpolicy`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When this directive is used, authentication information is obtained +from a ``repoze.who.identity`` key in the WSGI environment, assumed to +be set by :term:`repoze.who` middleware. + +An example of its usage, with all attributes fully expanded: + +.. code-block:: xml + :linenos: + + + +See :ref:`repozewho1authenticationpolicy_directive` for detailed +information. + +.. index:: + pair: ZCML directive; authorization policy + +.. _authorization_policies_directives_section: + +Built-In Authorization Policy ZCML Directives +--------------------------------------------- + +``aclauthorizationpolicy`` + +When this directive is used, authorization information is obtained +from :term:`ACL` objects attached to model instances. + +An example of its usage, with all attributes fully expanded: + +.. code-block:: xml + :linenos: + + + +In other words, it has no configuration attributes; its existence in a +``configure.zcml`` file enables it. + +See :ref:`aclauthorizationpolicy_directive` for detailed information. + +.. Todo +.. ---- + +.. - ``narr/project.rst`` chapter describes execution of a paster template that +.. is based on XML. + +.. - Skipped views chapter. + +.. - i18n chapter still has topics for ZCML + +.. - events chapter still has topics for ZCML + +.. - hooks chapter still has topics for ZCML + +.. - resources chapter still has topics for ZCML -- cgit v1.2.3