.. _hybrid_chapter:

Combining Traversal and URL Dispatch
====================================

When you write most :app:`Pyramid` applications, you'll be using one or the
other of two available :term:`resource location` 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.
:app:`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, :app:`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 statements like this within its application startup
configuration:

.. code-block:: python
    :linenos:

    # config is an instance of pyramid.config.Configurator

    config.add_route('foobar', '{foo}/{bar}')
    config.add_route('bazbuz', '{baz}/{buz}')

    config.add_view('myproject.views.foobar', route_name='foobar')
    config.add_view('myproject.views.bazbuz', route_name='bazbuz')

Each :term:`route` corresponds to one or more view callables.  Each view
callable is associated with a route by passing a ``route_name`` parameter that
matches its name during a call to
:meth:`~pyramid.config.Configurator.add_view`.  When a route is matched during
a request, :term:`view lookup` is used to match the request to its associated
view callable.  The presence of calls to
:meth:`~pyramid.config.Configurator.add_route` signify that an application is
using URL dispatch.

Traversal Only
~~~~~~~~~~~~~~

An application that uses only traversal will have view configuration
declarations that look like this:

.. code-block:: python
    :linenos:

    # config is an instance of pyramid.config.Configurator

    config.add_view('mypackage.views.foobar', name='foobar')
    config.add_view('mypackage.views.bazbuz', name='bazbuz')

When the above configuration is applied to an application, the
``mypackage.views.foobar`` view callable above will be called when the URL
``/foobar`` is visited.  Likewise, the view ``mypackage.views.bazbuz`` will be
called when the URL ``/bazbuz`` is visited.

Typically, an application that uses traversal exclusively won't perform any
calls to :meth:`pyramid.config.Configurator.add_route` in its startup code.

.. index::
   single: hybrid applications

Hybrid Applications
-------------------

Either traversal or URL dispatch alone can be used to create a :app:`Pyramid`
application.  However, it is also possible to combine the concepts of traversal
and URL dispatch when building an application, the result of which 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:`resource location`
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 the :class:`~pyramid.config.Configurator`
  at application startup time.

- If a ``root_factory`` argument is not provided to the
  :class:`~pyramid.config.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_resource_tree`.

.. index::
   pair: hybrid applications; *traverse route pattern

.. _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:: python
    :linenos:

    config.add_route('home', '{foo}/{bar}/*traverse')

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`.

A hybrid mode application relies more heavily on :term:`traversal` to do
:term:`resource location` and :term:`view lookup` than most examples indicate
within :ref:`urldispatch_chapter`.

Because the pattern of the above route ends with ``*traverse``, when this route
configuration is matched during a request, :app:`Pyramid` will attempt to use
:term:`traversal` against the :term:`root` object implied by the :term:`root
factory` that is implied by the route's configuration.  Since no
``root_factory`` argument is explicitly specified for this route, this will
either be the *global* root factory for the application, or the *default* root
factory.  Once :term:`traversal` has found a :term:`context` resource,
:term:`view lookup` will be invoked in almost exactly the same way it would
have been invoked in a "pure" traversal-based application.

Let's assume there is no *global* :term:`root factory` configured in this
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 custom 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 Resource(object):
        def __init__(self, subobjects):
           self.subobjects = subobjects

        def __getitem__(self, name):
           return self.subobjects[name]

    root = Resource(
               {'a': Resource({'b': Resource({'c': Resource({})})})}
           )

    def root_factory(request):
        return root

Above we've defined a (bogus) resource tree that can be traversed, and a
``root_factory`` function that can be used as part of a particular route
configuration statement:

.. code-block:: python
    :linenos:

    config.add_route('home', '{foo}/{bar}/*traverse',
                     factory='mypackage.routes.root_factory')

The ``factory`` above points at the function we've defined.  It will return an
instance of the ``Resource`` class as a root object whenever this route is
matched.  Instances of the ``Resource`` class can be used for tree traversal
because they have a ``__getitem__`` method that does something nominally
useful. Since traversal uses ``__getitem__`` to walk the resources of a
resource tree, using traversal against the root resource implied by our route
statement is a reasonable thing to do.

.. note::

  We could have also used our ``root_factory`` function as the ``root_factory``
  argument of the :class:`~pyramid.config.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  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 resource tree in order to
find a context resource, 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, :app:`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` resource of the view would be the ``Resource`` object we've
named ``'c'`` in our bogus resource tree, 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:: python
    :linenos:

    config.add_route('home', '{foo}/{bar}/*traverse',
                     factory='mypackage.routes.root_factory')
    config.add_view('mypackage.views.myview', route_name='home')

Note that the above call to :meth:`~pyramid.config.Configurator.add_view`
includes a ``route_name`` argument.  View configurations 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*.

Calls to :meth:`~pyramid.config.Configurator.add_view` may pass a
``route_name`` attribute, which refers to the value of an existing route's
``name`` argument.  In the above example, the route name is ``home``, referring
to the name of the route defined above it.

The above ``mypackage.views.myview`` view callable will be invoked when the
following conditions are met:

- The route named "home" is matched.

- The :term:`view name` resulting from traversal is the empty string.

- The :term:`context` resource is any object.

It is also possible to declare alternative views that may be invoked when a
hybrid route is matched:

.. code-block:: python
    :linenos:

    config.add_route('home', '{foo}/{bar}/*traverse',
                     factory='mypackage.routes.root_factory')
    config.add_view('mypackage.views.myview', route_name='home')
    config.add_view('mypackage.views.another_view', route_name='home',
                    name='another')

