diff options
Diffstat (limited to 'docs/narr/urldispatch.rst')
| -rw-r--r-- | docs/narr/urldispatch.rst | 1427 |
1 files changed, 816 insertions, 611 deletions
diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index 5df1eb3af..c13558008 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -6,59 +6,40 @@ URL Dispatch ============ -:term:`URL dispatch` provides a simple way to map URLs to :term:`view` -code using a simple pattern matching language. An ordered set of -patterns is checked one-by-one. If one of the patterns matches the path -information associated with a request, a particular :term:`view -callable` is invoked. - -:term:`URL dispatch` is one of two ways to perform :term:`resource -location` in :app:`Pyramid`; the other way is using :term:`traversal`. -If no route is matched using :term:`URL dispatch`, :app:`Pyramid` falls -back to :term:`traversal` to handle the :term:`request`. - -It is the responsibility of the :term:`resource location` subsystem -(i.e., :term:`URL dispatch` or :term:`traversal`) to find the resource -object that is the :term:`context` of the :term:`request`. Once the -:term:`context` is determined, :term:`view lookup` is then responsible -for finding and invoking a :term:`view callable`. A view callable is a -specific bit of code, defined in your application, that receives the -:term:`request` and returns a :term:`response` object. - -Where appropriate, we will describe how view lookup interacts with -:term:`resource location`. The :ref:`view_config_chapter` chapter describes -the details of :term:`view lookup`. +:term:`URL dispatch` provides a simple way to map URLs to :term:`view` code +using a simple pattern matching language. An ordered set of patterns is +checked one by one. If one of the patterns matches the path information +associated with a request, a particular :term:`view callable` is invoked. A +view callable is a specific bit of code, defined in your application, that +receives the :term:`request` and returns a :term:`response` object. High-Level Operational Overview ------------------------------- -If route configuration is present in an application, the :app:`Pyramid` +If any route configuration is present in an application, the :app:`Pyramid` :term:`Router` checks every incoming request against an ordered set of URL matching patterns present in a *route map*. If any route pattern matches the information in the :term:`request`, -:app:`Pyramid` will invoke :term:`view lookup` using a :term:`context` -resource generated by the route match. +:app:`Pyramid` will invoke the :term:`view lookup` process to find a matching +view. -However, if no route pattern matches the information in the :term:`request` -provided to :app:`Pyramid`, it will fail over to using :term:`traversal` to -perform resource location and view lookup. +If no route pattern in the route map matches the information in the +:term:`request` provided in your application, :app:`Pyramid` will fail over to +using :term:`traversal` to perform resource location and view lookup. -Technically, URL dispatch is a :term:`resource location` mechanism (it finds -a context object). But ironically, using URL dispatch (instead of -:term:`traversal`) allows you to avoid thinking about your application in -terms of "resources" entirely, because it allows you to directly map a -:term:`view callable` to a route. +.. index:: + single: route configuration Route Configuration ------------------- :term:`Route configuration` is the act of adding a new :term:`route` to an -application. A route has a *name*, which acts as an identifier to be used -for URL generation. The name also allows developers to associate a view +application. A route has a *name*, which acts as an identifier to be used for +URL generation. The name also allows developers to associate a view configuration with the route. A route also has a *pattern*, meant to match against the ``PATH_INFO`` portion of a URL (the portion following the scheme -and port, e.g. ``/foo/bar`` in the URL ``http://localhost:8080/foo/bar``). It +and port, e.g., ``/foo/bar`` in the URL ``http://localhost:8080/foo/bar``). It also optionally has a ``factory`` and a set of :term:`route predicate` attributes. @@ -67,14 +48,13 @@ attributes. .. _config-add-route: -Configuring a Route via The ``add_route`` Configurator Method -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Configuring a Route to Match a View +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The :meth:`pyramid.config.Configurator.add_route` method adds a single :term:`route configuration` to the :term:`application registry`. Here's an example: -.. ignore-next-block .. code-block:: python # "config" below is presumed to be an instance of the @@ -84,106 +64,58 @@ example: config.add_route('myroute', '/prefix/{one}/{two}') config.add_view(myview, route_name='myroute') -.. versionchanged:: 1.0a4 - Prior to 1.0a4, routes allow for a marker starting with a ``:``, for - example ``/prefix/:one/:two``. This style is now deprecated - in favor of ``{}`` usage which allows for additional functionality. - -.. index:: - single: route configuration; view callable - -.. _add_route_view_config: - -Route Configuration That Names a View Callable -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. warning:: This section describes a feature which has been deprecated in - Pyramid 1.1 and higher. In order to reduce confusion and documentation - burden, passing view-related parameters to - :meth:`~pyramid.config.Configurator.add_route` is deprecated. - - In versions earlier than 1.1, a view was permitted to be connected to a - route using a set of ``view*`` parameters passed to the - :meth:`~pyramid.config.Configurator.add_route`. This was a shorthand - which replaced the need to perform a subsequent call to - :meth:`~pyramid.config.Configurator.add_view` as described in - :ref:`config-add-route`. For example, it was valid (and often recommended) - to do: - - .. code-block:: python - - config.add_route('home', '/', view='mypackage.views.myview', - view_renderer='some/renderer.pt') - - Instead of the equivalent: - - .. code-block:: python - - config.add_route('home', '/') - config.add_view('mypackage.views.myview', route_name='home') - renderer='some/renderer.pt') - - Passing ``view*`` arguments to ``add_route`` as shown in the first - example above is now deprecated in favor of connecting a view to a - predefined route via :meth:`~pyramid.config.Configurator.add_view` using - the route's ``route_name`` parameter, as shown in the second example - above. - - A deprecation warning is now issued when any view-related parameter is - passed to ``Configurator.add_route``. The recommended way to associate a - view with a route is documented in :ref:`config-add-route`. +When a :term:`view callable` added to the configuration by way of +:meth:`~pyramid.config.Configurator.add_view` becomes associated with a route +via its ``route_name`` predicate, that view callable will always be found and +invoked when the associated route pattern matches during a request. -When a route configuration declaration names a ``view`` attribute, the value -of the attribute will reference a :term:`view callable`. This view callable -will be invoked when the route matches. A view callable, as described in -:ref:`views_chapter`, is developer-supplied code that "does stuff" as the -result of a request. - -Here's an example route configuration that references a view callable: +More commonly, you will not use any ``add_view`` statements in your project's +"setup" code. You will instead use ``add_route`` statements, and use a +:term:`scan` to associate view callables with routes. For example, if this is +a portion of your project's ``__init__.py``: .. code-block:: python - :linenos: - # "config" below is presumed to be an instance of the - # pyramid.config.Configurator class; "myview" is assumed - # to be a "view callable" function - from myproject.views import myview - config.add_route('myroute', '/prefix/{one}/{two}', view=myview) + config.add_route('myroute', '/prefix/{one}/{two}') + config.scan('mypackage') -You can also pass a :term:`dotted Python name` as the ``view`` argument -rather than an actual callable: +Note that we don't call :meth:`~pyramid.config.Configurator.add_view` in this +setup code. However, the above :term:`scan` execution +``config.scan('mypackage')`` will pick up each :term:`configuration +decoration`, including any objects decorated with the +:class:`pyramid.view.view_config` decorator in the ``mypackage`` Python +package. For example, if you have a ``views.py`` in your package, a scan will +pick up any of its configuration decorators, so we can add one there that +references ``myroute`` as a ``route_name`` parameter: .. code-block:: python - :linenos: - # "config" below is presumed to be an instance of the - # pyramid.config.Configurator class; "myview" is assumed - # to be a "view callable" function - config.add_route('myroute', '/prefix/{one}/{two}', - view='myproject.views.myview') + from pyramid.view import view_config + from pyramid.response import Response -When a route configuration names a ``view`` attribute, the :term:`view -callable` named as that ``view`` attribute will always be found and invoked -when the associated route pattern matches during a request. + @view_config(route_name='myroute') + def myview(request): + return Response('OK') -See :meth:`pyramid.config.Configurator.add_route` for a description of -view-related arguments. +The above combination of ``add_route`` and ``scan`` is completely equivalent to +using the previous combination of ``add_route`` and ``add_view``. .. index:: single: route path pattern syntax .. _route_pattern_syntax: + Route Pattern Syntax ~~~~~~~~~~~~~~~~~~~~ -The syntax of the pattern matching language used by :app:`Pyramid` URL -dispatch in the *pattern* argument is straightforward; it is close to that of -the :term:`Routes` system used by :term:`Pylons`. +The syntax of the pattern matching language used by :app:`Pyramid` URL dispatch +in the *pattern* argument is straightforward. It is close to that of the +:term:`Routes` system used by :term:`Pylons`. -The *pattern* used in route configuration may start with a slash character. -If the pattern does not start with a slash character, an implicit slash will -be prepended to it at matching time. For example, the following patterns are +The *pattern* used in route configuration may start with a slash character. If +the pattern does not start with a slash character, an implicit slash will be +prepended to it at matching time. For example, the following patterns are equivalent: .. code-block:: text @@ -196,18 +128,34 @@ and: /{foo}/bar/baz -A pattern segment (an individual item between ``/`` characters in the -pattern) may either be a literal string (e.g. ``foo``) *or* it may be a -replacement marker (e.g. ``{foo}``) or a certain combination of both. A -replacement marker does not need to be preceded by a ``/`` character. +If a pattern is a valid URL it won't be matched against an incoming request. +Instead it can be useful for generating external URLs. See :ref:`External +routes <external_route_narr>` for details. + +A pattern segment (an individual item between ``/`` characters in the pattern) +may either be a literal string (e.g., ``foo``) *or* it may be a replacement +marker (e.g., ``{foo}``), or a certain combination of both. A replacement +marker does not need to be preceded by a ``/`` character. + +A replacement marker is in the format ``{name}``, where this means "accept any +characters up to the next slash character and use this as the ``name`` +:term:`matchdict` value." -A replacement marker is in the format ``{name}``, where this means "accept -any characters up to the next slash character and use this as the ``name`` -:term:`matchdict` value." A matchdict is the dictionary representing the -dynamic parts extracted from a URL based on the routing pattern. It is -available as ``request.matchdict``. For example, the following pattern -defines one literal segment (``foo``) and two replacement markers (``baz``, -and ``bar``): +A replacement marker in a pattern must begin with an uppercase or lowercase +ASCII letter or an underscore, and can be composed only of uppercase or +lowercase ASCII letters, underscores, and numbers. For example: ``a``, +``a_b``, ``_b``, and ``b9`` are all valid replacement marker names, but ``0a`` +is not. + +.. versionchanged:: 1.2 + A replacement marker could not start with an underscore until Pyramid 1.2. + Previous versions required that the replacement marker start with an + uppercase or lowercase letter. + +A matchdict is the dictionary representing the dynamic parts extracted from a +URL based on the routing pattern. It is available as ``request.matchdict``. +For example, the following pattern defines one literal segment (``foo``) and +two replacement markers (``baz``, and ``bar``): .. code-block:: text @@ -227,48 +175,47 @@ It will not match the following patterns however: foo/1/2/ -> No match (trailing slash) bar/abc/def -> First segment literal mismatch -The match for a segment replacement marker in a segment will be done only up -to the first non-alphanumeric character in the segment in the pattern. So, -for instance, if this route pattern was used: +The match for a segment replacement marker in a segment will be done only up to +the first non-alphanumeric character in the segment in the pattern. So, for +instance, if this route pattern was used: .. code-block:: text foo/{name}.html -The literal path ``/foo/biz.html`` will match the above route pattern, and -the match result will be ``{'name':u'biz'}``. However, the literal path -``/foo/biz`` will not match, because it does not contain a literal ``.html`` -at the end of the segment represented by ``{name}.html`` (it only contains +The literal path ``/foo/biz.html`` will match the above route pattern, and the +match result will be ``{'name':u'biz'}``. However, the literal path +``/foo/biz`` will not match, because it does not contain a literal ``.html`` at +the end of the segment represented by ``{name}.html`` (it only contains ``biz``, not ``biz.html``). To capture both segments, two replacement markers can be used: .. code-block:: text - + foo/{name}.{ext} -The literal path ``/foo/biz.html`` will match the above route pattern, and -the match result will be ``{'name': 'biz', 'ext': 'html'}``. This occurs -because there is a literal part of ``.`` (period) between the two replacement -markers ``{name}`` and ``{ext}``. +The literal path ``/foo/biz.html`` will match the above route pattern, and the +match result will be ``{'name': 'biz', 'ext': 'html'}``. This occurs because +there is a literal part of ``.`` (period) between the two replacement markers +``{name}`` and ``{ext}``. Replacement markers can optionally specify a regular expression which will be -used to decide whether a path segment should match the marker. To specify -that a replacement marker should match only a specific set of characters as -defined by a regular expression, you must use a slightly extended form of -replacement marker syntax. Within braces, the replacement marker name must -be followed by a colon, then directly thereafter, the regular expression. -The *default* regular expression associated with a replacement marker -``[^/]+`` matches one or more characters which are not a slash. For example, -under the hood, the replacement marker ``{foo}`` can more verbosely be -spelled as ``{foo:[^/]+}``. You can change this to be an arbitrary regular -expression to match an arbitrary sequence of characters, such as -``{foo:\d+}`` to match only digits. +used to decide whether a path segment should match the marker. To specify that +a replacement marker should match only a specific set of characters as defined +by a regular expression, you must use a slightly extended form of replacement +marker syntax. Within braces, the replacement marker name must be followed by +a colon, then directly thereafter, the regular expression. The *default* +regular expression associated with a replacement marker ``[^/]+`` matches one +or more characters which are not a slash. For example, under the hood, the +replacement marker ``{foo}`` can more verbosely be spelled as ``{foo:[^/]+}``. +You can change this to be an arbitrary regular expression to match an arbitrary +sequence of characters, such as ``{foo:\d+}`` to match only digits. It is possible to use two replacement markers without any literal characters between them, for instance ``/{foo}{bar}``. However, this would be a -nonsensical pattern without specifying a custom regular expression to -restrict what each marker captures. +nonsensical pattern without specifying a custom regular expression to restrict +what each marker captures. Segments must contain at least one character in order to match a segment replacement marker. For example, for the URL ``/abc/``: @@ -277,7 +224,7 @@ replacement marker. For example, for the URL ``/abc/``: - ``/{foo}/`` will match. -Note that values representing matched path segments will be url-unquoted and +Note that values representing matched path segments will be URL-unquoted and decoded from UTF-8 into Unicode within the matchdict. So for instance, the following pattern: @@ -289,7 +236,7 @@ When matching the following URL: .. code-block:: text - foo/La%20Pe%C3%B1a + http://example.com/foo/La%20Pe%C3%B1a The matchdict will look like so (the value is URL-decoded / UTF-8 decoded): @@ -297,10 +244,54 @@ The matchdict will look like so (the value is URL-decoded / UTF-8 decoded): {'bar':u'La Pe\xf1a'} +Literal strings in the path segment should represent the *decoded* value of the +``PATH_INFO`` provided to Pyramid. You don't want to use a URL-encoded value +or a bytestring representing the literal encoded as UTF-8 in the pattern. For +example, rather than this: + +.. code-block:: text + + /Foo%20Bar/{baz} + +You'll want to use something like this: + +.. code-block:: text + + /Foo Bar/{baz} + +For patterns that contain "high-order" characters in its literals, you'll want +to use a Unicode value as the pattern as opposed to any URL-encoded or +UTF-8-encoded value. For example, you might be tempted to use a bytestring +pattern like this: + +.. code-block:: text + + /La Pe\xc3\xb1a/{x} + +But this will either cause an error at startup time or it won't match properly. +You'll want to use a Unicode value as the pattern instead rather than raw +bytestring escapes. You can use a high-order Unicode value as the pattern by +using `Python source file encoding <http://www.python.org/dev/peps/pep-0263/>`_ +plus the "real" character in the Unicode pattern in the source, like so: + +.. code-block:: text + + /La Peña/{x} + +Or you can ignore source file encoding and use equivalent Unicode escape +characters in the pattern. + +.. code-block:: text + + /La Pe\xf1a/{x} + +Dynamic segment names cannot contain high-order characters, so this applies +only to literals in the pattern. + If the pattern has a ``*`` in it, the name which follows it is considered a "remainder match". A remainder match *must* come at the end of the pattern. -Unlike segment replacement markers, it does not need to be preceded by a -slash. For example: +Unlike segment replacement markers, it does not need to be preceded by a slash. +For example: .. code-block:: text @@ -310,15 +301,15 @@ The above pattern will match these URLs, generating the following matchdicts: .. code-block:: text - foo/1/2/ -> + foo/1/2/ -> {'baz':u'1', 'bar':u'2', 'fizzle':()} - foo/abc/def/a/b/c -> + foo/abc/def/a/b/c -> {'baz':u'abc', 'bar':u'def', 'fizzle':(u'a', u'b', u'c')} Note that when a ``*stararg`` remainder match is matched, the value put into the matchdict is turned into a tuple of path segments representing the -remainder of the path. These path segments are url-unquoted and decoded from +remainder of the path. These path segments are URL-unquoted and decoded from UTF-8 into Unicode. For example, for the following pattern: .. code-block:: text @@ -342,15 +333,15 @@ split by segment. Changing the regular expression used to match a marker can also capture the remainder of the URL, for example: .. code-block:: text - + foo/{baz}/{bar}{fizzle:.*} The above pattern will match these URLs, generating the following matchdicts: .. code-block:: text - foo/1/2/ -> {'baz':u'1', 'bar':u'2', 'fizzle':()} - foo/abc/def/a/b/c -> {'baz':u'abc', 'bar':u'def', 'fizzle': u'a/b/c')} + foo/1/2/ -> {'baz':u'1', 'bar':u'2', 'fizzle':u''} + foo/abc/def/a/b/c -> {'baz':u'abc', 'bar':u'def', 'fizzle': u'a/b/c'} This occurs because the default regular expression for a marker is ``[^/]+`` which will match everything up to the first ``/``, while ``{fizzle:.*}`` will @@ -365,16 +356,15 @@ Route Declaration Ordering Route configuration declarations are evaluated in a specific order when a request enters the system. As a result, the order of route configuration -declarations is very important. - -The order that routes declarations are evaluated is the order in which they -are added to the application at startup time. This is unlike -:term:`traversal`, which depends on emergent behavior which happens as a -result of traversing a resource tree. +declarations is very important. The order in which route declarations are +evaluated is the order in which they are added to the application at startup +time. (This is unlike a different way of mapping URLs to code that +:app:`Pyramid` provides, named :term:`traversal`, which does not depend on +pattern ordering). For routes added via the :mod:`~pyramid.config.Configurator.add_route` method, -the order that routes are evaluated is the order in which they are added to -the configuration imperatively. +the order that routes are evaluated is the order in which they are added to the +configuration imperatively. For example, route configuration statements with the following patterns might be added in the following order: @@ -384,47 +374,12 @@ be added in the following order: members/{def} members/abc -In such a configuration, the ``members/abc`` pattern would *never* be -matched. This is because the match ordering will always match -``members/{def}`` first; the route configuration with ``members/abc`` will -never be evaluated. +In such a configuration, the ``members/abc`` pattern would *never* be matched. +This is because the match ordering will always match ``members/{def}`` first; +the route configuration with ``members/abc`` will never be evaluated. .. index:: - single: route factory - -.. _route_factories: - -Route Factories -~~~~~~~~~~~~~~~ - -A "route" configuration declaration can mention a "factory". When that route -matches a request, and a factory is attached to a route, the :term:`root -factory` passed at startup time to the :term:`Configurator` is ignored; -instead the factory associated with the route is used to generate a -:term:`root` object. This object will usually be used as the :term:`context` -resource of the view callable ultimately found via :term:`view lookup`. - -.. code-block:: python - :linenos: - - config.add_route('abc', '/abc', - factory='myproject.resources.root_factory') - config.add_view('myproject.views.theview', route_name='abc') - -The factory can either be a Python object or a :term:`dotted Python name` (a -string) which points to such a Python object, as it is above. - -In this way, each route can use a different factory, making it possible to -supply a different :term:`context` resource object to the view related to -each particular route. - -Supplying a different resource factory for each route is useful when you're -trying to use a :app:`Pyramid` :term:`authorization policy` to provide -declarative, "context sensitive" security checks; each resource can maintain -a separate :term:`ACL`, as documented in -:ref:`using_security_with_urldispatch`. It is also useful when you wish to -combine URL dispatch with :term:`traversal` as documented within -:ref:`hybrid_chapter`. + single: route configuration arguments Route Configuration Arguments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -439,171 +394,40 @@ the associated route to be considered a match during the route matching process. Examples of route predicate arguments are ``pattern``, ``xhr``, and ``request_method``. -Other arguments are view configuration related arguments. These only have an -effect when the route configuration names a ``view``. These arguments have -been deprecated as of :app:`Pyramid` 1.1 (see :ref:`add_route_view_config`). - Other arguments are ``name`` and ``factory``. These arguments represent neither predicates nor view configuration information. -.. _custom_route_predicates: - -Custom Route Predicates -~~~~~~~~~~~~~~~~~~~~~~~ - -Each of the predicate callables fed to the ``custom_predicates`` argument of -:meth:`~pyramid.config.Configurator.add_route` must be a callable accepting -two arguments. The first argument passed to a custom predicate is a -dictionary conventionally named ``info``. The second argument is the current -:term:`request` object. - -The ``info`` dictionary has a number of contained values: ``match`` is a -dictionary: it represents the arguments matched in the URL by the route. -``route`` is an object representing the route which was matched (see -:class:`pyramid.interfaces.IRoute` for the API of such a route object). - -``info['match']`` is useful when predicates need access to the route match. -For example: - -.. code-block:: python - :linenos: - - def any_of(segment_name, *allowed): - def predicate(info, request): - if info['match'][segment_name] in allowed: - return True - return predicate - - num_one_two_or_three = any_of('num', 'one', 'two', 'three') - - config.add_route('route_to_num', '/{num}', - custom_predicates=(num_one_two_or_three,)) - -The above ``any_of`` function generates a predicate which ensures that the -match value named ``segment_name`` is in the set of allowable values -represented by ``allowed``. We use this ``any_of`` function to generate a -predicate function named ``num_one_two_or_three``, which ensures that the -``num`` segment is one of the values ``one``, ``two``, or ``three`` , and use -the result as a custom predicate by feeding it inside a tuple to the -``custom_predicates`` argument to -:meth:`~pyramid.config.Configurator.add_route`. - -A custom route predicate may also *modify* the ``match`` dictionary. For -instance, a predicate might do some type conversion of values: - -.. code-block:: python - :linenos: - - def integers(*segment_names): - def predicate(info, request): - match = info['match'] - for segment_name in segment_names: - try: - match[segment_name] = int(match[segment_name]) - except (TypeError, ValueError): - pass - return True - return predicate - - ymd_to_int = integers('year', 'month', 'day') - - config.add_route('ymd', '/{year}/{month}/{day}', - custom_predicates=(ymd_to_int,)) - -Note that a conversion predicate is still a predicate so it must return -``True`` or ``False``; a predicate that does *only* conversion, such as the -one we demonstrate above should unconditionally return ``True``. - -To avoid the try/except uncertainty, the route pattern can contain regular -expressions specifying requirements for that marker. For instance: - -.. code-block:: python - :linenos: - - def integers(*segment_names): - def predicate(info, request): - match = info['match'] - for segment_name in segment_names: - match[segment_name] = int(match[segment_name]) - return True - return predicate - - ymd_to_int = integers('year', 'month', 'day') - - config.add_route('ymd', '/{year:\d+}/{month:\d+}/{day:\d+}', - custom_predicates=(ymd_to_int,)) - -Now the try/except is no longer needed because the route will not match at -all unless these markers match ``\d+`` which requires them to be valid digits -for an ``int`` type conversion. - -The ``match`` dictionary passed within ``info`` to each predicate attached to -a route will be the same dictionary. Therefore, when registering a custom -predicate which modifies the ``match`` dict, the code registering the -predicate should usually arrange for the predicate to be the *last* custom -predicate in the custom predicate list. Otherwise, custom predicates which -fire subsequent to the predicate which performs the ``match`` modification -will receive the *modified* match dictionary. - -.. warning:: - - It is a poor idea to rely on ordering of custom predicates to build a - conversion pipeline, where one predicate depends on the side effect of - another. For instance, it's a poor idea to register two custom - predicates, one which handles conversion of a value to an int, the next - which handles conversion of that integer to some custom object. Just do - all that in a single custom predicate. - -The ``route`` object in the ``info`` dict is an object that has two useful -attributes: ``name`` and ``pattern``. The ``name`` attribute is the route -name. The ``pattern`` attribute is the route pattern. An example of using -the route in a set of route predicates: - -.. code-block:: python - :linenos: - - def twenty_ten(info, request): - if info['route'].name in ('ymd', 'ym', 'y'): - return info['match']['year'] == '2010' - - config.add_route('y', '/{year}', custom_predicates=(twenty_ten,)) - config.add_route('ym', '/{year}/{month}', custom_predicates=(twenty_ten,)) - config.add_route('ymd', '/{year}/{month}/{day}', - custom_predicates=(twenty_ten,)) - -The above predicate, when added to a number of route configurations ensures -that the year match argument is '2010' if and only if the route name is -'ymd', 'ym', or 'y'. - -See also :class:`pyramid.interfaces.IRoute` for more API documentation about -route objects. +.. index:: + single: route matching Route Matching -------------- The main purpose of route configuration is to match (or not match) the ``PATH_INFO`` present in the WSGI environment provided during a request -against a URL path pattern. +against a URL path pattern. ``PATH_INFO`` represents the path portion of the +URL that was requested. The way that :app:`Pyramid` does this is very simple. When a request enters the system, for each route configuration declaration present in the system, -:app:`Pyramid` checks the ``PATH_INFO`` against the pattern declared. - -If any route matches, the route matching process stops. The :term:`request` -is decorated with a special :term:`interface` which describes it as a "route -request", the :term:`context` resource is generated, and the context and the -resulting request are handed off to :term:`view lookup`. During view lookup, -if a :term:`view callable` associated with the matched route is found, that -view is called. - -When a route configuration is declared, it may contain :term:`route -predicate` arguments. All route predicates associated with a route -declaration must be ``True`` for the route configuration to be used for a -given request. - -If any predicate in the set of :term:`route predicate` arguments provided to -a route configuration returns ``False``, that route is skipped and route -matching continues through the ordered set of routes. +:app:`Pyramid` checks the request's ``PATH_INFO`` against the pattern +declared. This checking happens in the order that the routes were declared +via :meth:`pyramid.config.Configurator.add_route`. + +When a route configuration is declared, it may contain :term:`route predicate` +arguments. All route predicates associated with a route declaration must be +``True`` for the route configuration to be used for a given request during a +check. If any predicate in the set of :term:`route predicate` arguments +provided to a route configuration returns ``False`` during a check, that route +is skipped and route matching continues through the ordered set of routes. + +If any route matches, the route matching process stops and the :term:`view +lookup` subsystem takes over to find the most reasonable view callable for the +matched route. Most often, there's only one view that will match (a view +configured with a ``route_name`` argument matching the matched route). To gain +a better understanding of how routes and views are associated in a real +application, you can use the ``pviews`` command, as documented in +:ref:`displaying_matching_views`. If no route matches after all route patterns are exhausted, :app:`Pyramid` falls back to :term:`traversal` to do :term:`resource location` and @@ -618,11 +442,10 @@ The Matchdict ~~~~~~~~~~~~~ When the URL pattern associated with a particular route configuration is -matched by a request, a dictionary named ``matchdict`` is added as an -attribute of the :term:`request` object. Thus, ``request.matchdict`` will -contain the values that match replacement patterns in the ``pattern`` -element. The keys in a matchdict will be strings. The values will be -Unicode objects. +matched by a request, a dictionary named ``matchdict`` is added as an attribute +of the :term:`request` object. Thus, ``request.matchdict`` will contain the +values that match replacement patterns in the ``pattern`` element. The keys in +a matchdict will be strings. The values will be Unicode objects. .. note:: @@ -639,10 +462,10 @@ The Matched Route When the URL pattern associated with a particular route configuration is matched by a request, an object named ``matched_route`` is added as an -attribute of the :term:`request` object. Thus, ``request.matched_route`` -will be an object implementing the :class:`~pyramid.interfaces.IRoute` -interface which matched the request. The most useful attribute of the route -object is ``name``, which is the name of the route that matched. +attribute of the :term:`request` object. Thus, ``request.matched_route`` will +be an object implementing the :class:`~pyramid.interfaces.IRoute` interface +which matched the request. The most useful attribute of the route object is +``name``, which is the name of the route that matched. .. note:: @@ -653,8 +476,8 @@ Routing Examples ---------------- Let's check out some examples of how route configuration statements might be -commonly declared, and what will happen if they are matched by the -information present in a request. +commonly declared, and what will happen if they are matched by the information +present in a request. .. _urldispatch_example1: @@ -665,35 +488,41 @@ The simplest route declaration which configures a route match to *directly* result in a particular view callable being invoked: .. code-block:: python - :linenos: + :linenos: config.add_route('idea', 'site/{id}') - config.add_view('mypackage.views.site_view', route_name='idea') + config.scan() When a route configuration with a ``view`` attribute is added to the system, and an incoming request matches the *pattern* of the route configuration, the :term:`view callable` named as the ``view`` attribute of the route configuration will be invoked. -In the case of the above example, when the URL of a request matches -``/site/{id}``, the view callable at the Python dotted path name -``mypackage.views.site_view`` will be called with the request. In other -words, we've associated a view callable directly with a route pattern. +Recall that the ``@view_config`` is equivalent to calling ``config.add_view``, +because the ``config.scan()`` call will import ``mypackage.views``, shown +below, and execute ``config.add_view`` under the hood. Each view then maps the +route name to the matching view callable. In the case of the above example, +when the URL of a request matches ``/site/{id}``, the view callable at the +Python dotted path name ``mypackage.views.site_view`` will be called with the +request. In other words, we've associated a view callable directly with a +route pattern. When the ``/site/{id}`` route pattern matches during a request, the -``site_view`` view callable is invoked with that request as its sole -argument. When this route matches, a ``matchdict`` will be generated and -attached to the request as ``request.matchdict``. If the specific URL -matched is ``/site/1``, the ``matchdict`` will be a dictionary with a single -key, ``id``; the value will be the string ``'1'``, ex.: ``{'id':'1'}``. +``site_view`` view callable is invoked with that request as its sole argument. +When this route matches, a ``matchdict`` will be generated and attached to the +request as ``request.matchdict``. If the specific URL matched is ``/site/1``, +the ``matchdict`` will be a dictionary with a single key, ``id``; the value +will be the string ``'1'``, ex.: ``{'id':'1'}``. The ``mypackage.views`` module referred to above might look like so: .. code-block:: python :linenos: + from pyramid.view import view_config from pyramid.response import Response + @view_config(route_name='idea') def site_view(request): return Response(request.matchdict['id']) @@ -706,20 +535,39 @@ information about views. Example 2 ~~~~~~~~~ -Below is an example of a more complicated set of route statements you might -add to your application: +Below is an example of a more complicated set of route statements you might add +to your application: .. code-block:: python :linenos: config.add_route('idea', 'ideas/{idea}') config.add_route('user', 'users/{user}') - config.add_route('tag', 'tags/{tags}') + config.add_route('tag', 'tags/{tag}') + config.scan() + +Here is an example of a corresponding ``mypackage.views`` module: - config.add_view('mypackage.views.idea_view', route_name='idea') - config.add_view('mypackage.views.user_view', route_name='user') - config.add_view('mypackage.views.tag_view', route_name='tag') +.. code-block:: python + :linenos: + from pyramid.view import view_config + from pyramid.response import Response + + @view_config(route_name='idea') + def idea_view(request): + return Response(request.matchdict['id']) + + @view_config(route_name='user') + def user_view(request): + user = request.matchdict['user'] + return Response(u'The user is {}.'.format(user)) + + @view_config(route_name='tag') + def tag_view(request): + tag = request.matchdict['tag'] + return Response(u'The tag is {}.'.format(tag)) + The above configuration will allow :app:`Pyramid` to service URLs in these forms: @@ -735,33 +583,32 @@ forms: and attached to the :term:`request` will consist of ``{'idea':'1'}``. - When a URL matches the pattern ``/users/{user}``, the view callable - available at the dotted Python pathname ``mypackage.views.user_view`` will - be called. For the specific URL ``/users/1``, the ``matchdict`` generated - and attached to the :term:`request` will consist of ``{'user':'1'}``. + available at the dotted Python pathname ``mypackage.views.user_view`` will be + called. For the specific URL ``/users/1``, the ``matchdict`` generated and + attached to the :term:`request` will consist of ``{'user':'1'}``. - When a URL matches the pattern ``/tags/{tag}``, the view callable available at the dotted Python pathname ``mypackage.views.tag_view`` will be called. - For the specific URL ``/tags/1``, the ``matchdict`` generated and attached - to the :term:`request` will consist of ``{'tag':'1'}``. + For the specific URL ``/tags/1``, the ``matchdict`` generated and attached to + the :term:`request` will consist of ``{'tag':'1'}``. In this example we've again associated each of our routes with a :term:`view -callable` directly. In all cases, the request, which will have a -``matchdict`` attribute detailing the information found in the URL by the -process will be passed to the view callable. +callable` directly. In all cases, the request, which will have a ``matchdict`` +attribute detailing the information found in the URL by the process will be +passed to the view callable. Example 3 ~~~~~~~~~ -The :term:`context` resource object passed in to a view found as the result -of URL dispatch will, by default, be an instance of the object returned by -the :term:`root factory` configured at startup time (the ``root_factory`` -argument to the :term:`Configurator` used to configure the application). +The :term:`context` resource object passed in to a view found as the result of +URL dispatch will, by default, be an instance of the object returned by the +:term:`root factory` configured at startup time (the ``root_factory`` argument +to the :term:`Configurator` used to configure the application). You can override this behavior by passing in a ``factory`` argument to the :meth:`~pyramid.config.Configurator.add_route` method for a particular route. -The ``factory`` should be a callable that accepts a :term:`request` and -returns an instance of a class that will be the context resource used by the -view. +The ``factory`` should be a callable that accepts a :term:`request` and returns +an instance of a class that will be the context resource used by the view. An example of using a route with a factory: @@ -769,7 +616,7 @@ An example of using a route with a factory: :linenos: config.add_route('idea', 'ideas/{idea}', factory='myproject.resources.Idea') - config.add_view('myproject.views.idea_view', route_name='idea') + config.scan() The above route will manufacture an ``Idea`` resource as a :term:`context`, assuming that ``mypackage.resources.Idea`` resolves to a class that accepts a @@ -783,11 +630,27 @@ request in its ``__init__``. For example: pass In a more complicated application, this root factory might be a class -representing a :term:`SQLAlchemy` model. +representing a :term:`SQLAlchemy` model. The view ``mypackage.views.idea_view`` +might look like this: + +.. code-block:: python + :linenos: + + @view_config(route_name='idea') + def idea_view(request): + idea = request.context + return Response(idea) + +Here, ``request.context`` is an instance of ``Idea``. If indeed the resource +object is a SQLAlchemy model, you do not even have to perform a query in the +view callable, since you have access to the resource via ``request.context``. + +See :ref:`route_factories` for more details about how to use route factories. .. index:: single: matching the root URL single: root url (matching) + pair: matching; root URL Matching the Root URL --------------------- @@ -812,24 +675,89 @@ Or provide the literal string ``/`` as the pattern: single: generating route URLs single: route URLs +.. _generating_route_urls: + Generating Route URLs --------------------- -Use the :func:`pyramid.url.route_url` function to generate URLs based on -route patterns. For example, if you've configured a route with the ``name`` +Use the :meth:`pyramid.request.Request.route_url` method to generate URLs based +on route patterns. For example, if you've configured a route with the ``name`` "foo" and the ``pattern`` "{a}/{b}/{c}", you might do this. -.. ignore-next-block .. code-block:: python :linenos: - from pyramid.url import route_url - url = route_url('foo', request, a='1', b='2', c='3') + url = request.route_url('foo', a='1', b='2', c='3') This would return something like the string ``http://example.com/1/2/3`` (at least if the current protocol and hostname implied ``http://example.com``). -See the :func:`~pyramid.url.route_url` API documentation for more -information. + +To generate only the *path* portion of a URL from a route, use the +:meth:`pyramid.request.Request.route_path` API instead of +:meth:`~pyramid.request.Request.route_url`. + +.. code-block:: python + + url = request.route_path('foo', a='1', b='2', c='3') + +This will return the string ``/1/2/3`` rather than a full URL. + +Replacement values passed to ``route_url`` or ``route_path`` must be Unicode or +bytestrings encoded in UTF-8. One exception to this rule exists: if you're +trying to replace a "remainder" match value (a ``*stararg`` replacement value), +the value may be a tuple containing Unicode strings or UTF-8 strings. + +Note that URLs and paths generated by ``route_url`` and ``route_path`` are +always URL-quoted string types (they contain no non-ASCII characters). +Therefore, if you've added a route like so: + +.. code-block:: python + + config.add_route('la', u'/La Peña/{city}') + +And you later generate a URL using ``route_path`` or ``route_url`` like so: + +.. code-block:: python + + url = request.route_path('la', city=u'Québec') + +You will wind up with the path encoded to UTF-8 and URL-quoted like so: + +.. code-block:: text + + /La%20Pe%C3%B1a/Qu%C3%A9bec + +If you have a ``*stararg`` remainder dynamic part of your route pattern: + +.. code-block:: python + + config.add_route('abc', 'a/b/c/*foo') + +And you later generate a URL using ``route_path`` or ``route_url`` using a +*string* as the replacement value: + +.. code-block:: python + + url = request.route_path('abc', foo=u'Québec/biz') + +The value you pass will be URL-quoted except for embedded slashes in the +result: + +.. code-block:: text + + /a/b/c/Qu%C3%A9bec/biz + +You can get a similar result by passing a tuple composed of path elements: + +.. code-block:: python + + url = request.route_path('abc', foo=(u'Québec', u'biz')) + +Each value in the tuple will be URL-quoted and joined by slashes in this case: + +.. code-block:: text + + /a/b/c/Qu%C3%A9bec/biz .. index:: single: static routes @@ -856,9 +784,38 @@ other non-``name`` and non-``pattern`` arguments to exception to this rule is use of the ``pregenerator`` argument, which is not ignored when ``static`` is ``True``. -.. note:: the ``static`` argument to - :meth:`~pyramid.config.Configurator.add_route` is new as of :app:`Pyramid` - 1.1. +:ref:`External routes <external_route_narr>` are implicitly static. + +.. versionadded:: 1.1 + the ``static`` argument to :meth:`~pyramid.config.Configurator.add_route`. + +.. _external_route_narr: + + +External Routes +--------------- + +.. versionadded:: 1.5 + +Route patterns that are valid URLs, are treated as external routes. Like +:ref:`static routes <static_route_narr>` they are useful for URL generation +purposes only and are never considered for matching at request time. + +.. code-block:: python + :linenos: + + >>> config = Configurator() + >>> config.add_route('youtube', 'https://youtube.com/watch/{video_id}') + ... + >>> request.route_url('youtube', video_id='oHg5SJYRHA0') + >>> "https://youtube.com/watch/oHg5SJYRHA0" + +Most pattern replacements and calls to +:meth:`pyramid.request.Request.route_url` will work as expected. However, calls +to :meth:`pyramid.request.Request.route_path` against external patterns will +raise an exception, and passing ``_app_url`` to +:meth:`~pyramid.request.Request.route_url` to generate a URL against a route +that has an external pattern will also raise an exception. .. index:: single: redirecting to slash-appended routes @@ -868,249 +825,495 @@ ignored when ``static`` is ``True``. Redirecting to Slash-Appended Routes ------------------------------------ -For behavior like Django's ``APPEND_SLASH=True``, use the -:func:`~pyramid.view.append_slash_notfound_view` view as the :term:`Not Found -view` in your application. Defining this view as the :term:`Not Found view` -is a way to automatically redirect requests where the URL lacks a trailing -slash, but requires one to match the proper route. When configured, along -with at least one other route in your application, this view will be invoked -if the value of ``PATH_INFO`` does not already end in a slash, and if the -value of ``PATH_INFO`` *plus* a slash matches any route's pattern. In this -case it does an HTTP redirect to the slash-appended ``PATH_INFO``. +For behavior like Django's ``APPEND_SLASH=True``, use the ``append_slash`` +argument to :meth:`pyramid.config.Configurator.add_notfound_view` or the +equivalent ``append_slash`` argument to the +:class:`pyramid.view.notfound_view_config` decorator. -Let's use an example, because this behavior is a bit magical. If the -``append_slash_notfound_view`` is configured in your application and your -route configuration looks like so: +Adding ``append_slash=True`` is a way to automatically redirect requests where +the URL lacks a trailing slash, but requires one to match the proper route. +When configured, along with at least one other route in your application, this +view will be invoked if the value of ``PATH_INFO`` does not already end in a +slash, and if the value of ``PATH_INFO`` *plus* a slash matches any route's +pattern. In this case it does an HTTP redirect to the slash-appended +``PATH_INFO``. In addition you may pass anything that implements +:class:`pyramid.interfaces.IResponse` which will then be used in place of the +default class (:class:`pyramid.httpexceptions.HTTPFound`). + +Let's use an example. If the following routes are configured in your +application: .. code-block:: python :linenos: - config.add_route('noslash', 'no_slash') - config.add_route('hasslash', 'has_slash/') + from pyramid.httpexceptions import HTTPNotFound + + def notfound(request): + return HTTPNotFound('Not found, bro.') + + def no_slash(request): + return Response('No slash') - config.add_view('myproject.views.no_slash', route_name='noslash') - config.add_view('myproject.views.has_slash', route_name='hasslash') + def has_slash(request): + return Response('Has slash') + + def main(g, **settings): + config = Configurator() + config.add_route('noslash', 'no_slash') + config.add_route('hasslash', 'has_slash/') + config.add_view(no_slash, route_name='noslash') + config.add_view(has_slash, route_name='hasslash') + config.add_notfound_view(notfound, append_slash=True) + +If a request enters the application with the ``PATH_INFO`` value of +``/no_slash``, the first route will match and the browser will show "No slash". +However, if a request enters the application with the ``PATH_INFO`` value of +``/no_slash/``, *no* route will match, and the slash-appending not found view +will not find a matching route with an appended slash. As a result, the +``notfound`` view will be called and it will return a "Not found, bro." body. If a request enters the application with the ``PATH_INFO`` value of ``/has_slash/``, the second route will match. If a request enters the application with the ``PATH_INFO`` value of ``/has_slash``, a route *will* be -found by the slash-appending not found view. An HTTP redirect to -``/has_slash/`` will be returned to the user's browser. +found by the slash-appending :term:`Not Found View`. An HTTP redirect to +``/has_slash/`` will be returned to the user's browser. As a result, the +``notfound`` view will never actually be called. -If a request enters the application with the ``PATH_INFO`` value of -``/no_slash``, the first route will match. However, if a request enters the -application with the ``PATH_INFO`` value of ``/no_slash/``, *no* route will -match, and the slash-appending not found view will *not* find a matching -route with an appended slash. +The following application uses the :class:`pyramid.view.notfound_view_config` +and :class:`pyramid.view.view_config` decorators and a :term:`scan` to do +exactly the same job: + +.. code-block:: python + :linenos: + + from pyramid.httpexceptions import HTTPNotFound + from pyramid.view import notfound_view_config, view_config + + @notfound_view_config(append_slash=True) + def notfound(request): + return HTTPNotFound('Not found, bro.') + + @view_config(route_name='noslash') + def no_slash(request): + return Response('No slash') + + @view_config(route_name='hasslash') + def has_slash(request): + return Response('Has slash') + + def main(g, **settings): + config = Configurator() + config.add_route('noslash', 'no_slash') + config.add_route('hasslash', 'has_slash/') + config.scan() .. warning:: - You **should not** rely on this mechanism to redirect ``POST`` requests. - The redirect of the slash-appending not found view will turn a ``POST`` - request into a ``GET``, losing any ``POST`` data in the original - request. + You **should not** rely on this mechanism to redirect ``POST`` requests. + The redirect of the slash-appending :term:`Not Found View` will turn a + ``POST`` request into a ``GET``, losing any ``POST`` data in the original + request. + +See :ref:`view_module` and :ref:`changing_the_notfound_view` for a more +general description of how to configure a view and/or a :term:`Not Found View`. + +.. index:: + pair: debugging; route matching + +.. _debug_routematch_section: + +Debugging Route Matching +------------------------ + +It's useful to be able to take a peek under the hood when requests that enter +your application aren't matching your routes as you expect them to. To debug +route matching, use the ``PYRAMID_DEBUG_ROUTEMATCH`` environment variable or +the ``pyramid.debug_routematch`` configuration file setting (set either to +``true``). Details of the route matching decision for a particular request to +the :app:`Pyramid` application will be printed to the ``stderr`` of the console +which you started the application from. For example: + +.. code-block:: text + :linenos: + + $ PYRAMID_DEBUG_ROUTEMATCH=true $VENV/bin/pserve development.ini + Starting server in PID 13586. + serving on 0.0.0.0:6543 view at http://127.0.0.1:6543 + 2010-12-16 14:45:19,956 no route matched for url \ + http://localhost:6543/wontmatch + 2010-12-16 14:45:20,010 no route matched for url \ + http://localhost:6543/favicon.ico + 2010-12-16 14:41:52,084 route matched for url \ + http://localhost:6543/static/logo.png; \ + route_name: 'static/', .... + +See :ref:`environment_chapter` for more information about how and where to set +these values. + +You can also use the ``proutes`` command to see a display of all the routes +configured in your application. For more information, see +:ref:`displaying_application_routes`. + +.. _route_prefix: + +Using a Route Prefix to Compose Applications +-------------------------------------------- + +.. versionadded:: 1.2 + +The :meth:`pyramid.config.Configurator.include` method allows configuration +statements to be included from separate files. See +:ref:`building_an_extensible_app` for information about this method. Using +:meth:`pyramid.config.Configurator.include` allows you to build your +application from small and potentially reusable components. + +The :meth:`pyramid.config.Configurator.include` method accepts an argument +named ``route_prefix`` which can be useful to authors of URL-dispatch-based +applications. If ``route_prefix`` is supplied to the include method, it must +be a string. This string represents a route prefix that will be prepended to +all route patterns added by the *included* configuration. Any calls to +:meth:`pyramid.config.Configurator.add_route` within the included callable will +have their pattern prefixed with the value of ``route_prefix``. This can be +used to help mount a set of routes at a different location than the included +callable's author intended while still maintaining the same route names. For +example: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator -To configure the slash-appending not found view in your application, change -the application's startup configuration, adding the following stanza: + def users_include(config): + config.add_route('show_users', '/show') + + def main(global_config, **settings): + config = Configurator() + config.include(users_include, route_prefix='/users') + +In the above configuration, the ``show_users`` route will have an effective +route pattern of ``/users/show`` instead of ``/show`` because the +``route_prefix`` argument will be prepended to the pattern. The route will +then only match if the URL path is ``/users/show``, and when the +:meth:`pyramid.request.Request.route_url` function is called with the route +name ``show_users``, it will generate a URL with that same path. + +Route prefixes are recursive, so if a callable executed via an include itself +turns around and includes another callable, the second-level route prefix will +be prepended with the first: .. code-block:: python :linenos: - config.add_view('pyramid.view.append_slash_notfound_view', - context='pyramid.exceptions.NotFound') + from pyramid.config import Configurator + + def timing_include(config): + config.add_route('show_times', '/times') -See :ref:`view_module` and :ref:`changing_the_notfound_view` for more -information about the slash-appending not found view and for a more general -description of how to configure a not found view. + def users_include(config): + config.add_route('show_users', '/show') + config.include(timing_include, route_prefix='/timing') -Custom Not Found View With Slash Appended Routes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + def main(global_config, **settings): + config = Configurator() + config.include(users_include, route_prefix='/users') -There can only be one :term:`Not Found view` in any :app:`Pyramid` -application. Even if you use :func:`~pyramid.view.append_slash_notfound_view` -as the Not Found view, :app:`Pyramid` still must generate a ``404 Not Found`` -response when it cannot redirect to a slash-appended URL; this not found -response will be visible to site users. +In the above configuration, the ``show_users`` route will still have an +effective route pattern of ``/users/show``. The ``show_times`` route, however, +will have an effective pattern of ``/users/timing/times``. -If you don't care what this 404 response looks like, and only you need -redirections to slash-appended route URLs, you may use the -:func:`~pyramid.view.append_slash_notfound_view` object as the Not Found view -as described above. However, if you wish to use a *custom* notfound view -callable when a URL cannot be redirected to a slash-appended URL, you may -wish to use an instance of the -:class:`~pyramid.view.AppendSlashNotFoundViewFactory` class as the Not Found -view, supplying a :term:`view callable` to be used as the custom notfound -view as the first argument to its constructor. For instance: +Route prefixes have no impact on the requirement that the set of route *names* +in any given Pyramid configuration must be entirely unique. If you compose +your URL dispatch application out of many small subapplications using +:meth:`pyramid.config.Configurator.include`, it's wise to use a dotted name for +your route names so they'll be unlikely to conflict with other packages that +may be added in the future. For example: .. code-block:: python - :linenos: + :linenos: - from pyramid.exceptions import NotFound - from pyramid.view import AppendSlashNotFoundViewFactory + from pyramid.config import Configurator - def notfound_view(context, request): - return HTTPNotFound('It aint there, stop trying!') + def timing_include(config): + config.add_route('timing.show_times', '/times') - custom_append_slash = AppendSlashNotFoundViewFactory(notfound_view) - config.add_view(custom_append_slash, context=NotFound) + def users_include(config): + config.add_route('users.show_users', '/show') + config.include(timing_include, route_prefix='/timing') -The ``notfound_view`` supplied must adhere to the two-argument view callable -calling convention of ``(context, request)`` (``context`` will be the -exception object). + def main(global_config, **settings): + config = Configurator() + config.include(users_include, route_prefix='/users') -.. _cleaning_up_after_a_request: +.. index:: + single: route predicates (custom) -Cleaning Up After a Request ---------------------------- +.. _custom_route_predicates: -Sometimes it's required that some cleanup be performed at the end of a -request when a database connection is involved. +Custom Route Predicates +----------------------- + +Each of the predicate callables fed to the ``custom_predicates`` argument of +:meth:`~pyramid.config.Configurator.add_route` must be a callable accepting two +arguments. The first argument passed to a custom predicate is a dictionary +conventionally named ``info``. The second argument is the current +:term:`request` object. -For example, let's say you have a ``mypackage`` :app:`Pyramid` application -package that uses SQLAlchemy, and you'd like the current SQLAlchemy database -session to be removed after each request. Put the following in the -``mypackage.__init__`` module: +The ``info`` dictionary has a number of contained values, including ``match`` +and ``route``. ``match`` is a dictionary which represents the arguments matched +in the URL by the route. ``route`` is an object representing the route which +was matched (see :class:`pyramid.interfaces.IRoute` for the API of such a route +object). + +``info['match']`` is useful when predicates need access to the route match. +For example: -.. ignore-next-block .. code-block:: python :linenos: - from mypackage.models import DBSession + def any_of(segment_name, *allowed): + def predicate(info, request): + if info['match'][segment_name] in allowed: + return True + return predicate - from pyramid.events import subscriber - from pyramid.events import NewRequest + num_one_two_or_three = any_of('num', 'one', 'two', 'three') - def cleanup_callback(request): - DBSession.remove() + config.add_route('route_to_num', '/{num}', + custom_predicates=(num_one_two_or_three,)) - @subscriber(NewRequest) - def add_cleanup_callback(event): - event.request.add_finished_callback(cleanup_callback) +The above ``any_of`` function generates a predicate which ensures that the +match value named ``segment_name`` is in the set of allowable values +represented by ``allowed``. We use this ``any_of`` function to generate a +predicate function named ``num_one_two_or_three``, which ensures that the +``num`` segment is one of the values ``one``, ``two``, or ``three`` , and use +the result as a custom predicate by feeding it inside a tuple to the +``custom_predicates`` argument to +:meth:`~pyramid.config.Configurator.add_route`. -Registering the ``cleanup_callback`` finished callback at the start of a -request (by causing the ``add_cleanup_callback`` to receive a -:class:`pyramid.events.NewRequest` event at the start of each request) will -cause the DBSession to be removed whenever request processing has ended. -Note that in the example above, for the :class:`pyramid.events.subscriber` -decorator to "work", the :meth:`pyramid.config.Configurator.scan` method must -be called against your ``mypackage`` package during application -initialization. +A custom route predicate may also *modify* the ``match`` dictionary. For +instance, a predicate might do some type conversion of values: -.. note:: This is only an example. In particular, it is not necessary to - cause ``DBSession.remove`` to be called in an application generated from - any :app:`Pyramid` scaffold, because these all use the - ``repoze.tm2`` middleware. The cleanup done by ``DBSession.remove`` is - unnecessary when ``repoze.tm2`` middleware is in the WSGI pipeline. +.. code-block:: python + :linenos: -.. index:: - pair: URL dispatch; security + def integers(*segment_names): + def predicate(info, request): + match = info['match'] + for segment_name in segment_names: + try: + match[segment_name] = int(match[segment_name]) + except (TypeError, ValueError): + pass + return True + return predicate -.. _using_security_with_urldispatch: + ymd_to_int = integers('year', 'month', 'day') -Using :app:`Pyramid` Security With URL Dispatch --------------------------------------------------- + config.add_route('ymd', '/{year}/{month}/{day}', + custom_predicates=(ymd_to_int,)) -:app:`Pyramid` provides its own security framework which consults an -:term:`authorization policy` before allowing any application code to be -called. This framework operates in terms of an access control list, which is -stored as an ``__acl__`` attribute of a resource object. A common thing to -want to do is to attach an ``__acl__`` to the resource object dynamically for -declarative security purposes. You can use the ``factory`` argument that -points at a factory which attaches a custom ``__acl__`` to an object at its -creation time. +Note that a conversion predicate is still a predicate, so it must return +``True`` or ``False``. A predicate that does *only* conversion, such as the one +we demonstrate above, should unconditionally return ``True``. -Such a ``factory`` might look like so: +To avoid the try/except uncertainty, the route pattern can contain regular +expressions specifying requirements for that marker. For instance: + +.. code-block:: python + :linenos: + + def integers(*segment_names): + def predicate(info, request): + match = info['match'] + for segment_name in segment_names: + match[segment_name] = int(match[segment_name]) + return True + return predicate + + ymd_to_int = integers('year', 'month', 'day') + + config.add_route('ymd', '/{year:\d+}/{month:\d+}/{day:\d+}', + custom_predicates=(ymd_to_int,)) + +Now the try/except is no longer needed because the route will not match at all +unless these markers match ``\d+`` which requires them to be valid digits for +an ``int`` type conversion. + +The ``match`` dictionary passed within ``info`` to each predicate attached to a +route will be the same dictionary. Therefore, when registering a custom +predicate which modifies the ``match`` dict, the code registering the predicate +should usually arrange for the predicate to be the *last* custom predicate in +the custom predicate list. Otherwise, custom predicates which fire subsequent +to the predicate which performs the ``match`` modification will receive the +*modified* match dictionary. + +.. warning:: + + It is a poor idea to rely on ordering of custom predicates to build a + conversion pipeline, where one predicate depends on the side effect of + another. For instance, it's a poor idea to register two custom predicates, + one which handles conversion of a value to an int, the next which handles + conversion of that integer to some custom object. Just do all that in a + single custom predicate. + +The ``route`` object in the ``info`` dict is an object that has two useful +attributes: ``name`` and ``pattern``. The ``name`` attribute is the route name. +The ``pattern`` attribute is the route pattern. Here's an example of using the +route in a set of route predicates: + +.. code-block:: python + :linenos: + + def twenty_ten(info, request): + if info['route'].name in ('ymd', 'ym', 'y'): + return info['match']['year'] == '2010' + + config.add_route('y', '/{year}', custom_predicates=(twenty_ten,)) + config.add_route('ym', '/{year}/{month}', custom_predicates=(twenty_ten,)) + config.add_route('ymd', '/{year}/{month}/{day}', + custom_predicates=(twenty_ten,)) + +The above predicate, when added to a number of route configurations ensures +that the year match argument is '2010' if and only if the route name is 'ymd', +'ym', or 'y'. + +You can also caption the predicates by setting the ``__text__`` attribute. This +will help you with the ``pviews`` command (see +:ref:`displaying_application_routes`) and the ``pyramid_debugtoolbar``. + +If a predicate is a class, just add ``__text__`` property in a standard manner. .. code-block:: python :linenos: - class Article(object): - def __init__(self, request): - matchdict = request.matchdict - article = matchdict.get('article', None) - if article == '1': - self.__acl__ = [ (Allow, 'editor', 'view') ] + class DummyCustomPredicate1(object): + def __init__(self): + self.__text__ = 'my custom class predicate' -If the route ``archives/{article}`` is matched, and the article number is -``1``, :app:`Pyramid` will generate an ``Article`` :term:`context` resource -with an ACL on it that allows the ``editor`` principal the ``view`` -permission. Obviously you can do more generic things than inspect the routes -match dict to see if the ``article`` argument matches a particular string; -our sample ``Article`` factory class is not very ambitious. + class DummyCustomPredicate2(object): + __text__ = 'my custom class predicate' -.. note:: See :ref:`security_chapter` for more information about - :app:`Pyramid` security and ACLs. +If a predicate is a method, you'll need to assign it after method declaration +(see `PEP 232 <http://www.python.org/dev/peps/pep-0232/>`_). -.. _debug_routematch_section: +.. code-block:: python + :linenos: -Debugging Route Matching ------------------------- + def custom_predicate(): + pass + custom_predicate.__text__ = 'my custom method predicate' -It's useful to be able to take a peek under the hood when requests that enter -your application arent matching your routes as you expect them to. To debug -route matching, use the ``PYRAMID_DEBUG_ROUTEMATCH`` environment variable or the -``debug_routematch`` configuration file setting (set either to ``true``). -Details of the route matching decision for a particular request to the -:app:`Pyramid` application will be printed to the ``stderr`` of the console -which you started the application from. For example: +If a predicate is a classmethod, using ``@classmethod`` will not work, but you +can still easily do it by wrapping it in a classmethod call. -.. code-block:: text +.. code-block:: python :linenos: - [chrism@thinko pylonsbasic]$ PYRAMID_DEBUG_ROUTEMATCH=true \ - bin/paster serve development.ini - Starting server in PID 13586. - serving on 0.0.0.0:6543 view at http://127.0.0.1:6543 - 2010-12-16 14:45:19,956 no route matched for url \ - http://localhost:6543/wontmatch - 2010-12-16 14:45:20,010 no route matched for url \ - http://localhost:6543/favicon.ico - 2010-12-16 14:41:52,084 route matched for url \ - http://localhost:6543/static/logo.png; \ - route_name: 'static/', .... + def classmethod_predicate(): + pass + classmethod_predicate.__text__ = 'my classmethod predicate' + classmethod_predicate = classmethod(classmethod_predicate) + +The same will work with ``staticmethod``, using ``staticmethod`` instead of +``classmethod``. + +.. seealso:: -See :ref:`environment_chapter` for more information about how, and where to -set these values. + See also :class:`pyramid.interfaces.IRoute` for more API documentation + about route objects. .. index:: - pair: routes; printing - single: paster proutes + single: route factory -.. _displaying_application_routes: +.. _route_factories: -Displaying All Application Routes ---------------------------------- +Route Factories +--------------- -You can use the ``paster proutes`` command in a terminal window to print a -summary of routes related to your application. Much like the ``paster -pshell`` command (see :ref:`interactive_shell`), the ``paster proutes`` -command accepts two arguments. The first argument to ``proutes`` is the path -to your application's ``.ini`` file. The second is the ``app`` section name -inside the ``.ini`` file which points to your application. +Although it is not a particularly common need in basic applications, a "route" +configuration declaration can mention a "factory". When a route matches a +request, and a factory is attached to the route, the :term:`root factory` +passed at startup time to the :term:`Configurator` is ignored. Instead the +factory associated with the route is used to generate a :term:`root` object. +This object will usually be used as the :term:`context` resource of the view +callable ultimately found via :term:`view lookup`. -For example: +.. code-block:: python + :linenos: -.. code-block:: text + config.add_route('abc', '/abc', + factory='myproject.resources.root_factory') + config.add_view('myproject.views.theview', route_name='abc') + +The factory can either be a Python object or a :term:`dotted Python name` (a +string) which points to such a Python object, as it is above. + +In this way, each route can use a different factory, making it possible to +supply a different :term:`context` resource object to the view related to each +particular route. + +A factory must be a callable which accepts a request and returns an arbitrary +Python object. For example, the below class can be used as a factory: + +.. code-block:: python :linenos: - [chrism@thinko MyProject]$ ../bin/paster proutes development.ini MyProject - Name Pattern View - ---- ------- ---- - home / <function my_view> - home2 / <function my_view> - another /another None - static/ static/*subpath <static_view object> - catchall /*subpath <function static_view> - -``paster proutes`` generates a table. The table has three columns: a Name -name column, a Pattern column, and a View column. The items listed in the -Name column are route names, the items listen in the Pattern column are route -patterns, and the items listed in the View column are representations of the -view callable that will be invoked when a request matches the associated -route pattern. The view column may show ``None`` if no associated view -callable could be found. If no routes are configured within your -application, nothing will be printed to the console when ``paster proutes`` -is executed. + class Mine(object): + def __init__(self, request): + pass + +A route factory is actually conceptually identical to the :term:`root factory` +described at :ref:`the_resource_tree`. + +Supplying a different resource factory for each route is useful when you're +trying to use a :app:`Pyramid` :term:`authorization policy` to provide +declarative, "context sensitive" security checks. Each resource can maintain a +separate :term:`ACL`, as documented in :ref:`using_security_with_urldispatch`. +It is also useful when you wish to combine URL dispatch with :term:`traversal` +as documented within :ref:`hybrid_chapter`. + +.. index:: + pair: URL dispatch; security + +.. _using_security_with_urldispatch: + +Using :app:`Pyramid` Security with URL Dispatch +----------------------------------------------- + +:app:`Pyramid` provides its own security framework which consults an +:term:`authorization policy` before allowing any application code to be called. +This framework operates in terms of an access control list, which is stored as +an ``__acl__`` attribute of a resource object. A common thing to want to do is +to attach an ``__acl__`` to the resource object dynamically for declarative +security purposes. You can use the ``factory`` argument that points at a +factory which attaches a custom ``__acl__`` to an object at its creation time. + +Such a ``factory`` might look like so: + +.. code-block:: python + :linenos: + + class Article(object): + def __init__(self, request): + matchdict = request.matchdict + article = matchdict.get('article', None) + if article == '1': + self.__acl__ = [ (Allow, 'editor', 'view') ] + +If the route ``archives/{article}`` is matched, and the article number is +``1``, :app:`Pyramid` will generate an ``Article`` :term:`context` resource +with an ACL on it that allows the ``editor`` principal the ``view`` permission. +Obviously you can do more generic things than inspect the route's match dict to +see if the ``article`` argument matches a particular string. Our sample +``Article`` factory class is not very ambitious. + +.. note:: + + See :ref:`security_chapter` for more information about :app:`Pyramid` + security and ACLs. + +.. index:: + pair: route; view callable lookup details Route View Callable Registration and Lookup Details --------------------------------------------------- @@ -1119,10 +1322,9 @@ When a request enters the system which matches the pattern of the route, the usual result is simple: the view callable associated with the route is invoked with the request that caused the invocation. -For most usage, you needn't understand more than this; how it works is an -implementation detail. In the interest of completeness, however, we'll -explain how it *does* work in the this section. You can skip it if you're -uninterested. +For most usage, you needn't understand more than this. How it works is an +implementation detail. In the interest of completeness, however, we'll explain +how it *does* work in this section. You can skip it if you're uninterested. When a view is associated with a route configuration, :app:`Pyramid` ensures that a :term:`view configuration` is registered that will always be found @@ -1138,25 +1340,28 @@ when the route pattern is matched during a request. To do so: - At runtime, when a request causes any route to match, the :term:`request` object is decorated with the route-specific interface. -- The fact that the request is decorated with a route-specific interface - causes the view lookup machinery to always use the view callable registered +- The fact that the request is decorated with a route-specific interface causes + the :term:`view lookup` machinery to always use the view callable registered using that interface by the route configuration to service requests that match the route pattern. -In this way, we supply a shortcut to the developer. Under the hood, the -:term:`resource location` and :term:`view lookup` subsystems provided by -:app:`Pyramid` are still being utilized, but in a way which does not require -a developer to understand either of them in detail. It also means that we -can allow a developer to combine :term:`URL dispatch` and :term:`traversal` -in various exceptional cases as documented in :ref:`hybrid_chapter`. - -To gain a better understanding of how routes and views are associated in a -real application, you can use the ``paster pviews`` command, as documented -in :ref:`displaying_matching_views`. +As we can see from the above description, technically, URL dispatch doesn't +actually map a URL pattern directly to a view callable. Instead URL dispatch +is a :term:`resource location` mechanism. A :app:`Pyramid` :term:`resource +location` subsystem (i.e., :term:`URL dispatch` or :term:`traversal`) finds a +:term:`resource` object that is the :term:`context` of a :term:`request`. Once +the :term:`context` is determined, a separate subsystem named :term:`view +lookup` is then responsible for finding and invoking a :term:`view callable` +based on information available in the context and the request. When URL +dispatch is used, the resource location and view lookup subsystems provided by +:app:`Pyramid` are still being utilized, but in a way which does not require a +developer to understand either of them in detail. + +If no route is matched using :term:`URL dispatch`, :app:`Pyramid` falls back to +:term:`traversal` to handle the :term:`request`. References ---------- A tutorial showing how :term:`URL dispatch` can be used to create a :app:`Pyramid` application exists in :ref:`bfg_sql_wiki_tutorial`. - |
