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