summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2009-11-24 20:49:16 +0000
committerChris McDonough <chrism@agendaless.com>2009-11-24 20:49:16 +0000
commit13c923f6eaf56a49897af75e14c1f70d1b26c75b (patch)
treec78bfdd395b7c3f676b462122d0a15709052ed98 /docs
parent84630d3576dc7a6d6c66fdf191bc377402eef743 (diff)
downloadpyramid-13c923f6eaf56a49897af75e14c1f70d1b26c75b.tar.gz
pyramid-13c923f6eaf56a49897af75e14c1f70d1b26c75b.tar.bz2
pyramid-13c923f6eaf56a49897af75e14c1f70d1b26c75b.zip
Docs updates.
Diffstat (limited to 'docs')
-rw-r--r--docs/api/configuration.rst10
-rw-r--r--docs/glossary.rst43
-rw-r--r--docs/index.rst1
-rw-r--r--docs/narr/configuration.rst208
-rw-r--r--docs/narr/events.rst95
-rw-r--r--docs/narr/extending.rst31
-rw-r--r--docs/narr/hooks.rst252
-rw-r--r--docs/narr/scanning.rst188
-rw-r--r--docs/narr/views.rst30
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