diff options
| author | Chris McDonough <chrism@agendaless.com> | 2009-11-24 20:49:16 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2009-11-24 20:49:16 +0000 |
| commit | 13c923f6eaf56a49897af75e14c1f70d1b26c75b (patch) | |
| tree | c78bfdd395b7c3f676b462122d0a15709052ed98 /docs | |
| parent | 84630d3576dc7a6d6c66fdf191bc377402eef743 (diff) | |
| download | pyramid-13c923f6eaf56a49897af75e14c1f70d1b26c75b.tar.gz pyramid-13c923f6eaf56a49897af75e14c1f70d1b26c75b.tar.bz2 pyramid-13c923f6eaf56a49897af75e14c1f70d1b26c75b.zip | |
Docs updates.
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/api/configuration.rst | 10 | ||||
| -rw-r--r-- | docs/glossary.rst | 43 | ||||
| -rw-r--r-- | docs/index.rst | 1 | ||||
| -rw-r--r-- | docs/narr/configuration.rst | 208 | ||||
| -rw-r--r-- | docs/narr/events.rst | 95 | ||||
| -rw-r--r-- | docs/narr/extending.rst | 31 | ||||
| -rw-r--r-- | docs/narr/hooks.rst | 252 | ||||
| -rw-r--r-- | docs/narr/scanning.rst | 188 | ||||
| -rw-r--r-- | docs/narr/views.rst | 30 |
9 files changed, 619 insertions, 239 deletions
diff --git a/docs/api/configuration.rst b/docs/api/configuration.rst index fb7c508fc..e621fbbe1 100644 --- a/docs/api/configuration.rst +++ b/docs/api/configuration.rst @@ -5,15 +5,17 @@ .. automodule:: repoze.bfg.configuration - .. autoclass:: Configurator(registry=None, package=None, settings=None, root_factory=None, zcml_file=None, authentication_policy=None, authorization_policy=None, renderers=DEFAULT_RENDERERS) + .. autoclass:: Configurator(registry=None, package=None, settings=None, root_factory=None, zcml_file=None, authentication_policy=None, authorization_policy=None, renderers=DEFAULT_RENDERERS, debug_logger=None) + + .. automethod:: add_renderer(name, factory) .. automethod:: add_route - .. automethod:: add_view + .. automethod:: add_static_view(name, path, cache_max_age=3600) - .. automethod:: add_renderer(name, factory) + .. automethod:: add_subscriber - .. automethod:: add_static_view(name, path, cache_max_age=3600) + .. automethod:: add_view .. automethod:: load_zcml(spec) diff --git a/docs/glossary.rst b/docs/glossary.rst index b50b77f96..79b780092 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -130,7 +130,7 @@ Glossary An alternative to graph traversal as a mechanism for locating a :term:`context` for a :term:`view`. When you use a :term:`route` in your :mod:`repoze.bfg` application via a ``<route>`` - declaration in ZCML, you are using URL dispatch. See the + :term:`ZCML declaration` in ZCML, you are using URL dispatch. See the :ref:`urldispatch_chapter` for more information. Context An object in the system that is found during :term:`traversal` or @@ -316,12 +316,16 @@ Glossary `Zope Configuration Markup Language <http://www.muthukadan.net/docs/zca.html#zcml>`_, an XML dialect used by Zope and :mod:`repoze.bfg` for configuration tasks. ZCML - is capable of performing many different registrations and - declarations, but its primary purpose in :mod:`repoze.bfg` is to + is capable of performing different types of :term:`configuration + declaration`, but its primary purpose in :mod:`repoze.bfg` is to perform :term:`view configuration` and :term:`route configuration` within the ``configure.zcml`` file in a :mod:`repoze.bfg` application. ZCML in a :mod:`repoze.bfg` application represents the application's :term:`application registry`. + ZCML Directive + A ZCML "tag" such as ``<view>`` or ``<route>``. + ZCML Declaration + The concrete use of a :term:`ZCML directive` within a ZCML file. Zope Component Architecture The `Zope Component Architecture <http://www.muthukadan.net/docs/zca.html>`_ (aka ZCA) is a system @@ -469,4 +473,35 @@ Glossary and a :term:`route predicate`. View predicates are attached to :term:`view configuration` and route predicates are attached to :term:`route configuration`. - + Decorator + A wrapper around a Python function or class which accepts the + function or class as its first argument and which returns an + arbitrary object. :mod:`repoze.bfg` provides several decorators, + used for configuration and return value modification purposes. See + also `PEP 318 <http://www.python.org/dev/peps/pep-0318/>`_. + Configuration Declaration + An individual method call made to an instance of a + :mod:`repoze.bfg` :term:`Configurator` object which performs an + arbitrary action, such as registering a :term:`view configuration` + (via the ``view`` method of the configurator) or :term:`route + configuration` (via the ``route`` method of the configurator). A + set of configuration declarations is also usually implied via the + use of a :term:`ZCML declaration` within an application, or a set + of configuration declarations might be performed by a :term:`scan` + of code in a package. + Configuration Decoration + Metadata implying one or more :term:`configuration declaration` + invocations. Often set by configuration Python :term:`decorator` + attributes, such as ``repoze.bfg.view.bfg_view``, aka ``@bfg_view``. + Scan + The term used by :mod:`repoze.bfg` to define the process of + importing and examining all code in a Python package or module for + :term:`configuration decoration`. + Configurator + An object used to do :term:`configuration declaration` within an + application. The most common configurator is an instance of the + ``repoze.bfg.configuration.Configurator`` class. + Imperative Configuration + The configuration mode in which you use Python to call methods on + a :term:`Configurator` in order to add each :term:`configuration + declaration` required by your application. diff --git a/docs/index.rst b/docs/index.rst index 9a45d91d1..97261b27a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -35,6 +35,7 @@ Narrative documentation in chapter form explaining how to use narr/introduction narr/install narr/configuration + narr/scanning narr/project narr/startup narr/urlmapping diff --git a/docs/narr/configuration.rst b/docs/narr/configuration.rst index 01fd58300..6a465db15 100644 --- a/docs/narr/configuration.rst +++ b/docs/narr/configuration.rst @@ -21,29 +21,29 @@ used to create all kinds of web applications. libraries to create an application is often initially easier than using a framework to create an application, because the developer can choose to cede control to library code he has not authored - selectively, making the resulting application easier to understand - for the original developer. When using a framework, the developer - is typically required to cede a greater portion of control to code - he has not authored: code that resides in the framework itself. - You needn't use a framework at all to create a web application - using Python. A rich set of libraries exists for the platform - which you can snap together to effectively create your own - framework. In practice, however, using an existing framework to - create an application is often more practical than rolling your own - via a set of libraries if the framework provides a set of - facilities and assumptions that fit your application domain's - requirements. :mod:`repoze.bfg` is a framework that fits a large - set of assumptions in the domain of web application creation. + selectively, making the resulting application easier to understand. + When using a framework, the developer is typically required to cede + a greater portion of control to code he has not authored: code that + resides in the framework itself. You needn't use a framework at + all to create a web application using Python. A rich set of + libraries exists for the platform which you can snap together to + effectively create your own framework. In practice, however, using + an existing framework to create an application is often more + practical than rolling your own via a set of libraries if the + framework provides a set of facilities and assumptions that fit + your application requirements. :mod:`repoze.bfg` is a framework + that fits a large set of assumptions in the domain of web + application construction. As a framework, the primary job of :mod:`repoze.bfg` is to make it -easier for a developer to create an arbitrary web application. From -the perspective of the authors of :mod:`repoze.bfg`, each deployment -of an application written using :mod:`repoze.bfg` implies a specific -*configuration* of the framework itself. For example, a song-serving -application might plug code into the framework that manages songs, -while the ledger- serving application might plug code that manages -accounting information. :mod:`repoze.bfg` refers to the way in which -code is plugged in to it for a specific deployment as "configuration". +easier for a developer to create an arbitrary web application. Each +deployment of an application written using :mod:`repoze.bfg` implies a +specific *configuration* of the framework itself. For example, a +song-serving application might plug code into the framework that +manages songs, while the ledger- serving application might plug in +code that manages accounting information. :mod:`repoze.bfg` refers to +the way in which code is plugged in to it for a specific deployment as +"configuration". Most people understand "configuration" as coarse knobs that inform the high-level operation of a specific application deployment. For @@ -57,13 +57,6 @@ code into the :mod:`repoze.bfg` framework, you are indeed "configuring" :mod:`repoze.bfg` for the purpose of creating a particular application deployment. -.. admonition:: Tip - - If the term "configuration" as used in this guide just doesn't seem - to "click" in your brain, it may help to mentally substitute the - term "configuration" with "wiring" or "plumbing" as you read the - chapter. - There are a number of different mechanisms you may use to configure :mod:`repoze.bfg` to create an application: *imperative* configuration and *declarative* configuration. We'll examine both modes in the @@ -173,9 +166,10 @@ the ``Response`` constructor as the *body* of the response. In the Each of these functions is known as a :term:`view callable`. View callables in a "real" :mod:`repoze.bfg` application are often -functions which accept a request and return a response. A view -callable can be represented via another type of object, like a class -or an instance, but for our purposes here, a function serves us well. +functions which accept a :term:`request` and return a +:term:`response`. A view callable can be represented via another type +of object, like a class or an instance, but for our purposes here, a +function serves us well. A view callable is called with a :term:`request` object, which is a representation of an HTTP request sent by a remote user agent. A view @@ -192,32 +186,52 @@ but return a response with the body ``Hello world!``; the Traversal ~~~~~~~~~ -If you've used the code in this tutorial already, you've actually +If you've run the code in this tutorial already, you've actually unwittingly configured :mod:`repoze.bfg` to serve an application that relies on :term:`traversal`. A full explanation of how :mod:`repoze.bfg` locates "the right" :term:`view callable` for a -given request requires some explanation of traversal. +given request requires some explanation of :term:`traversal`. + +Traversal is part of a mechanism used by :mod:`repoze.bfg` to map the +URL of some request to a particular :term:`view callable`. It is not +the only mechanism made available by :mod:`repoze.bfg` that allows the +mapping a URL to a view callable. Another distinct mode known as +:term:`URL dispatch` can alternately be used to find a view callable +based on a URL. However, our sample application uses only +:term:`traversal`. In :mod:`repoze.bfg` terms, :term:`traversal` is the act of walking -over a *directed graph* of objects from a root object using the -individual path segments of the "path info" portion of a URL (the data -following the hostname and port number, but before any query strng -elements or fragments) in order to find a :term:`context` object. - -We will use an analogy to clarify traversal. - -Let's imagine an inexperienced UNIX computer user, wishing only to -find a file and to invoke the ``cat`` command against that file. -Because he is inexperienced, the only commands he knows how to use are -``cd`` and ``cat``. And because he is inexperienced, he doesn't -understand that ``cat`` can take an absolute path specification as an -argument, so he doesn't know that you can issue a single command -command ``cat /an/absolute/path`` to get the desired result. Instead, -this user believes he must issue the ``cd`` command, starting from the -root, for each intermediate path segment, *even the path segment that -represents the file itself*. Once he gets an error (because you can't -succesfully ``cd`` into a file) , he knows he has reached the file he -wants. +over a *directed graph* of objects from a :term:`root` object using +the individual path segments of the "path info" portion of a URL (the +data following the hostname and port number, but before any query +strng elements or fragments, for example the ``/a/b/c`` portion of the +URL ``http://example.com/a/b/c?foo=1``) in order to find a +:term:`context` object and a :term:`view name`. The combination of +the :term:`context` object and the :term:`view name` (and, in more +complex configurations, other :term:`predicate` values) are used to +find "the right" :term:`view callable`, which will be invoked after +traversal. + +The object graph of our hello world application is very simple: +there's exactly one object in our graph; the default :term:`root` +object. + +We need to use an analogy to clarify how traversal works on an +arbitrary object graph. Let's imagine an inexperienced UNIX computer +user, wishing only to use the command line to find a file and to +invoke the ``cat`` command against that file. Because he is +inexperienced, the only commands he knows how to use are ``cd``, which +changes the current directory and ``cat``, which prints the contents +of a file. And because he is inexperienced, he doesn't understand +that ``cat`` can take an absolute path specification as an argument, +so he doesn't know that you can issue a single command command ``cat +/an/absolute/path`` to get the desired result. Instead, this user +believes he must issue the ``cd`` command, starting from the root, for +each intermediate path segment, *even the path segment that represents +the file itself*. Once he gets an error (because you cannot +succesfully ``cd`` into a file), he knows he has reached the file he +wants, and he will be able to execute ``cat`` against the resulting +path segment. This inexperienced user's attempt to execute ``cat`` against the file named ``/fiz/buz/myfile`` might be to issue the following set of UNIX @@ -226,6 +240,7 @@ commands: .. code-block:: bash :linenos: + cd / cd fiz cd buz cd myfile @@ -239,16 +254,18 @@ can run the ``cat`` command: cat myfile -Now he has his answer. +The contents of ``myfile`` are now printed on the user's behalf. :mod:`repoze.bfg` is very much like this inexperienced UNIX user as it uses :term:`traversal` against an object graph. In this analogy, we can map the ``cat`` program to the :mod:`repoze.bfg` concept of a -:term:`view callable`. The file being operated on in this analogy is -the :term:`context` object. The directory structure is the object -graph being traversed. The act of progressively changing directories -to find the file as well as the handling of a ``cd`` error as a stop -condition is analogous to :term:`traversal`. +:term:`view callable`: it is a program that can be run against some +:term:`context`. The file being operated on in this analogy is the +:term:`context` object; the context is the "last object found" in a +traversal. The directory structure is the object graph being +traversed. The act of progressively changing directories to find the +file as well as the handling of a ``cd`` error as a stop condition is +analogous to :term:`traversal`. Here's an image that depicts the :mod:`repoze.bfg` traversal process graphically as a flowchart: @@ -256,32 +273,35 @@ graphically as a flowchart: .. image:: modelgraphtraverser.png The object graph is traversed, beginning at a root object, represented -by ``/``; if there are further path segments in the path info, the -root object's ``__getitem__`` is called with the next path segment, -and it is presumed to return another graph object, *ad infinitum* -until all path segments are exhausted; if any node in the graph -doesn't *have* a ``__getitem__`` method, or if the ``__getitem__`` of -a node raises a ``KeyError``, traversal ends immediately. +by the root URL (``/``); if there are further path segments in the +path info of the request being processed, the root object's +``__getitem__`` is called with the next path segment, and it is +expected to return another graph object. The resulting object's +``__getitem__`` is called with the very next path segment, and it is +expected to return another graph object. This happens *ad infinitum* +until all path segments are exhausted. If at any point during +traversal any node in the graph doesn't *have* a ``__getitem__`` +method, or if the ``__getitem__`` of a node raises a ``KeyError``, +traversal ends immediately, and the node becomes the :term:`context`. The results of a :term:`traversal` include a :term:`context` and a :term:`view name`. The :term:`view name` is the *first* URL path segment in the set of path segments "left over" during -:term:`traversal`. - -Effectively, if traversal returns a non-empty :term:`view name`, it -means that traversal "ran out" of nodes in the graph before it -finished exhausting all the path segments implied by the path info of -the URL: no segments are "left over". In this case, because the -:term:`view name` is non-empty, a *non-default* view callable will be -invoked. - -The :term:`default view` of a :term:`context` is represented by a -:term:`view configuration` that has the :term:`view name` of the empty -string. The :term:`default view` is found when all path elements in -the URL *are* exhausted before :term:`traversal` returns a -:term:`context` object, causing the :term:`view name` to be ``''`` -(the empty string). When no path segements are "left over" after -traversal, the :term:`default view` for the context found is invoked. +:term:`traversal`. This will either be the empty string (``''``) or a +non-empty string (one of the path segment strings). The empty string +represents the :term:`default view` of a context object. + +The :term:`default view` is found when all path elements in the URL +are exhausted before :term:`traversal` returns a :term:`context` +object, causing the :term:`view name` to be ``''`` (the empty string). +When no path segements are "left over" after traversal, the +:term:`default view` for the context found is invoked. + +If traversal returns a non-empty :term:`view name`, it means that +traversal "ran out" of nodes in the graph before it finished +exhausting all the path segments implied by the path info of the URL: +no segments are "left over". In this case, because the :term:`view +name` is non-empty, a *non-default* view callable will be invoked. Apologies that this digression was required; on with the chapter. @@ -293,19 +313,19 @@ Apologies that this digression was required; on with the chapter. Relating Traversal to the Hello World Application ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Our application's :term:`root` object is a default root object used -when one isn't otherwise specified in application configuration. This -root object does not have a ``__getitem__`` method, thus it has no -children. Although in a more complex system there can be many +Our application's :term:`root` object is the *default* root object +used when one isn't otherwise specified in application configuration. +This root object does not have a ``__getitem__`` method, thus it has +no children. Although in a more complex system there can be many contexts which URLs resolve to in our application, effectively there is only ever one context: the root object. We have only a single default view registered (the registration for the ``hello_world`` view callable). Due to this set of circumstances, -you can consider the sole possible URL that will resolve to a default -view in this application the root URL ``'/'``. It is the only URL -that will resolve to the :term:`view name` of ``''`` (the empty -string). +you can consider the sole possible URL that will resolve to a +:term:`default view` in this application the root URL ``'/'``. It is +the only URL that will resolve to the :term:`view name` of ``''`` (the +empty string). We have only a single view registered for the :term:`view name` ``goodbye`` (the registration for the ``goodbye_world`` view @@ -454,8 +474,8 @@ view configuration registration for the ``hello_world`` view callable has no :term:`predicate` arguments, the ``hello_world`` view callable is applicable for the :term:`default view` of any :term:`context` resulting from a request. This isn't all that interesting in this -application, because we only *have* one potential context (the root -object). +application, because we always only have *one* potential context (the +root object): it is the only object in the graph. We've also registered a view configuration for another circumstance: the ``goodbye_world`` view callable has a ``name`` predicate of @@ -883,6 +903,14 @@ XML.com article <http://www.xml.com/pub/a/1999/01/namespaces.html>`_. Conclusions ----------- +.. sidebar:: Which Configuration Mode Should I Use? + + We recommend declarative configuration (ZMCL), because it's the more + traditional form of configuration used by Zope-based systems, 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. + :mod:`repoze.bfg` allows an application to perform configuration tasks either imperatively or declaratively. You can choose the mode that best fits your brain as necessary. diff --git a/docs/narr/events.rst b/docs/narr/events.rst index 028bacaa8..cd5a94871 100644 --- a/docs/narr/events.rst +++ b/docs/narr/events.rst @@ -27,26 +27,61 @@ when it's called. The mere existence of a subscriber function, however, is not sufficient to arrange for it to be called. To arrange for the subscriber to be called, you'll need to change your :term:`application -registry` by modifying your application's ``configure.zcml``. Here's -an example of a bit of XML you can add to the ``configure.zcml`` file -which registers the above ``mysubscriber`` function, which we assume -lives in a ``subscribers.py`` module within your application: +registry` by either of the following methods: -.. code-block:: xml - :linenos: +.. topic:: Configuring an Event Listener Imperatively - <subscriber - for="repoze.bfg.interfaces.INewRequest" - handler=".subscribers.mysubscriber" - /> + You can imperatively configure a subscriber function to be called + for some event type via the ``add_subscriber`` method of a + :term:`Configurator`: + + .. code-block:: python + :linenos: + + from repoze.bfg.interfaces import INewRequest + + from subscribers import mysubscriber + + config.add_subscriber(mysubscriber, INewRequest) + + The first argument to ``add_subscriber`` is the subscriber + function; the second argument is the event type. See + :ref:`configuration_module` for API documentation related to the + ``add_subscriber`` method of a :term:`Configurator`. + +.. topic:: Configuring an Event Listener Through ZCML + + You can configure an event listener by modifying your application's + ``configure.zcml``. Here's an example of a bit of XML you can add + to the ``configure.zcml`` file which registers the above + ``mysubscriber`` function, which we assume lives in a + ``subscribers.py`` module within your application: -The above example means "every time the :mod:`repoze.bfg` framework -emits an event object that supplies an ``INewRequest`` interface, call -the ``mysubscriber`` function with the event object. As you can see, -a subscription is made in terms of an :term:`interface`. The event -object sent to a subscriber will always have possess an interface. -The interface itself provides documentation of what attributes of the -event are available. + .. code-block:: xml + :linenos: + + <subscriber + for="repoze.bfg.interfaces.INewRequest" + handler=".subscribers.mysubscriber" + /> + + The *subscriber* :term:`ZCML directive` takes two attributes: + ``for``, and ``handler``. The value of ``for`` is the interface + the subscriber is registered for. Registering a subscriber for a + specific interface limits the event types that the subscriber will + receive to those specified by the interface. The value of + ``handler`` is a Python dotted-name path to the subscriber + function. + +Each of the above examples implies that every time the +:mod:`repoze.bfg` framework emits an event object that supplies an +``INewRequest`` interface, the ``mysubscriber`` function will be +called with an *event* object. + +As you can see, a subscription is made in terms of an +:term:`interface`. The event object sent to a subscriber will always +have possess an interface. The interface itself provides +documentation of what attributes of the event are available. For example, if you create event listener functions in a ``subscribers.py`` file in your application like so: @@ -77,6 +112,21 @@ file: handler=".subscribers.handle_new_response" /> +Or imperatively via the ``add_subscriber`` method of a +:term:`Configurator`: + +.. code-block:: python + :linenos: + + from repoze.bfg.interfaces import INewRequest + from repoze.bfg.interfaces import INewResponse + + from subscribers import handle_new_request + from subscribers import handle_new_response + + config.add_subscriber(handle_new_request, INewRequest) + config.add_subscriber(handle_new_response, INewResponse) + This causes the functions as to be registered as event subscribers within the :term:`application registry` . Under this configuration, when the application is run, each time a new request or response is @@ -99,14 +149,9 @@ defined at ``repoze.bfg.interfaces.INewResponse`` says it must. These particular interfaces, along with others, are documented in the :ref:`events_module` API chapter. -The *subscriber* ZCML element takes two attributes: ``for``, and -``handler``. The value of ``for`` is the interface the subscriber is -registered for. Registering a subscriber for a specific interface -limits the event types that the subscriber will receive to those -specified by the interface. The value of ``handler`` is a Python -dotted-name path to the subscriber function. - -The return value of a subscriber function is ignored. +The return value of a subscriber function is ignored. Subscribers to +the same event type are not guaranteed to be called in any particular +order relative to one another. .. _using_an_event_to_vary_the_request_type: diff --git a/docs/narr/extending.rst b/docs/narr/extending.rst index e77f4cfbe..bfe971b55 100644 --- a/docs/narr/extending.rst +++ b/docs/narr/extending.rst @@ -28,16 +28,19 @@ declarations made using the ZCML ``<view>`` directive (or the repoze bfg using the :term:`pkg_resources` API such as static files and templates. -There's only one rule you absolutely need to obey if you want to build -a maximally extensible :mod:`repoze.bfg` application: you should not -use the ``@bfg_view`` decorator or any other decorator meant to be -detected via the ZCML ``<scan>`` directive, and you mustn't configure -your :mod:`repoze.bfg` application *imperatively* by using any code -which configures the application by mutating the BFG component -registry via Python. Instead, you must use :term:`ZCML` for the -equivalent purposes. :term:`ZCML` declarations that belong to an -application can be "overridden" by integrators as necessary, but -decorators and imperative code which perform the same tasks cannot. +There's only one rule you need to obey if you want to build a +maximally extensible :mod:`repoze.bfg` application: you should not use +any :term:`configuration decoration` or :term:`imperative +configuration`. This means the application developer should avoid +relying on :term:`configuration decoration` meant to be detected via +the ZCML ``<scan>`` directive, and you mustn't configure your +:mod:`repoze.bfg` application *imperatively* by using any code which +configures the application through methods of the :term:`Configurator` +except for its ``load_zcml`` method. Instead, you must use +:term:`ZCML` for the equivalent purposes. :term:`ZCML` declarations +that belong to an application can be "overridden" by integrators as +necessary, but decorators and imperative code which perform the same +tasks cannot. In general: use only :term:`ZCML` to configure your application if you'd like it to be extensible. @@ -103,10 +106,10 @@ one of two things may be true: :ref:`extending_the_application`. If the source of trouble is configuration done imperatively (perhaps - in the ``make_app`` function called during application startup), - you'll need to or copying configuration information out of decorator - arguments and code which does imperative configuration into - equivalent :term:`ZCML` declarations. + in the function called during application startup), you'll need to + or copying configuration information out of decorator arguments and + code which does imperative configuration into equivalent + :term:`ZCML` declarations. Once this is done, you should be able to extend or modify the application like any other (see :ref:`extending_the_application`). diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index f5fc4233b..7ed0172fc 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -1,9 +1,9 @@ .. _hooks_chapter: -Using ZCML Hooks -================ +Using Hooks +=========== -ZCML "hooks" can be used to influence the behavior of the +"hooks" can be used to influence the behavior of the :mod:`repoze.bfg` framework in various ways. .. _changing_the_notfound_view: @@ -12,19 +12,82 @@ Changing the Not Found View --------------------------- When :mod:`repoze.bfg` can't map a URL to view code, it invokes a -notfound :term:`view`. The view it invokes can be customized by -placing something like the following ZCML in your ``configure.zcml`` -file. +notfound :term:`view`. The view it invokes can be customized through +application configuration. -.. code-block:: xml - :linenos: +.. topic:: Using ZCML + + If your application uses :term:`ZCML`, you can replace the Not Found + view by placing something like the following ZCML in your + ``configure.zcml`` file. + + .. code-block:: xml + :linenos: + + <notfound + view="helloworld.views.notfound_view"/> + + Replace ``helloworld.views.notfound_view`` with the Python dotted name + to the notfound view you want to use. + + Other available attributes of the ``notfound`` ZCML directive are as + follows: + + attr + + The attribute of the view callable to use if ``__call__`` is not + correct (has the same meaning as in the context of + :ref:`the_view_zcml_directive`; see the description of ``attr`` + there). + + .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. + + renderer + + This is either a single string term (e.g. ``json``) or a string + implying a path or :term:`resource specification` + (e.g. ``templates/views.pt``) used when the view returns a + non-:term:`response` object. This attribute has the same meaning as + it would in the context of :ref:`the_view_zcml_directive`; see the + description of ``renderer`` there). + + .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. + + wrapper + + The :term:`view name` (*not* an object dotted name) of another view + declared elsewhere in ZCML (or via the ``@bfg_view`` decorator) + which will receive the response body of this view as the + ``request.wrapped_body`` attribute of its own request, and the + response returned by this view as the ``request.wrapped_response`` + attribute of its own request. This attribute has the same meaning + as it would in the context of :ref:`the_view_zcml_directive`; see + the description of ``wrapper`` there). Note that the wrapper view + *should not* be protected by any permission; behavior is undefined + if it does. - <notfound - view="helloworld.views.notfound_view"/> + .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. -Replace ``helloworld.views.notfound_view`` with the Python dotted name -to the notfound view you want to use. Here's some sample code that -implements a minimal NotFound view: +.. topic:: Using Imperative Configuration + + If your application uses :term:`imperative configuration`, you can + replace the Not Found view by using the ``set_notfound_view`` + method of the ``Configurator`` named ``config``: + + .. code-block:: python + :linenos: + + import helloworld.views + config.set_notfound_view(helloworld.views.notfound_view) + + Replace ``helloworld.views.notfound_view`` with a reference to the + Python :term:`view callable` you want to use to represent the Not + Found view. + + See :ref:`configuration_module` for more information about other + arguments to the ``set_notfound_view`` method. + +Here's some sample code that implements a minimal NotFound view: .. code-block:: python :linenos: @@ -41,68 +104,98 @@ implements a minimal NotFound view: This error will be different when the ``debug_notfound`` environment setting is true than it is when it is false. -Other available attributes of the ``notfound`` ZCML directive are as -follows: +.. _changing_the_forbidden_view: -attr +Changing the Forbidden View +--------------------------- - The attribute of the view callable to use if ``__call__`` is not - correct (has the same meaning as in the context of - :ref:`the_view_zcml_directive`; see the description of ``attr`` - there). +When :mod:`repoze.bfg` can't authorize execution of a view based on +the authorization policy in use, it invokes a "forbidden view". The +default forbidden response has a 401 status code and is very plain, +but it can be overridden as necessary using one of the following +mechanisms: - .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. +.. topic:: Using ZCML -renderer + If your application uses :term:`ZCML`, you can replace the + Forbidden view by placing something like the following ZCML in your + ``configure.zcml`` file. - This is either a single string term (e.g. ``json``) or a string - implying a path or :term:`resource specification` - (e.g. ``templates/views.pt``) used when the view returns a - non-:term:`response` object. This attribute has the same meaning as - it would in the context of :ref:`the_view_zcml_directive`; see the - description of ``renderer`` there). + .. code-block:: xml + :linenos: - .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. + <forbidden + view="helloworld.views.forbidden_view"/> -wrapper - The :term:`view name` (*not* an object dotted name) of another view - declared elsewhere in ZCML (or via the ``@bfg_view`` decorator) - which will receive the response body of this view as the - ``request.wrapped_body`` attribute of its own request, and the - response returned by this view as the ``request.wrapped_response`` - attribute of its own request. This attribute has the same meaning - as it would in the context of :ref:`the_view_zcml_directive`; see - the description of ``wrapper`` there). Note that the wrapper view - *should not* be protected by any permission; behavior is undefined - if it does. + Replace ``helloworld.views.forbidden_view`` with the Python + dotted name to the forbidden view you want to use. - .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. + Other available attributes of the ``forbidden`` ZCML directive are as + follows: -.. _changing_the_forbidden_view: + attr -Changing the Forbidden View ---------------------------- + The attribute of the view callable to use if ``__call__`` is not + correct (has the same meaning as in the context of + :ref:`the_view_zcml_directive`; see the description of ``attr`` + there). -When :mod:`repoze.bfg` can't authorize execution of a view based on -the authorization policy in use, it invokes a "forbidden view". The -default forbidden response has a 401 status code and is very plain, -but it can be overridden as necessary by placing something like the -following ZCML in your ``configure.zcml`` file. + .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. -.. code-block:: xml - :linenos: + renderer + + This is either a single string term (e.g. ``json``) or a string + implying a path or :term:`resource specification` + (e.g. ``templates/views.pt``) used when the view returns a + non-:term:`response` object. This attribute has the same meaning as + it would in the context of :ref:`the_view_zcml_directive`; see the + description of ``renderer`` there). + + .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. + + wrapper + + The :term:`view name` (*not* an object dotted name) of another view + declared elsewhere in ZCML (or via the ``@bfg_view`` decorator) + which will receive the response body of this view as the + ``request.wrapped_body`` attribute of its own request, and the + response returned by this view as the ``request.wrapped_response`` + attribute of its own request. This attribute has the same meaning + as it would in the context of :ref:`the_view_zcml_directive`; see + the description of ``wrapper`` there). Note that the wrapper view + *should not* be protected by any permission; behavior is undefined + if it does. + + .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. + +.. topic:: Using Imperative Configuration + + If your application uses :term:`imperative configuration`, you can + replace the Forbidden view by using the ``set_forbidden_view`` + method of the ``Configurator`` named ``config``: + + .. code-block:: python + :linenos: - <forbidden - view="helloworld.views.forbidden_view"/> + import helloworld.views + config.set_forbiddden_view(helloworld.views.forbidden_view) -Replace ``helloworld.views.forbidden_view`` with the Python -dotted name to the forbidden view you want to use. Like any other -view, the forbidden view must accept two parameters: ``context`` and -``request`` . The ``context`` is the context found by the router when -the view invocation was denied. The ``request`` is the current -:term:`request` representing the denied action. Here's some sample -code that implements a minimal forbidden view: + Replace ``helloworld.views.forbidden_view`` with a reference to the + Python :term:`view callable` you want to use to represent the + Forbidden view. + + See :ref:`configuration_module` for more information about other + arguments to the ``set_forbidden_view`` method. + +Like any other view, the forbidden view must accept at least a +``request`` parameter, or both ``context`` and ``request``. The +``context`` (available as ``request.context`` if you're using the +request-only view argument pattern) is the context found by the router +when the view invocation was denied. The ``request`` is the current +:term:`request` representing the denied action. + +Here's some sample code that implements a minimal forbidden view: .. code-block:: python :linenos: @@ -126,45 +219,6 @@ code that implements a minimal forbidden view: an alternate forbidden view. For example, it would make sense to return a response with a ``403 Forbidden`` status code. -Other available attributes of the ``forbidden`` ZCML directive are as -follows: - -attr - - The attribute of the view callable to use if ``__call__`` is not - correct (has the same meaning as in the context of - :ref:`the_view_zcml_directive`; see the description of ``attr`` - there). - - .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. - -renderer - - This is either a single string term (e.g. ``json``) or a string - implying a path or :term:`resource specification` - (e.g. ``templates/views.pt``) used when the view returns a - non-:term:`response` object. This attribute has the same meaning as - it would in the context of :ref:`the_view_zcml_directive`; see the - description of ``renderer`` there). - - .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. - -wrapper - - The :term:`view name` (*not* an object dotted name) of another view - declared elsewhere in ZCML (or via the ``@bfg_view`` decorator) - which will receive the response body of this view as the - ``request.wrapped_body`` attribute of its own request, and the - response returned by this view as the ``request.wrapped_response`` - attribute of its own request. This attribute has the same meaning - as it would in the context of :ref:`the_view_zcml_directive`; see - the description of ``wrapper`` there). Note that the wrapper view - *should not* be protected by any permission; behavior is undefined - if it does. - - .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. - - Changing the response factory ----------------------------- diff --git a/docs/narr/scanning.rst b/docs/narr/scanning.rst new file mode 100644 index 000000000..084d550d6 --- /dev/null +++ b/docs/narr/scanning.rst @@ -0,0 +1,188 @@ +.. _scanning_chapter: + +Configuration, Decorations And Code Scanning +============================================ + +:mod:`repoze.bfg` provides a number of "modes" for performing +application configuration. These modes can be used interchangeably or +even combined, as necessary. + +For example: + +- A ``<view>`` :term:`ZCML declaration` adds a :term:`view + configuration` to the current :term:`application registry`. + +- A call to the ``add_view`` method of a :term:`Configurator` ZCML + adds a :term:`view configuration` to the current :term:`application + registry`. + +- the ``@bfg_view`` :term:`decorator` adds :term:`configuration + decoration` to the function or method it decorates. This particular + decoration can result in a :term:`view configuration` to be added to + the current :term:`application registry` if the package the code + lives in is run through a :term:`scan`. + +Decorations and Code Scanning +----------------------------- + +To lend more *locality of reference* to a :term:`configuration +declaration`, :mod:`repoze.bfg` allows you to insert +:term:`configuration decoration` statements very close to code that is +referred to by the declaration itself. + +The mere existence of configuration decoration doesn't cause any +configuration registration to be made. Before they have any effect on +the configuration of a :mod:`repoze.bfg` application, a configuration +decoration within application code must be found through a process +known as *scanning*. + +:mod:`repoze.bfg` is willing to :term:`scan` a module or a package and +its subpackages for decorations when the ``scan`` method of a +:term:`Configurator` is invoked: scanning implies searching for +configuration declarations in a package and its subpackages. +:term:`ZCML` can also invoke a :term:`scan` via its ``<scan>`` +directive. + +The scanning machinery imports each module and subpackage in a package +or module recursively, looking for special attributes attached to +objects defined within a module. These special attributes are +typically attached to code via the use of a :term:`decorator`. For +example, the :class:`repoze.bfg.view.bfg_view` decorator can be +attached to a function or instance method: + +.. code-block:: python + :linenos: + + from repoze.bfg.view import bfg_view + from webob import Response + + @bfg_view(name='hello', request_method='GET') + def hello(request): + return Response('Hello') + +The ``@bfg_view`` decorator above simply adds an attribute to the +``hello`` function, making it available for a :term:`scan` to find it +later. + +Once scanning is invoked, and :term:`configuration decoration` is +found by the scanner, a set of calls are made to a :term:`Configurator` +on behalf of the developer: these calls represent the intent of the +configuration decoration. In the example above, this is best +represented as the scanner translating the arguments to ``@bfg_view`` +into a call to the ``add_view`` method of a :term:`Configurator`, +effectively: + +.. code-block:: python + :linenos: + + config.add_view(hello, name='hello', request_method='GET') + +Scanning for :term:`configuration decoration` is performed via the +``scan`` method of a :term:`Configurator` or via a ``<scan>`` +:term:`ZCML declaration`. See :ref:`config_mode_equivalence` for +examples. + +.. _config_mode_equivalence: + +Configuration Mode Equivalence +------------------------------ + +A combination of imperative configuration, declarative configuration +via ZCML and scanning can be used to configure any application. Each +of the below examples produces the same application configuration. + +.. topic:: Completely Imperative Configuration + + .. code-block:: python + :linenos: + + # helloworld.py + + from repoze.bfg.view import bfg_view + from webob import Response + + def hello(request): + return Response('Hello') + + if __name__ == '__main__': + from repoze.bfg.configuration import Configurator + config = Configurator() + config.add_view(hello, name='hello', request_method='GET') + +.. topic:: Configuration via ZCML + + .. code-block:: python + :linenos: + + # helloworld.py + + from webob import Response + + def hello(request): + return Response('Hello') + + if __name__ == '__main__': + from repoze.bfg.configuration import Configurator + config = Configurator(zcml_file='configure.zcml') + + .. code-block:: xml + :linenos: + + <configure xmlns="http://namespaces.repoze.org"> + + <!-- configure.zcml --> + + <include package="repoze.bfg.includes"/> + + <view name="hello" + request_method="GET"/> + + </configure> + +.. topic:: Using Decorations (Imperatively Starting a Scan) + + .. code-block:: python + :linenos: + + from repoze.bfg.view import bfg_view + from webob import Response + + @bfg_view(name='hello', request_method='GET') + def hello(request): + return Response('Hello') + + if __name__ == '__main__': + from repoze.bfg.configuration import Configurator + config = Configurator() + config.scan() + +.. topic:: Using Decorations (Starting a Scan via ZCML) + + .. code-block:: python + :linenos: + + # helloworld.py + + from repoze.bfg.view import bfg_view + from webob import Response + + @bfg_view(name='hello', request_method='GET') + def hello(request): + return Response('Hello') + + if __name__ == '__main__': + from repoze.bfg.configuration import Configurator + config = Configurator(zcml_file='configure.zcml') + + .. code-block:: xml + :linenos: + + <configure xmlns="http://namespaces.repoze.org"> + + <!-- configure.zcml --> + + <include package="repoze.bfg.includes"/> + <scan package="."/> + + </configure> + diff --git a/docs/narr/views.rst b/docs/narr/views.rst index 2e574cb14..566ef765a 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -574,10 +574,34 @@ acts as a :mod:`repoze.bfg` view. All ZCML attributes (save for the the same thing. To make :mod:`repoze.bfg` process your ``@bfg_view`` declarations, you -*must* insert the following boilerplate into your application's -``configure.zcml``:: +*must* do one of the following: - <scan package="."/> +- If you are using :term:`ZCML`, insert the following boilerplate into + your application's ``configure.zcml``: + + .. code-block:: xml + :linenos: + + <scan package="."/> + +- If you are using :term:`imperative configuration`, use the ``scan`` + method of the ``Configurator`` object: + + .. code-block:: python + :linenos: + + config.scan() + +.. note:: See :ref:`configuration_module` for additional API arguments + to the ``Configurator.scan`` method. For example, the ``scan`` + method allows you to supply a ``package`` argument to better + control exactly *which* code will be scanned. This is the same + value implied by the ``package`` attribute of the ZCML ``<scan>`` + directive. + +Please see :ref:`scanning_chapter` for more information about what +happens when code is scanned for configuration declarations resulting +from use of decorators like ``@bfg_view``. After you do so, you will not need to use any other ZCML to configure :mod:`repoze.bfg` view declarations. Instead, you will be able to use |
