summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/errata.rst1
-rw-r--r--docs/narr/hybrid.rst494
-rw-r--r--docs/narr/traversal.rst5
-rw-r--r--docs/narr/urldispatch.rst2
4 files changed, 338 insertions, 164 deletions
diff --git a/docs/errata.rst b/docs/errata.rst
index b27bfc684..89e1b909a 100644
--- a/docs/errata.rst
+++ b/docs/errata.rst
@@ -64,6 +64,7 @@ Should read:
def __call__(self):
return Response('OK')
+
pp. 98
------
diff --git a/docs/narr/hybrid.rst b/docs/narr/hybrid.rst
index 2beb3aed9..fc51feb1c 100644
--- a/docs/narr/hybrid.rst
+++ b/docs/narr/hybrid.rst
@@ -3,39 +3,32 @@
Combining Traversal and URL Dispatch
====================================
-:mod:`repoze.bfg` makes an honest attempt to unify the largely
-incompatible concepts of :term:`traversal` and :term:`url dispatch`.
-
When you write most :mod:`repoze.bfg` applications, you'll be using
-either one or the other subsystem, but not both, to perform
-:term:`context finding`. However, to solve specific problems, it's
-useful to use *both* traversal *and* URL dispatch within the same
-application. :mod:`repoze.bfg` makes this possible via *hybrid*
-applications.
+one or the other of two available :term:`context finding` subsystems:
+traversal or URL dispatch. However, to solve a limited set of
+problems, it's useful to use *both* traversal and URL dispatch
+together within the same application. :mod:`repoze.bfg` makes this
+possible via *hybrid* applications.
.. warning::
- Creating an application that uses hybrid-mode features of
- :mod:`repoze.bfg` exposes non-trivial corner cases. Reasoning
- about a "hybrid" URL dispatch + traversal model can be difficult
- because the combination of the two concepts seems to fall outside
- the sweet spot of `the magical number seven plus or minus 2
- <http://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus_or_Minus_Two>`_.
- To reason successfully about using URL dispatch and traversal
- together, you need to understand 1) URL pattern matching, 2) root
- factories and 3) the traversal algorithm, and the interactions
- between all of them. Therefore, we don't recommend creating an
- application that relies on hybrid behavior unless you must.
-
-The Schism
-----------
-
-:mod:`repoze.bfg`, especially when used according to the tutorials in
-its documentation is sort of a "dual-mode" framework. The tutorials
-explain how to create an application in terms of using either
-:term:`url dispatch` *or* :term:`traversal`. But not both. It's
-useful to examine that pattern in order to understand the schism
-between the two.
+ Reasoning about the behavior of a "hybrid" URL dispatch + traversal
+ application can be challenging. To successfully reason about using
+ URL dispatch and traversal together, you need to understand URL
+ pattern matching, root factories, and the :term:`traversal`
+ algorithm, and the potential interactions between them. Therefore,
+ we don't recommend creating an application that relies on hybrid
+ behavior unless you must.
+
+A Review of Non-Hybrid Applications
+-----------------------------------
+
+When used according to the tutorials in its documentation
+:mod:`repoze.bfg` is a "dual-mode" framework: the tutorials explain
+how to create an application in terms of using either :term:`url
+dispatch` *or* :term:`traversal`. This chapter details how you might
+combine these two dispatch mechanisms, but we'll review how they work
+in isolation before trying to combine them.
URL Dispatch Only
~~~~~~~~~~~~~~~~~
@@ -44,6 +37,7 @@ An application that uses :term:`url dispatch` exclusively to map URLs
to code will often have declarations like this within :term:`ZCML`:
.. code-block:: xml
+ :linenos:
<route
path=":foo/:bar"
@@ -57,42 +51,24 @@ to code will often have declarations like this within :term:`ZCML`:
view=".views.bazbuz"
/>
-In other words, each :term:`route` typically corresponds to a single
-view callable, and when that route is matched during a request, the
-view callable attached to it is invoked.
-
-"Under the hood", these ``<route>`` declarations register a view for
-each route. This view is registered for the following context/request
-type/name triad:
-
-- the context :term:`interface` ``None``, implying any context.
-
-- Two :term:`request type` interfaces are attached to the request: the
- :class:`repoze.bfg.interfaces.IRequest` interface and a
- dynamically-constructed route-statement-specific :term:`interface`.
-
-- the empty string as the :term:`view name`, implying the default
- view.
-
-This usually ensures that the named view will only be called when the
-route it's attached to actually matches.
+Each :term:`route` typically corresponds to a single view callable,
+and when that route is matched during a request, the view callable
+named by the ``view`` attribute is invoked.
Typically, an application that uses only URL dispatch won't perform
-any view configuration in ZCML and won't have any calls to
+any configuration in ZCML that includes a ``<view>`` declaration and
+won't have any calls to
:meth:`repoze.bfg.configuration.Configurator.add_view` in its startup
code.
Traversal Only
~~~~~~~~~~~~~~
-An application that uses :term:`traversal` exclusively to map URLs to
-code just won't have any ZCML ``<route>`` declarations nor will it
-make any calls to the
-:meth:`repoze.bfg.configuration.Configurator.add_route` method.
-Instead, its view configuration will imply declarations that look like
-this:
+An application that uses only traversal will have view configuration
+declarations that look like this:
.. code-block:: xml
+ :linenos:
<view
name="foobar"
@@ -104,61 +80,163 @@ this:
view=".views.bazbuz"
/>
-"Under the hood", the above view statements register a view using the
-following context/request/name :term:`triad`:
-
-- the :term:`context` interface ``None``
+When the above configuration is applied to an application, the
+``.views.foobar`` view callable above will be called when the URL
+``/foobar`` is visited. Likewise, the view ``.views.bazbuz`` will be
+called when the URL ``/bazbuz`` is visited.
-- the :class:`repoze.bfg.interfaces.IRequest` :term:`request type`
- interface
-
-- a :term:`view name` matching the ``name=`` argument.
-
-The ``.views.foobar`` view callable above will be called when the URL
-``/a/b/c/foobar`` or ``/foobar``, etc, assuming that no view is named
-``a``, ``b``, or ``c`` during traversal.
-
-.. index::
- single: hybrid mode application
+An application that uses :term:`traversal` exclusively to map URLs to
+code usually won't have any ZCML ``<route>`` declarations nor will it
+make any calls to the
+:meth:`repoze.bfg.configuration.Configurator.add_route` method.
Hybrid Applications
-------------------
-Clearly either traversal or url dispatch can be used to create a
-:mod:`repoze.bfg` application. However, it is possible to combine the
-competing concepts of traversal and url dispatch to resolve URLs to
-code within the same application.
-
-To "turn on" hybrid mode, use a :term:`route configuration` that
-includes a ``path`` argument that contains a special dynamic part:
-either ``*traverse`` or ``*subpath``.
+Either traversal or url dispatch alone can be used to create a
+:mod:`repoze.bfg` application. However, it is also possible to
+combine the concepts of traversal and url dispatch when building an
+application: the result is a hybrid application. In a hybrid
+application, traversal is performed *after* a particular route has
+matched.
+
+A hybrid application is a lot more like a "pure" traversal-based
+application than it is like a "pure" URL-dispatch based application.
+But unlike in a "pure" traversal-based application, in a hybrid
+application, :term:`traversal` is performed during a request after a
+route has already matched. This means that the URL pattern that
+represents the ``path`` argument of a route must match the
+``PATH_INFO`` of a request, and after the route path has matched, most
+of the "normal" rules of traversal with respect to :term:`context
+finding` and :term:`view lookup` apply.
+
+There are only four real differences between a purely traversal-based
+application and a hybrid application:
+
+- In a purely traversal based application, no routes are defined; in a
+ hybrid application, at least one route will be defined.
+
+- In a purely traversal based application, the root object used is
+ global implied by the :term:`root factory` provided at startup
+ time; in a hybrid application, the :term:`root` object at which
+ traversal begins may be varied on a per-route basis.
+
+- In a purely traversal-based application, the ``PATH_INFO`` of the
+ underlying :term:`WSGI` environment is used wholesale as a traversal
+ path; in a hybrid application, the traversal path is not the entire
+ ``PATH_INFO`` string, but a portion of the URL determined by a
+ matching pattern in the matched route configuration's path.
+
+- In a purely traversal based applications, view configurations which
+ do not mention a ``route_name`` argument are considered during
+ :term:`view lookup`; in a hybrid application, when a route is
+ matched, only view configurations which mention that route's name as
+ a ``route_name`` are considered during :term:`view lookup`.
+
+More generally, a hybrid application *is* a traversal-based
+application except:
+
+- the traversal *root* is chosen based on the route configuration of
+ the route that matched instead of from the ``root_factory`` supplied
+ during application startup configuration.
+
+- the traversal *path* is chosen based on the route configuration of
+ the route that matched rather than from the ``PATH_INFO`` of a
+ request.
+
+- the set of views that may be chosen during :term:`view lookup` when
+ a route matches are limited to those which specifically name a
+ ``route_name`` in their configuration that is the same as the
+ matched route's ``name``.
+
+To create a hybrid mode application, use a :term:`route configuration`
+that implies a particular :term:`root factory` and which also includes
+a ``path`` argument that contains a special dynamic part: either
+``*traverse`` or ``*subpath``.
+
+The Root Object for a Route Match
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A hybrid application implies that traversal is performed during a
+request after a route has matched. Traversal, by definition, must
+always begin at a root object. Therefore it's important to know
+*which* root object will be traversed after a route has matched.
+
+Figuring out which :term:`root` object results from a particular route
+match is straightforward. When a route is matched:
+
+- If the route's configuration has a ``root_factory`` argument which
+ points to a :term:`root factory` callable, that callable will be
+ called to generate a :term:`root` object.
+
+- If the route's configuration does not have a ``root_factory``
+ argument, the *global* :term:`root factory` will be called to
+ generate a :term:`root` object. The global root factory is the
+ callable implied by the ``root_factory`` argument passed to
+ :class:`repoze.bfg.configuration.Configurator` at application
+ startup time.
+
+- If a ``root_factory`` argument is not provided to the
+ :class:`repoze.bfg.configuration.Configurator` at startup time, a
+ *default* root factory is used. The default root factory is used to
+ generate a root object.
+
+.. note::
+
+ Root factories related to a route were explained previously in
+ within :ref:`route_factories`. Both the global root factory and
+ default root factory were explained previously within
+ :ref:`the_object_graph`.
Using ``*traverse`` In a Route Path
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-To create a hybrid application, combine traversal and URL dispatch by
-using route configuration that contains the special token
-``*traverse`` in the route *path*. For example:
+A hybrid application most often implies the inclusion of a route
+configuration that contains the special token ``*traverse`` at the end
+of a route's path:
.. code-block:: xml
+ :linenos:
<route
path=":foo/:bar/*traverse"
name="home"
- view=".views.home"
/>
-When the this route is matched, :mod:`repoze.bfg` will attempt to use
-:term:`traversal` against the context implied by the :term:`root
-factory` of this route.
-
-The above example isn't very useful unless you've defined a custom
-:term:`root factory` by passing it to constructor of a
-:class:`repoze.bfg.configuration.Configurator` because the *default*
-root factory cannot be traversed (it has no useful ``__getitem__``
-method). But let's imagine that your root factory looks like so:
+A ``*traverse`` token at the end of the path in a route's
+configuration implies a "stararg" *capture* value. When it is used,
+it will match the remainder of the path segments of the URL. This
+remainder becomes the path used to perform traversal.
+
+.. note::
+
+ The ``*stararg`` route path pattern syntax is explained in more
+ detail within :ref:`route_path_pattern_syntax`.
+
+Note that unlike the examples provided within
+:ref:`urldispatch_chapter`, the ``<route>`` configuration named
+previously does not name a ``view`` attribute. This is because a
+hybrid mode application relies on :term:`traversal` to do
+:term:`context finding` and :term:`view lookup` instead of invariably
+invoking a specific view callable named directly within the matched
+route's configuration.
+
+Because the path of the above route ends with ``*traverse``, when this
+route configuration is matched during a request, :mod:`repoze.bfg`
+will attempt to use :term:`traversal` against the :term:`root` object
+implied by the :term:`root factory` implied by the route's
+configuration. Once :term:`traversal` has found a :term:`context`,
+:term:`view lookup` will be invoked in almost exactly the same way it
+would have been invoked in a "pure" traversal-based application.
+
+The *default* :term:`root factory` cannot be traversed: it has no
+useful ``__getitem__`` method. So we'll need to associate this route
+configuration with a non-default root factory in order to create a
+useful hybrid application. To that end, let's imagine that we've
+created a root factory looks like so in a module named ``routes.py``:
.. code-block:: python
+ :linenos:
class Traversable(object):
def __init__(self, subobjects):
@@ -168,66 +246,174 @@ method). But let's imagine that your root factory looks like so:
return self.subobjects[name]
root = Traversable(
- {'a':Traversable({'b':Traversable({'c':Traversable({})})})})
+ {'a':Traversable({'b':Traversable({'c':Traversable({})})})}
+ )
def root_factory(request):
return root
-We've defined a bogus graph here that can be traversed, and a
-``root_factory`` function that returns the root of the graph that we
-can pass to our :class:`repoze.bfg.configuration.Configurator`.
-
-Because the ``Traversable`` object we've defined has a ``__getitem__``
-method that does something nominally useful, using traversal against
-the root implied by a route statement becomes a reasonable thing to
-do.
-
-Under the circumstance implied by ``:foo/:bar/*traverse``, traversal
-is performed *after* the route matches. If the root factory
-associated with that route returns a traversable object, the "capture
-value" implied by the ``*traverse`` element in the path pattern will
-be used to traverse the graph, starting from the root object returned
-from the root factory.
-
-For example, if the URL requested by a user was
-``http://example.com/one/two/a/b/c``, and the above route was matched,
-the traversal path used against the root would be ``a/b/c``.
-:mod:`repoze.bfg` will attempt to traverse a graph through the edges
-``a``, ``b``, and ``c``. In our above example, that would imply that
-the *context* of the view would be the ``Traversable`` object we've
-named ``c`` in our bogus graph, using the ``.views.home`` view as the
-view callable.
-
-We can also define extra views that match a route:
+Above, we've defined a (bogus) graph here that can be traversed, and a
+``root_factory`` function that can be used as part of a particular
+route configuration statement:
.. code-block:: xml
+ :linenos:
<route
path=":foo/:bar/*traverse"
name="home"
- view=".views.home"
+ root_factory=".routes.root_factory"
+ />
+
+The ``root_factory`` above points at the function we've defined. It
+will return an instance of the ``Traversable`` class as a root object
+whenever this route is matched. Because the ``Traversable`` object
+we've defined has a ``__getitem__`` method that does something
+nominally useful, and because traversal uses ``__getitem__`` to walk
+the nodes that make up an object graph, using traversal against the
+root object implied by our route statement becomes a reasonable thing
+to do.
+
+.. note::
+
+ We could have also used our ``root_factory`` callable as the
+ ``root_factory`` argument of the
+ :class:`repoze.bfg.configuration.Configurator` constructor instead
+ of associating it with a particular route inside the route's
+ configuration. Every hybrid route configuration that is matched but
+ which does *not* name a ``root_factory``` attribute will use the use
+ global ``root_factory`` function to generate a root object.
+
+When the route configuration named ``home`` above is matched during a
+request, the matchdict generated will be based on its path:
+``:foo/:bar/*traverse``. The "capture value" implied by the
+``*traverse`` element in the path pattern will be used to traverse the
+graph in order to find a context, starting from the root object
+returned from the root factory. In the above example, the
+:term:`root` object found will be the instance named ``root`` in
+``routes.py``.
+
+If the URL that matched a route with the path ``:foo/:bar/*traverse``,
+is ``http://example.com/one/two/a/b/c``, the traversal path used
+against the root object will be ``a/b/c``. As a result,
+:mod:`repoze.bfg` will attempt to traverse through the edges ``a``,
+``b``, and ``c``, beginning at the root object.
+
+In our above example, this particular set of traversal steps will mean
+that the :term:`context` of the view would be the ``Traversable``
+object we've named ``c`` in our bogus graph and the :term:`view name`
+resulting from traversal will be the empty string; if you need a
+refresher about why this outcome is presumed, see
+:ref:`traversal_algorithm`.
+
+At this point, a suitable view callable will be found and invoked
+using :term:`view lookup` as described in :ref:`view_configuration`,
+but with a caveat: in order for view lookup to work, we need to define
+a view configuration that will match when :term:`view lookup` is
+invoked after a route matches:
+
+.. code-block:: xml
+ :linenos:
+
+ <route
+ path=":foo/:bar/*traverse"
+ name="home"
+ root_factory=".routes.root_factory"
/>
<view
route_name="home"
- name="another"
- view=".views.another"
+ view=".views.myview"
/>
-Views that spell a route name are meant to associate a particular view
-declaration with a route, using the route's name, in order to indicate
-that the view should *only be invoked when the route matches*.
+Note that the above ``view`` declaration includes a ``route_name``
+argument. Views that include a ``route_name`` argument are meant to
+associate a particular view declaration with a route, using the
+route's name, in order to indicate that the view should *only be
+invoked when the route matches*.
View configurations may have a ``route_name`` attribute which refers
to the value of the ``<route>`` declaration's ``name`` attribute. In
-the above example, the route name is ``home``.
+the above example, the route name is ``home``, referring to the name
+of the route defined above it.
+
+The above ``.views.myview`` view will be invoked when:
+
+- the route named "home" is matched
+
+- the :term:`view name` resulting from traversal is the empty string.
+
+- the :term:`context` is any object.
+
+It is also possible to declare alternate views that may be invoked
+when a hybrid route is matched:
+
+.. code-block:: xml
+ :linenos:
+
+ <route
+ path=":foo/:bar/*traverse"
+ name="home"
+ root_factory=".routes.root_factory"
+ />
+
+ <view
+ route_name="home"
+ view=".views.myview"
+ />
+
+ <view
+ route_name="home"
+ name="another"
+ view=".views.another_view"
+ />
+
+The ``view`` declaration for ``.views.another_view`` above names a
+different view and, more importantly, a different :term:`view name`.
+The above ``.views.another_view`` view will be invoked when:
+
+- the route named "home" is matched
+
+- the :term:`view name` resulting from traversal is ``another``.
+
+- the :term:`context` is any object.
+
+For instance, if the URL ``http://example.com/one/two/a/another`` is
+provided to an application that uses the previously mentioned object
+graph, the ``.views.another`` view callable will be called instead of
+the ``.views.myview`` view callable because the :term:`view name` will
+be ``another`` instead of the empty string.
-The ``<view>`` declaration above names a different view and (more
-importantly) a different :term:`view name`. It's :term:`view name`
-will be looked for during traversal. So if our URL is
-"http://example.com/one/two/a/another", the ``.views.another`` view
-callable will be called instead of the *default* view callable (the
-one implied by the route with the name ``home``).
+More complicated matching can be composed. All arguments to *route*
+configuration statements and *view* configuration statements are
+supported in hybrid applications (such as :term:`predicate`
+arguments).
+
+Making Global Views Match
++++++++++++++++++++++++++
+
+By default, view configurations that don't mention a ``route_name``
+will be not found by view lookup when a route that mentions a
+``*traverse`` in its path matches. You can make these match forcibly
+by adding the ``use_global_views`` flag to the route definition. For
+example, the ``views.bazbuz`` view below will be found if the route
+named ``abc`` below is matched and the ``PATH_INFO`` is
+``/abc/bazbuz``, even though the view configuration statement does not
+have the ``route_name="abc"`` attribute.
+
+.. code-block:: xml
+ :linenos:
+
+ <route
+ path="/abc/*traverse"
+ name="abc"
+ use_global_views="True"
+ />
+
+ <view
+ name="bazbuz"
+ view=".views.bazbuz"
+ />
.. index::
single: route subpath
@@ -252,6 +438,7 @@ list implied by the capture value of ``*subpath``. You'll see this
pattern most commonly in route declarations that look like this:
.. code-block:: xml
+ :linenos:
<route
path="/static/*subpath"
@@ -263,30 +450,6 @@ Where ``.views.static_view`` is an instance of
:class:`repoze.bfg.view.static`. This effectively tells the static
helper to traverse everything in the subpath as a filename.
-Making Global Views Match
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-By default, view configurations that don't mention a ``route_name``
-will be found by view lookup when a route matches. You can make these
-match forcibly by adding the ``use_global_views`` flag to the route
-definition. For example, the ``views.bazbuz`` view below will be
-found if the route named ``abc`` below is matched and the
-``PATH_INFO`` is ``/abc/bazbuz``, even though the view configuration
-statement does not have the ``route_name="abc"`` attribute.
-
-.. code-block:: xml
-
- <route
- path="/abc/*traverse"
- name="abc"
- use_global_views="True"
- />
-
- <view
- name="bazbuz"
- view=".views.bazbuz"
- />
-
Corner Cases
------------
@@ -303,6 +466,7 @@ For example, this pair of route/view ZCML declarations will generate a
"conflict" error at startup time.
.. code-block:: xml
+ :linenos:
<route
path=":foo/:bar/*traverse"
@@ -321,6 +485,7 @@ above is an *implicit* default view when that route matches.
example, this ``<route>`` statement:
.. code-block:: xml
+ :linenos:
<route
path=":foo/:bar/*traverse"
@@ -331,6 +496,7 @@ example, this ``<route>`` statement:
Can also be spelled like so:
.. code-block:: xml
+ :linenos:
<route
path=":foo/:bar/*traverse"
@@ -351,6 +517,7 @@ Binding Extra Views Against a Route Configuration that Doesn't Have a ``*travers
Here's another corner case that just makes no sense.
.. code-block:: xml
+ :linenos:
<route
path="/abc"
@@ -364,16 +531,17 @@ Here's another corner case that just makes no sense.
route_name="abc"
/>
-The above ``<view>`` declaration is completely useless, because the
-view name will never be matched when the route it references matches.
-Only the view associated with the route itself (``.views.abc``) will
-ever be invoked when the route matches, because the default view is
-always invoked when a route matches and when no post-match traversal
-is performed. To make the below ``<view>`` declaration non-useless,
-you must the special ``*traverse`` token to the route's "path." For
-example:
+The above ``<view>`` declaration is useless, because it will never be
+matched when the route it references has matched. Only the view
+associated with the route itself (``.views.abc``) will ever be invoked
+when the route matches, because the default view is always invoked
+when a route matches and when no post-match traversal is performed.
+
+To make the above ``<view>`` declaration non-useless, the special
+``*traverse`` token must end the route's path. For example:
.. code-block:: xml
+ :linenos:
<route
path="/abc/*traverse"
diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst
index eb53f94a7..126678f07 100644
--- a/docs/narr/traversal.rst
+++ b/docs/narr/traversal.rst
@@ -146,6 +146,8 @@ within the :ref:`views_chapter` chapter.
single: traversal graph
single: model graph
+.. _the_object_graph:
+
The Object Graph
----------------
@@ -180,7 +182,8 @@ named ``config``:
Using the ``root_factory`` argument to a
:class:`repoze.bfg.configuration.Configurator` constructor tells your
:mod:`repoze.bfg` application to call this root factory to generate a
-root object whenever a request enters the application.
+root object whenever a request enters the application. This root
+factory is also known as the global root factory.
A root factory is passed a :term:`request` object and it is expected
to return an object which represents the root of the object graph.
diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst
index d766eba09..81254d3a8 100644
--- a/docs/narr/urldispatch.rst
+++ b/docs/narr/urldispatch.rst
@@ -357,6 +357,8 @@ will never be evaluated.
.. index::
single: route factory
+.. _route_factories:
+
Route Factories
~~~~~~~~~~~~~~~