1.. _hybrid_chapter:
2
3Combining Traversal and URL Dispatch
4====================================
5
6When you write most :app:`Pyramid` applications, you'll be using one or the
7other of two available :term:`resource location` subsystems: traversal or URL
8dispatch.  However, to solve a limited set of problems, it's useful to use
9*both* traversal and URL dispatch together within the same application.
10:app:`Pyramid` makes this possible via *hybrid* applications.
11
12.. warning::
13
14   Reasoning about the behavior of a "hybrid" URL dispatch + traversal
15   application can be challenging.  To successfully reason about using URL
16   dispatch and traversal together, you need to understand URL pattern
17   matching, root factories, and the :term:`traversal` algorithm, and the
18   potential interactions between them.  Therefore, we don't recommend creating
19   an application that relies on hybrid behavior unless you must.
20
21A Review of Non-Hybrid Applications
22-----------------------------------
23
24When used according to the tutorials in its documentation, :app:`Pyramid` is a
25"dual-mode" framework: the tutorials explain how to create an application in
26terms of using either :term:`URL dispatch` *or* :term:`traversal`.  This
27chapter details how you might combine these two dispatch mechanisms, but we'll
28review how they work in isolation before trying to combine them.
29
30URL Dispatch Only
31~~~~~~~~~~~~~~~~~
32
33An application that uses :term:`URL dispatch` exclusively to map URLs to code
34will often have statements like this within its application startup
35configuration:
36
37.. code-block:: python
38   :linenos:
39
40   # config is an instance of pyramid.config.Configurator
41
42   config.add_route('foobar', '{foo}/{bar}')
43   config.add_route('bazbuz', '{baz}/{buz}')
44
45   config.add_view('myproject.views.foobar', route_name='foobar')
46   config.add_view('myproject.views.bazbuz', route_name='bazbuz')
47
48Each :term:`route` corresponds to one or more view callables.  Each view
49callable is associated with a route by passing a ``route_name`` parameter that
50matches its name during a call to
51:meth:`~pyramid.config.Configurator.add_view`.  When a route is matched during
52a request, :term:`view lookup` is used to match the request to its associated
53view callable.  The presence of calls to
54:meth:`~pyramid.config.Configurator.add_route` signify that an application is
55using URL dispatch.
56
57Traversal Only
58~~~~~~~~~~~~~~
59
60An application that uses only traversal will have view configuration
61declarations that look like this:
62
63.. code-block:: python
64    :linenos:
65
66    # config is an instance of pyramid.config.Configurator
67
68    config.add_view('mypackage.views.foobar', name='foobar')
69    config.add_view('mypackage.views.bazbuz', name='bazbuz')
70
71When the above configuration is applied to an application, the
72``mypackage.views.foobar`` view callable above will be called when the URL
73``/foobar`` is visited.  Likewise, the view ``mypackage.views.bazbuz`` will be
74called when the URL ``/bazbuz`` is visited.
75
76Typically, an application that uses traversal exclusively won't perform any
77calls to :meth:`pyramid.config.Configurator.add_route` in its startup code.
78
79.. index::
80   single: hybrid applications
81
82Hybrid Applications
83-------------------
84
85Either traversal or URL dispatch alone can be used to create a :app:`Pyramid`
86application.  However, it is also possible to combine the concepts of traversal
87and URL dispatch when building an application, the result of which is a hybrid
88application.  In a hybrid application, traversal is performed *after* a
89particular route has matched.
90
91A hybrid application is a lot more like a "pure" traversal-based application
92than it is like a "pure" URL-dispatch based application. But unlike in a "pure"
93traversal-based application, in a hybrid application :term:`traversal` is
94performed during a request after a route has already matched.  This means that
95the URL pattern that represents the ``pattern`` argument of a route must match
96the ``PATH_INFO`` of a request, and after the route pattern has matched, most
97of the "normal" rules of traversal with respect to :term:`resource location`
98and :term:`view lookup` apply.
99
100There are only four real differences between a purely traversal-based
101application and a hybrid application:
102
103- In a purely traversal-based application, no routes are defined.  In a hybrid
104  application, at least one route will be defined.
105
106- In a purely traversal-based application, the root object used is global,
107  implied by the :term:`root factory` provided at startup time.  In a hybrid
108  application, the :term:`root` object at which traversal begins may be varied
109  on a per-route basis.
110
111- In a purely traversal-based application, the ``PATH_INFO`` of the underlying
112  :term:`WSGI` environment is used wholesale as a traversal path.  In a hybrid
113  application, the traversal path is not the entire ``PATH_INFO`` string, but a
114  portion of the URL determined by a matching pattern in the matched route
115  configuration's pattern.
116
117- In a purely traversal-based application, view configurations which do not
118  mention a ``route_name`` argument are considered during :term:`view lookup`.
119  In a hybrid application, when a route is matched, only view configurations
120  which mention that route's name as a ``route_name`` are considered during
121  :term:`view lookup`.
122
123More generally, a hybrid application *is* a traversal-based application except:
124
125- the traversal *root* is chosen based on the route configuration of the route
126  that matched, instead of from the ``root_factory`` supplied during
127  application startup configuration.
128
129- the traversal *path* is chosen based on the route configuration of the route
130  that matched, rather than from the ``PATH_INFO`` of a request.
131
132- the set of views that may be chosen during :term:`view lookup` when a route
133  matches are limited to those which specifically name a ``route_name`` in
134  their configuration that is the same as the matched route's ``name``.
135
136To create a hybrid mode application, use a :term:`route configuration` that
137implies a particular :term:`root factory` and which also includes a ``pattern``
138argument that contains a special dynamic part: either ``*traverse`` or
139``*subpath``.
140
141The Root Object for a Route Match
142~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
143
144A hybrid application implies that traversal is performed during a request after
145a route has matched.  Traversal, by definition, must always begin at a root
146object.  Therefore it's important to know *which* root object will be traversed
147after a route has matched.
148
149Figuring out which :term:`root` object results from a particular route match is
150straightforward.  When a route is matched:
151
152- If the route's configuration has a ``factory`` argument which points to a
153  :term:`root factory` callable, that callable will be called to generate a
154  :term:`root` object.
155
156- If the route's configuration does not have a ``factory`` argument, the
157  *global* :term:`root factory` will be called to generate a :term:`root`
158  object.  The global root factory is the callable implied by the
159  ``root_factory`` argument passed to the :class:`~pyramid.config.Configurator`
160  at application startup time.
161
162- If a ``root_factory`` argument is not provided to the
163  :class:`~pyramid.config.Configurator` at startup time, a *default* root
164  factory is used.  The default root factory is used to generate a root object.
165
166.. note::
167
168   Root factories related to a route were explained previously within
169   :ref:`route_factories`.  Both the global root factory and default root
170   factory were explained previously within :ref:`the_resource_tree`.
171
172.. index::
173   pair: hybrid applications; *traverse route pattern
174
175.. _using_traverse_in_a_route_pattern:
176
177Using ``*traverse`` in a Route Pattern
178~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
179
180A hybrid application most often implies the inclusion of a route configuration
181that contains the special token ``*traverse`` at the end of a route's pattern:
182
183.. code-block:: python
184   :linenos:
185
186   config.add_route('home', '{foo}/{bar}/*traverse')
187
188A ``*traverse`` token at the end of the pattern in a route's configuration
189implies a "remainder" *capture* value.  When it is used, it will match the
190remainder of the path segments of the URL.  This remainder becomes the path
191used to perform traversal.
192
193.. note::
194
195   The ``*remainder`` route pattern syntax is explained in more detail within
196   :ref:`route_pattern_syntax`.
197
198A hybrid mode application relies more heavily on :term:`traversal` to do
199:term:`resource location` and :term:`view lookup` than most examples indicate
200within :ref:`urldispatch_chapter`.
201
202Because the pattern of the above route ends with ``*traverse``, when this route
203configuration is matched during a request, :app:`Pyramid` will attempt to use
204:term:`traversal` against the :term:`root` object implied by the :term:`root
205factory` that is implied by the route's configuration.  Since no
206``root_factory`` argument is explicitly specified for this route, this will
207either be the *global* root factory for the application, or the *default* root
208factory.  Once :term:`traversal` has found a :term:`context` resource,
209:term:`view lookup` will be invoked in almost exactly the same way it would
210have been invoked in a "pure" traversal-based application.
211
212Let's assume there is no *global* :term:`root factory` configured in this
213application. The *default* :term:`root factory` cannot be traversed; it has no
214useful ``__getitem__`` method.  So we'll need to associate this route
215configuration with a custom root factory in order to create a useful hybrid
216application.  To that end, let's imagine that we've created a root factory that
217looks like so in a module named ``routes.py``:
218
219.. code-block:: python
220   :linenos:
221
222   class Resource(object):
223       def __init__(self, subobjects):
224          self.subobjects = subobjects
225
226       def __getitem__(self, name):
227          return self.subobjects[name]
228
229   root = Resource(
230           {'a': Resource({'b': Resource({'c': Resource({})})})}
231          )
232
233   def root_factory(request):
234       return root
235
236Above we've defined a (bogus) resource tree that can be traversed, and a
237``root_factory`` function that can be used as part of a particular route
238configuration statement:
239
240.. code-block:: python
241   :linenos:
242
243   config.add_route('home', '{foo}/{bar}/*traverse',
244                    factory='mypackage.routes.root_factory')
245
246The ``factory`` above points at the function we've defined.  It will return an
247instance of the ``Resource`` class as a root object whenever this route is
248matched.  Instances of the ``Resource`` class can be used for tree traversal
249because they have a ``__getitem__`` method that does something nominally
250useful. Since traversal uses ``__getitem__`` to walk the resources of a
251resource tree, using traversal against the root resource implied by our route
252statement is a reasonable thing to do.
253
254.. note::
255
256  We could have also used our ``root_factory`` function as the ``root_factory``
257  argument of the :class:`~pyramid.config.Configurator` constructor, instead of
258  associating it with a particular route inside the route's configuration.
259  Every hybrid route configuration that is matched, but which does *not* name a
260  ``factory`` attribute, will use the  global ``root_factory`` function to
261  generate a root object.
262
263When the route configuration named ``home`` above is matched during a request,
264the matchdict generated will be based on its pattern:
265``{foo}/{bar}/*traverse``.  The "capture value" implied by the ``*traverse``
266element in the pattern will be used to traverse the resource tree in order to
267find a context resource, starting from the root object returned from the root
268factory.  In the above example, the :term:`root` object found will be the
269instance named ``root`` in ``routes.py``.
270
271If the URL that matched a route with the pattern ``{foo}/{bar}/*traverse`` is
272``http://example.com/one/two/a/b/c``, the traversal path used against the root
273object will be ``a/b/c``.  As a result, :app:`Pyramid` will attempt to traverse
274through the edges ``'a'``, ``'b'``, and ``'c'``, beginning at the root object.
275
276In our above example, this particular set of traversal steps will mean that the
277:term:`context` resource of the view would be the ``Resource`` object we've
278named ``'c'`` in our bogus resource tree, and the :term:`view name` resulting
279from traversal will be the empty string.  If you need a refresher about why
280this outcome is presumed, see :ref:`traversal_algorithm`.
281
282At this point, a suitable view callable will be found and invoked using
283:term:`view lookup` as described in :ref:`view_configuration`, but with a
284caveat: in order for view lookup to work, we need to define a view
285configuration that will match when :term:`view lookup` is invoked after a route
286matches:
287
288.. code-block:: python
289   :linenos:
290
291   config.add_route('home', '{foo}/{bar}/*traverse',
292                    factory='mypackage.routes.root_factory')
293   config.add_view('mypackage.views.myview', route_name='home')
294
295Note that the above call to :meth:`~pyramid.config.Configurator.add_view`
296includes a ``route_name`` argument.  View configurations that include a
297``route_name`` argument are meant to associate a particular view declaration
298with a route, using the route's name, in order to indicate that the view should
299*only be invoked when the route matches*.
300
301Calls to :meth:`~pyramid.config.Configurator.add_view` may pass a
302``route_name`` attribute, which refers to the value of an existing route's
303``name`` argument.  In the above example, the route name is ``home``, referring
304to the name of the route defined above it.
305
306The above ``mypackage.views.myview`` view callable will be invoked when the
307following conditions are met:
308
309- The route named "home" is matched.
310
311- The :term:`view name` resulting from traversal is the empty string.
312
313- The :term:`context` resource is any object.
314
315It is also possible to declare alternative views that may be invoked when a
316hybrid route is matched:
317
318.. code-block:: python
319   :linenos:
320
321   config.add_route('home', '{foo}/{bar}/*traverse',
322                    factory='mypackage.routes.root_factory')
323   config.add_view('mypackage.views.myview', route_name='home')
324   config.add_view('mypackage.views.another_view', route_name='home',
325                   name='another')
326
327The ``add_view`` call for ``mypackage.views.another_view`` above names a
328different view and, more importantly, a different :term:`view name`.  The above
329``mypackage.views.another_view`` view will be invoked when the following
330conditions are met:
331
332- The route named "home" is matched.
333
334- The :term:`view name` resulting from traversal is ``another``.
335
336- The :term:`context` resource is any object.
337
338For instance, if the URL ``http://example.com/one/two/a/another`` is provided
339to an application that uses the previously mentioned resource tree, the
340``mypackage.views.another_view`` view callable will be called instead of the
341``mypackage.views.myview`` view callable because the :term:`view name` will be
342``another`` instead of the empty string.
343
344More complicated matching can be composed.  All arguments to *route*
345configuration statements and *view* configuration statements are supported in
346hybrid applications (such as :term:`predicate` arguments).
347
348Using the ``traverse`` Argument in a Route Definition
349~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
350
351Rather than using the ``*traverse`` remainder marker in a pattern, you can use
352the ``traverse`` argument to the :meth:`~pyramid.config.Configurator.add_route`
353method.
354
355When you use the ``*traverse`` remainder marker, the traversal path is limited
356to being the remainder segments of a request URL when a route matches.
357However, when you use the ``traverse`` argument or attribute, you have more
358control over how to compose a traversal path.
359
360Here's a use of the ``traverse`` pattern in a call to
361:meth:`~pyramid.config.Configurator.add_route`:
362
363.. code-block:: python
364   :linenos:
365
366   config.add_route('abc', '/articles/{article}/edit',
367                    traverse='/{article}')
368
369The syntax of the ``traverse`` argument is the same as it is for ``pattern``.
370
371If, as above, the ``pattern`` provided is ``/articles/{article}/edit``, and the
372``traverse`` argument provided is ``/{article}``, when a request comes in that
373causes the route to match in such a way that the ``article`` match value is
374``1`` (when the request URI is ``/articles/1/edit``), the traversal path will
375be generated as ``/1``. This means that the root object's ``__getitem__`` will
376be called with the name ``1`` during the traversal phase.  If the ``1`` object
377exists, it will become the :term:`context` of the request. The
378:ref:`traversal_chapter` chapter has more information about traversal.
379
380If the traversal path contains segment marker names which are not present in
381the pattern argument, a runtime error will occur.  The ``traverse`` pattern
382should not contain segment markers that do not exist in the ``path``.
383
384Note that the ``traverse`` argument is ignored when attached to a route that
385has a ``*traverse`` remainder marker in its pattern.
386
387Traversal will begin at the root object implied by this route (either the
388global root, or the object returned by the ``factory`` associated with this
389route).
390
391.. index::
392   pair: hybrid applications; global views
393
394Making Global Views Match
395+++++++++++++++++++++++++
396
397By default, only view configurations that mention a ``route_name`` will be
398found during view lookup when a route that has a ``*traverse`` in its pattern
399matches.  You can allow views without a ``route_name`` attribute to match a
400route by adding the ``use_global_views`` flag to the route definition.  For
401example, the ``myproject.views.bazbuz`` view below will be found if the route
402named ``abc`` below is matched and the ``PATH_INFO`` is ``/abc/bazbuz``, even
403though the view configuration statement does not have the ``route_name="abc"``
404attribute.
405
406.. code-block:: python
407   :linenos:
408
409   config.add_route('abc', '/abc/*traverse', use_global_views=True)
410   config.add_view('myproject.views.bazbuz', name='bazbuz')
411
412.. index::
413   pair: hybrid applications; *subpath
414   single: route subpath
415   single: subpath (route)
416
417.. _star_subpath:
418
419Using ``*subpath`` in a Route Pattern
420~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
421
422There are certain extremely rare cases when you'd like to influence the
423traversal :term:`subpath` when a route matches without actually performing
424traversal.  For instance, the :func:`pyramid.wsgi.wsgiapp2` decorator and the
425:class:`pyramid.static.static_view` helper attempt to compute ``PATH_INFO``
426from the request's subpath when its ``use_subpath`` argument is ``True``, so
427it's useful to be able to influence this value.
428
429When ``*subpath`` exists in a pattern, no path is actually traversed, but the
430traversal algorithm will return a :term:`subpath` list implied by the capture
431value of ``*subpath``.  You'll see this pattern most commonly in route
432declarations that look like this:
433
434.. code-block:: python
435   :linenos:
436
437   from pyramid.static import static_view
438
439   www = static_view('mypackage:static', use_subpath=True)
440
441   config.add_route('static', '/static/*subpath')
442   config.add_view(www, route_name='static')
443
444``mypackage.views.www`` is an instance of :class:`pyramid.static.static_view`.
445This effectively tells the static helper to traverse everything in the subpath
446as a filename.
447
448
449.. index::
450   pair: hybrid URLs; generating
451
452.. _generating_hybrid_urls:
453
454Generating Hybrid URLs
455----------------------
456
457.. versionadded:: 1.5
458
459The :meth:`pyramid.request.Request.resource_url` method and the
460:meth:`pyramid.request.Request.resource_path` method both accept optional
461keyword arguments that make it easier to generate route-prefixed URLs that
462contain paths to traversal resources: ``route_name``, ``route_kw``, and
463``route_remainder_name``.
464
465Any route that has a pattern that contains a ``*remainder`` pattern (any
466stararg remainder pattern, such as ``*traverse``, ``*subpath``, or ``*fred``)
467can be used as the target name for ``request.resource_url(..., route_name=)``
468and ``request.resource_path(..., route_name=)``.
469
470For example, let's imagine you have a route defined in your Pyramid application
471like so:
472
473.. code-block:: python
474
475   config.add_route('mysection', '/mysection*traverse')
476
477If you'd like to generate the URL ``http://example.com/mysection/a/``, you can
478use the following incantation, assuming that the variable ``a`` below points to
479a resource that is a child of the root with a ``__name__`` of ``a``:
480
481.. code-block:: python
482
483   request.resource_url(a, route_name='mysection')
484
485You can generate only the path portion ``/mysection/a/`` assuming the same:
486
487.. code-block:: python
488
489   request.resource_path(a, route_name='mysection')
490
491The path is virtual host aware, so if the ``X-Vhm-Root`` environment variable
492is present in the request, and it's set to ``/a``, the above call to
493``request.resource_url`` would generate ``http://example.com/mysection/``, and
494the above call to ``request.resource_path`` would generate ``/mysection/``. See
495:ref:`virtual_root_support` for more information.
496
497If the route you're trying to use needs simple dynamic part values to be filled
498in to succesfully generate the URL, you can pass these as the ``route_kw``
499argument to ``resource_url`` and ``resource_path``.  For example, assuming that
500the route definition is like so:
501
502.. code-block:: python
503
504   config.add_route('mysection', '/{id}/mysection*traverse')
505
506You can pass ``route_kw`` in to fill in ``{id}`` above:
507
508.. code-block:: python
509
510   request.resource_url(a, route_name='mysection', route_kw={'id':'1'})
511
512If you pass ``route_kw`` but do not pass ``route_name``, ``route_kw`` will be
513ignored.
514
515By default this feature works by calling ``route_url`` under the hood, and
516passing the value of the resource path to that function as ``traverse``. If
517your route has a different ``*stararg`` remainder name (such as ``*subpath``),
518you can tell ``resource_url`` or ``resource_path`` to use that instead of
519``traverse`` by passing ``route_remainder_name``.  For example, if you have the
520following route:
521
522.. code-block:: python
523
524   config.add_route('mysection', '/mysection*subpath')
525
526You can fill in the ``*subpath`` value using ``resource_url`` by doing:
527
528.. code-block:: python
529
530   request.resource_path(a, route_name='mysection',
531                         route_remainder_name='subpath')
532
533If you pass ``route_remainder_name`` but do not pass ``route_name``,
534``route_remainder_name`` will be ignored.
535
536If you try to use ``resource_path`` or ``resource_url`` when the ``route_name``
537argument points at a route that does not have a remainder stararg, an error
538will not be raised, but the generated URL will not contain any remainder
539information either.
540
541All other values that are normally passable to ``resource_path`` and
542``resource_url`` (such as ``query``, ``anchor``, ``host``, ``port``, and
543positional elements) work as you might expect in this configuration.
544
545Note that this feature is incompatible with the ``__resource_url__`` feature
546(see :ref:`overriding_resource_url_generation`) implemented on resource
547objects.  Any  ``__resource_url__`` supplied by your resource will be ignored
548when you pass ``route_name``.
549