The ``add_view`` call for ``mypackage.views.another_view`` above names a
different view and, more importantly, a different :term:`view name`.  The above
``mypackage.views.another_view`` view will be invoked when the following
conditions are met:

- The route named "home" is matched.

- The :term:`view name` resulting from traversal is ``another``.

- The :term:`context` resource 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 resource tree, the
``mypackage.views.another_view`` view callable will be called instead of the
``mypackage.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.config.Configurator.add_route`
method.

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 call to
:meth:`~pyramid.config.Configurator.add_route`:

.. code-block:: python
    :linenos:

    config.add_route('abc', '/articles/{article}/edit',
                     traverse='/{article}')

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. The
:ref:`traversal_chapter` 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).

.. index::
   pair: hybrid applications; global views

Making Global Views Match
+++++++++++++++++++++++++

By default, only view configurations that mention a ``route_name`` will be
found during view lookup when a route that has a ``*traverse`` in its pattern
matches.  You can allow views without a ``route_name`` attribute to match a
route by adding the ``use_global_views`` flag to the route definition.  For
example, the ``myproject.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:: python
    :linenos:

    config.add_route('abc', '/abc/*traverse', use_global_views=True)
    config.add_view('myproject.views.bazbuz', name='bazbuz')

.. index::
   pair: hybrid applications; *subpath
   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.static.static_view` helper attempt to compute ``PATH_INFO``
from the request's subpath when its ``use_subpath`` argument is ``True``, 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:: python
    :linenos:

    from pyramid.static import static_view

    www = static_view('mypackage:static', use_subpath=True)

    config.add_route('static', '/static/*subpath')
    config.add_view(www, route_name='static')

``mypackage.views.www`` is an instance of :class:`pyramid.static.static_view`.
This effectively tells the static helper to traverse everything in the subpath
as a filename.


.. index::
   pair: hybrid URLs; generating

.. _generating_hybrid_urls:

Generating Hybrid URLs
----------------------

.. versionadded:: 1.5

The :meth:`pyramid.request.Request.resource_url` method and the
:meth:`pyramid.request.Request.resource_path` method both accept optional
keyword arguments that make it easier to generate route-prefixed URLs that
contain paths to traversal resources: ``route_name``, ``route_kw``, and
``route_remainder_name``.

Any route that has a pattern that contains a ``*remainder`` pattern (any
stararg remainder pattern, such as ``*traverse``, ``*subpath``, or ``*fred``)
can be used as the target name for ``request.resource_url(..., route_name=)``
and ``request.resource_path(..., route_name=)``.

For example, let's imagine you have a route defined in your Pyramid application
like so:

.. code-block:: python

    config.add_route('mysection', '/mysection*traverse')

If you'd like to generate the URL ``http://example.com/mysection/a/``, you can
use the following incantation, assuming that the variable ``a`` below points to
a resource that is a child of the root with a ``__name__`` of ``a``:

.. code-block:: python

    request.resource_url(a, route_name='mysection')

You can generate only the path portion ``/mysection/a/`` assuming the same:

.. code-block:: python

    request.resource_path(a, route_name='mysection')

The path is virtual host aware, so if the ``X-Vhm-Root`` environment variable
is present in the request, and it's set to ``/a``, the above call to
``request.resource_url`` would generate ``http://example.com/mysection/``, and
the above call to ``request.resource_path`` would generate ``/mysection/``. See
:ref:`virtual_root_support` for more information.

If the route you're trying to use needs simple dynamic part values to be filled
in to successfully generate the URL, you can pass these as the ``route_kw``
argument to ``resource_url`` and ``resource_path``.  For example, assuming that
the route definition is like so:

.. code-block:: python

    config.add_route('mysection', '/{id}/mysection*traverse')

You can pass ``route_kw`` in to fill in ``{id}`` above:

.. code-block:: python

    request.resource_url(a, route_name='mysection', route_kw={'id':'1'})

If you pass ``route_kw`` but do not pass ``route_name``, ``route_kw`` will be
ignored.

By default this feature works by calling ``route_url`` under the hood, and
passing the value of the resource path to that function as ``traverse``. If
your route has a different ``*stararg`` remainder name (such as ``*subpath``),
you can tell ``resource_url`` or ``resource_path`` to use that instead of
``traverse`` by passing ``route_remainder_name``.  For example, if you have the
following route:

.. code-block:: python

    config.add_route('mysection', '/mysection*subpath')

You can fill in the ``*subpath`` value using ``resource_url`` by doing:

.. code-block:: python

    request.resource_path(a, route_name='mysection',
                         route_remainder_name='subpath')

If you pass ``route_remainder_name`` but do not pass ``route_name``,
``route_remainder_name`` will be ignored.

If you try to use ``resource_path`` or ``resource_url`` when the ``route_name``
argument points at a route that does not have a remainder stararg, an error
will not be raised, but the generated URL will not contain any remainder
information either.

All other values that are normally passable to ``resource_path`` and
``resource_url`` (such as ``query``, ``anchor``, ``host``, ``port``, and
positional elements) work as you might expect in this configuration.

Note that this feature is incompatible with the ``__resource_url__`` feature
(see :ref:`overriding_resource_url_generation`) implemented on resource
objects.  Any  ``__resource_url__`` supplied by your resource will be ignored
when you pass ``route_name``.
