.. _hybrid_chapter: Combining Traversal and URL Dispatch ==================================== When you write most :mod:`pyramid` applications, you'll be using 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:`pyramid` makes this possible via *hybrid* applications. .. warning:: 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:`pyramid` 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 ~~~~~~~~~~~~~~~~~ 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: 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 configuration in ZCML that includes a ```` declaration and won't have any calls to :meth:`pyramid.configuration.Configurator.add_view` in its startup code. Traversal Only ~~~~~~~~~~~~~~ An application that uses only traversal will have view configuration declarations that look like this: .. code-block:: xml :linenos: 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. An application that uses :term:`traversal` exclusively to map URLs to code usually won't have any ZCML ```` declarations nor will it make any calls to the :meth:`pyramid.configuration.Configurator.add_route` method. Hybrid Applications ------------------- Either traversal or url dispatch alone can be used to create a :mod:`pyramid` 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 ``pattern`` argument of a route must match the ``PATH_INFO`` of a request, and after the route pattern 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 pattern. - In a purely traversal based application, 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 ``pattern`` 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 ``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 ``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:`pyramid.configuration.Configurator` at application startup time. - If a ``root_factory`` argument is not provided to the :class:`pyramid.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 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_pattern: Using ``*traverse`` In a Route Pattern ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 pattern: .. code-block:: xml :linenos: A ``*traverse`` token at the end of the pattern in a route's configuration implies a "remainder" *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 ``*remainder`` route pattern syntax is explained in more detail within :ref:`route_pattern_syntax`. Note that unlike the examples provided within :ref:`urldispatch_chapter`, the ```` 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 pattern of the above route ends with ``*traverse``, when this route configuration is matched during a request, :mod:`pyramid` 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 that looks like so in a module named ``routes.py``: .. code-block:: python :linenos: class Traversable(object): def __init__(self, subobjects): self.subobjects = subobjects def __getitem__(self, name): return self.subobjects[name] root = Traversable( {'a':Traversable({'b':Traversable({'c':Traversable({})})})} ) def root_factory(request): return root 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: The ``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:`pyramid.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 ``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 pattern: ``:foo/:bar/*traverse``. The "capture value" implied by the ``*traverse`` element in the 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 pattern ``: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:`pyramid` 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: 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 ```` declaration's ``name`` attribute. In 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: 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. 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). Using the ``traverse`` Argument In a Route Definition ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Rather than using the ``*traverse`` remainder marker in a pattern, you can use the ``traverse`` argument to the :meth:`pyramid.configuration.Configurator.add_route`` method or the ``traverse`` attribute of the :ref:`route_directive` ZCML directive. (either method is equivalent). When you use the ``*traverse`` remainder marker, the traversal path is limited to being the remainder segments of a request URL when a route matches. However, when you use the ``traverse`` argument or attribute, you have more control over how to compose a traversal path. Here's a use of the ``traverse`` pattern in a ZCML ``route`` declaration: .. code-block:: xml :linenos: The syntax of the ``traverse`` argument is the same as it is for ``pattern``. If, as above, the ``pattern`` provided is ``articles/:article/edit``, and the ``traverse`` argument provided is ``/:article``, when a request comes in that causes the route to match in such a way that the ``article`` match value is ``1`` (when the request URI is ``/articles/1/edit``), the traversal path will be generated as ``/1``. This means that the root object's ``__getitem__`` will be called with the name ``1`` during the traversal phase. If the ``1`` object exists, it will become the :term:`context` of the request. :ref:`traversal_chapter` has more information about traversal. If the traversal path contains segment marker names which are not present in the pattern argument, a runtime error will occur. The ``traverse`` pattern should not contain segment markers that do not exist in the ``path``. Note that the ``traverse`` argument is ignored when attached to a route that has a ``*traverse`` remainder marker in its pattern. Traversal will begin at the root object implied by this route (either the global root, or the object returned by the ``factory`` associated with this route). 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 pattern 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: .. index:: single: route subpath single: subpath (route) .. _star_subpath: Using ``*subpath`` in a Route Pattern ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ There are certain extremely rare cases when you'd like to influence the traversal :term:`subpath` when a route matches without actually performing traversal. For instance, the :func:`pyramid.wsgi.wsgiapp2` decorator and the :class:`pyramid.view.static` helper attempt to compute ``PATH_INFO`` from the request's subpath, so it's useful to be able to influence this value. When ``*subpath`` exists in a pattern, no path is actually traversed, but the traversal algorithm will return a :term:`subpath` 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: Where ``.views.static_view`` is an instance of :class:`pyramid.view.static`. This effectively tells the static helper to traverse everything in the subpath as a filename. Corner Cases ------------ A number of corner case "gotchas" exist when using a hybrid application. We'll detail them here. Registering a Default View for a Route That Has a ``view`` Attribute ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It is an error to provide *both* a ``view`` argument to a :term:`route configuration` *and* a :term:`view configuration` which names a ``route_name`` that has no ``name`` value or the empty ``name`` value. For example, this pair of route/view ZCML declarations will generate a "conflict" error at startup time. .. code-block:: xml :linenos: This is because the ``view`` attribute of the ```` statement above is an *implicit* default view when that route matches. ```` declarations don't *need* to supply a view attribute. For example, this ```` statement: .. code-block:: xml :linenos: Can also be spelled like so: .. code-block:: xml :linenos: The two spellings are logically equivalent. In fact, the former is just a syntactical shortcut for the latter. Binding Extra Views Against a Route Configuration that Doesn't Have a ``*traverse`` Element In Its Patttern ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Here's another corner case that just makes no sense. .. code-block:: xml :linenos: The above ```` 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 ```` declaration non-useless, the special ``*traverse`` token must end the route's pattern. For example: .. code-block:: xml :linenos: