.. index:: single: url dispatch .. _urldispatch_chapter: URL Dispatch ============ The URL dispatch feature of :mod:`repoze.bfg` allows you to either augment or replace :term:`traversal` as a :term:`context finding` mechanism, allowing URL pattern matching to have the "first crack" at resolving a given URL to :term:`context` and :term:`view name`. Although it is a "context-finding" mechanism, ironically, using URL dispatch exclusively allows you to avoid thinking about your application in terms of "contexts" and "view names" entirely. Many applications don't need :mod:`repoze.bfg` features -- such as declarative security via an :term:`authorization policy` -- that benefit from having any visible separation between :term:`context finding` and :term:`view lookup`. To this end, URL dispatch provides a handy syntax that allows you to effectively map URLs *directly* to :term:`view` code in such a way that you needn't think about your application in terms "context finding" at all. This makes developing a :mod:`repoze.bfg` application seem more like developing an application in a system that is "context-free", such as :term:`Pylons` or :term:`Django`. Whether or not you care about "context", it often makes a lot of sense to use :term:`URL dispatch` instead of :term:`traversal` in an application that has no natural data hierarchy. For instance, if all the data in your application lives in a relational database, and that relational database has no self-referencing tables that form a natural hierarchy, URL dispatch is easier to use than traversal, and is often a more natural fit for creating an application that manipulates "flat" data. The presence of :ref:`route_directive` statements in a :term:`ZCML` file used by your application or the presence of calls to the :meth:`repoze.bfg.configuration.Configurator.add_route` method in imperative configuration within your application is a sign that you're using :term:`URL dispatch`. High-Level Operational Overview ------------------------------- If route configuration is present in an application, the :mod:`repoze.bfg` :term:`Router` checks every incoming request against an ordered set of URL matching patterns present in a *route map*. If any route patern matches the information in the :term:`request` provided to :mod:`repoze.bfg`, a route-specific :term:`context` and :term:`view name` will be generated. In this circumstance, :mod:`repoze.bfg` will shortcut :term:`traversal`, and will invoke :term:`view lookup` using the context and view name generated by URL dispatch. If the matched route names a :term:`view callable` in its configuration, that view callable will be invoked when view lookup is performed. However, if no route pattern matches the information in the :term:`request` provided to :mod:`repoze.bfg`, it will fail over to using :term:`traversal` to perform context finding and view lookup. Route Configuration ------------------- :term:`route configuration` is the act of adding a new :term:`route` to an application. A route has a *path*, representing a pattern meant to match against the ``PATH_INFO`` portion of a URL, and a *name*, which is used by developers within a :mod:`repoze.bfg` application to uniquely identify a particular route when generating a URL. It also optionally has a ``factory`` and a set of :term:`view` parameters. A route configuration may be added to the system via :term:`imperative configuration` or via :term:`ZCML`. Both are completely equivalent. .. index:: single: add_route Configuring a Route Imperatively via The ``add_route`` Configurator Method ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The :meth:`repoze.bfg.configuration.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 # repoze.bfg.configuration.Configurator class; "myview" is assumed # to be a "view callable" function from views import myview config.add_route(name='myroute', path='/prefix/:one/:two', view=myview) .. index:: single: ZCML directive; route Configuring a Route via ZCML ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Instead of using the imperative :meth:`repoze.bfg.configuration.Configurator.add_route` method to add a new route, you can alternately use :term:`ZCML`. For example, the following :term:`ZCML declaration` causes a route to be added to the application. .. code-block:: xml :linenos: .. note:: Values prefixed with a period (``.``) within the values of ZCML attributes such as the ``view`` attribute of a ``route`` mean "relative to the Python package directory in which this :term:`ZCML` file is stored". So if the above ``route`` declaration was made inside a ``configure.zcml`` file that lived in the ``hello`` package, you could replace the relative ``.views.myview`` with the absolute ``hello.views.myview`` Either the relative or absolute form is functionally equivalent. It's often useful to use the relative form, in case your package's name changes. It's also shorter to type. See :ref:`route_directive` for full ``route`` ZCML directive documentation. Route Configuration That Names a View Callable ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When a route configuration declaration names a ``view`` attribute, the attribute will be a value that references a :term:`view callable`. A view callable, as described in :ref:`views_chapter`, is developer-supplied code that "does stuff" as the result of a request. For more information about how to create view callables, see :ref:`views_chapter`. Here's an example route configuration that references a view callable: .. code-block:: xml :linenos: 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 path pattern matches during a request. The purpose of making it possible to specify a view callable within a route configuration is to prevent developers from needing to deeply understand the details of :term:`context finding` and :term:`view lookup`. When a route names a view callable, and a request enters the system which matches the path of the route, the 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 following section. You can skip it if you're uninterested. Route View Callable Registration and Lookup Details +++++++++++++++++++++++++++++++++++++++++++++++++++ When a ``view`` attribute is attached to a route configuration, :mod:`repoze.bfg` ensures that a :term:`view configuration` is registered that will always be found when the route path pattern is matched during a request. To do so: - A special route-specific :term:`interface` is created at startup time for each route configuration declaration. - When a route configuration declaration mentions a ``view`` attribute, a :term:`view configuration` is registered at startup time. This view configuration uses the route-specific interface as a :term:`request` type. - 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 using that interface by the route configuration to service requests that match the route path pattern. In this way, we supply a shortcut to the developer. Under the hood, :mod:`repoze.bfg` still consumes the :term:`context finding` and :term:`view lookup` subsystems provided by :mod:`repoze.bfg`, but in a way which does not require that a developer understand either of them if he doesn't want or need to. 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`. .. index:: pair: URL dispatch; path pattern syntax .. _route_path_pattern_syntax: Route Path Pattern Syntax ~~~~~~~~~~~~~~~~~~~~~~~~~ The syntax of the pattern matching language used by :mod:`repoze.bfg` URL dispatch in the *path* argument is straightforward; it is close to that of the :term:`Routes` system used by :term:`Pylons`. The *path* used in route configuration may start with a slash character. If the path does not start with a slash character, an implicit slash will be prepended to it at matching time. For example, the following paths are equivalent: .. code-block:: text :foo/bar/baz and: .. code-block:: text /:foo/bar/baz A path segment (an individual item between ``/`` characters in the path) may either be a literal string (e.g. ``foo``) *or* it may segment replacement marker (e.g. ``:foo``). A segment replacement marker is in the format ``:name``, where this means "accept any characters up to the next slash and use this as the ``name`` matchdict value." For example, the following pattern defines one literal segment ("foo") and two dynamic segments ("baz", and "bar"): .. code-block:: text foo/:baz/:bar The above pattern will match these URLs, generating the following matchdicts: .. code-block:: text foo/1/2 -> {'baz':u'1', 'bar':u'2'} foo/abc/def -> {'baz':u'abc', 'bar':u'def'} It will not match the following patterns however: .. code-block:: text foo/1/2/ -> No match (trailing slash) bar/abc/def -> First segment literal mismatch Note that values representing path segments matched with a ``:segment`` match will be url-unquoted and decoded from UTF-8 into Unicode within the matchdict. So for instance, the following pattern: .. code-block:: text foo/:bar When matching the following URL: .. code-block:: text foo/La%20Pe%C3%B1a The matchdict will look like so (the value is URL-decoded / UTF-8 decoded): .. code-block:: text {'bar':u'La Pe\xf1a'} 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 path pattern. Unlike segment replacement markers, it does not need to be preceded by a slash. 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':1, 'bar':2, 'fizzle':()} foo/abc/def/a/b/c -> {'baz':abc, 'bar':def, 'fizzle':('a', 'b', '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 UTF-8 into Unicode. For example, for the following pattern: .. code-block:: text foo/*fizzle When matching the following path: .. code-block:: text /foo/La%20Pe%C3%B1a/a/b/c Will generate the following matchdict: .. code-block:: text {'fizzle':(u'La Pe\xf1a', u'a', u'b', u'c')} .. index:: triple: ZCML directive; route; examples .. index:: pair: route; ordering Route Declaration Ordering ~~~~~~~~~~~~~~~~~~~~~~~~~~ Because route configuration declarations are evaluated in a specific order when a request enters the system, route configuration declaration ordering 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 graph. The order that route are evaluated when they are defined via :term:`ZCML` is the order in which they appear in the ZCML relative to each other. For routes added via the :mod:`repoze.bfg.configuration.Configurator.add_route` method, the order that routes are evaluated is the order in which they are added to the configuration imperatively. .. index:: pair: route; factory 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` of the view callable ultimately found via :term:`view lookup`. .. code-block:: xml In this way, each route can use a different factory, making it possible to supply a different :term:`context` object to the view related to each particular route. Supplying a different context for each route is useful when you're trying to use a :mod:`repoze.bfg` :term:`authorization policy` to provide declarative "context-sensitive" security checks; each context can maintain a separate :term:`ACL`, as 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`. Route Matching -------------- The main purpose of route configuration is to match (nor not match) the ``PATH_INFO`` present in the WSGI environment provided during a request against a URL path pattern. The way that :mod:`repoze.bfg` does this is very simple. When a request enters the system, for each route configuration declaration present in the system, :mod:`repoze.bfg` 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` and :term:`view name` are generated, and the context, the view name, and the resulting request are handed off to :term:`view lookup`. This process is otherwise known as :term:`context finding`. During view lookup, if any ``view`` argument was provided within the matched route configuration, the :term:`view callable` it points to is called. If no route matches after all route patterns are exhausted, :mod:`repoze.bfg` falls back to :term:`traversal` to do :term:`context finding` and :term:`view lookup`. .. index:: pair: URL dispatch; matchdict The Matchdict ~~~~~~~~~~~~~ When the URL path 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 ``path`` element. The keys in a matchdict will be strings. The values will be Unicode objects. .. note:: If no route URL pattern matches, no ``matchdict`` is attached to the request. Routing Examples ---------------- Let's check out some examples of how route configuration statements might be commonly declared, and what will happen if a they are matched by the information present in a request. The examples that follow assume that :term:`ZCML` will be used to perform route configuration, although you can use :term:`imperative configuration` equivalently if you like. .. _urldispatch_example1: Example 1 ~~~~~~~~~ The simplest route declaration which configures a route match to *directly* result in a particular view callable being invoked: .. code-block:: xml :linenos: When a route configuration with a ``view`` attribute is added to the system, and an incoming request matches the *path* 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 path. When the ``/site/:id`` route path 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'}``. The ``mypackage.views`` module referred to above might look like so: .. code-block:: python :linenos: from webob import Response def site_view(request): return Response(request.matchdict['id']) The view has access to the matchdict directly via the request, and can access variables within it that match keys present as a result of the route path pattern. See :ref:`views_chapter` for more information about views. Example 2 ~~~~~~~~~ Below is an example of a more complicated set of route statements you might add to your application: .. code-block:: xml :linenos: The above configuration will allow :mod:`repoze.bfg` to service URLs in these forms: .. code-block:: text /ideas/:idea /users/:user /tags/:tag - When a URL matches the pattern ``/ideas/:idea``, the view available at the dotted Python pathname ``mypackage.views.idea_view`` will be called. For the specific URL ``/ideas/1``, the ``matchdict`` generated and attached to the :term:`request` will consist of ``{'idea':'1'}``. - When a URL matches the pattern ``/users/:user``, the view 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 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'}``. 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. Example 3 ~~~~~~~~~ The context 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 ZCML directive 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 used by the view. An example of using a route with a factory: .. code-block:: xml :linenos: The above route will manufacture an ``Idea`` model as a :term:`context`, assuming that ``mypackage.models.Idea`` resolves to a class that accepts a request in its ``__init__``. For example: .. code-block:: python :linenos: class Idea(object): def __init__(self, request): pass In a more complicated application, this root factory might be a class representing a :term:`SQLAlchemy` model. Example 4 ~~~~~~~~~ It is possible to create a route declaration without a ``view`` attribute, but associate the route with a :term:`view callable` using a ``view`` declaration. .. code-block:: xml :linenos: This set of configuration parameters creates a configuration completely equivalent to this example provided in :ref:`urldispatch_example1`: .. code-block:: xml :linenos: In fact, the spelling which names a ``view`` attribute is just syntactic sugar for the more verbose spelling which contains separate view and route registrations. More uses for this style of associating views with routes are explored in :ref:`hybrid_chapter`. .. index:: pair: URL dispatch; matching the root URL Matching the Root URL --------------------- It's not entirely obvious how to use a route path pattern to match the root URL ("/"). To do so, give the empty string as a path in a ZCML ``route`` declaration: .. code-block:: xml :linenos: Or provide the literal string ``/`` as the path: .. code-block:: xml :linenos: .. index:: pair: URL dispatch; generating route URLs Generating Route URLs --------------------- Use the :func:`repoze.bfg.url.route_url` function to generate URLs based on route paths. For example, if you've configured a route in ZCML with the ``name`` "foo" and the ``path`` ":a/:b/:c", you might do this. .. ignore-next-block .. code-block:: python :linenos: from repoze.bfg.url import route_url url = route_url('foo', request, 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:`repoze.bfg.url.route_url` API documentation for more information. .. index:: pair: URL dispatch; slash-redirecting Redirecting to Slash-Appended Routes ------------------------------------ For behavior like Django's ``APPEND_SLASH=True``, use the :func:`repoze.bfg.view.append_slash_notfound_view` view as the :term:`Not Found view` in your application. When this view is the Not Found view (indicating that no view was found), and any routes have been defined in the configuration of your application, 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 path, do an HTTP redirect to the slash-appended ``PATH_INFO``. Let's use an example, because this behavior is a bit magical. If this your route configuration is looks like so, and the ``append_slash_notfound_view`` is configured in your application: .. code-block:: xml :linenos: If a request enters the application with the ``PATH_INFO`` value of ``/no_slash``, the first route will match. 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. However, 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 notfound view. An HTTP redirect to ``/has_slash/`` will be returned to the user's browser. Note that this will *lose* ``POST`` data information (turning it into a GET), so you shouldn't rely on this to redirect POST requests. To configure the slash-appending not found view in your application, change the application's ``configure.zcml``, adding the following stanza: .. code-block:: xml :linenos: 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. .. note:: This feature is new as of :mod:`repoze.bfg` 1.1. Cleaning Up After a Request --------------------------- Often it's required that some cleanup be performed at the end of a request when a database connection is involved. When :term:`traversal` is used, this cleanup is often done as a side effect of the traversal :term:`root factory`. Often the root factory will insert an object into the WSGI environment that performs some cleanup when its ``__del__`` method is called. When URL dispatch is used, however, no special root factory is required, so sometimes that option is not open to you. Instead of putting this cleanup logic in the root factory, however, you can cause a subscriber to be fired when a new request is detected; the subscriber can do this work. For example, let's say you have a ``mypackage`` :mod:`repoze.bfg` 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.run`` module: .. ignore-next-block .. code-block:: python :linenos: from mypackage.sql import DBSession class Cleanup: def __init__(self, cleaner): self.cleaner = cleaner def __del__(self): self.cleaner() def handle_teardown(event): environ = event.request.environ environ['mypackage.sqlcleaner'] = Cleanup(DBSession.remove) Then in the ``configure.zcml`` of your package, inject the following: .. code-block:: xml This will cause the DBSession to be removed whenever the WSGI environment is destroyed (usually at the end of every request). Alternate mechanisms for performing this sort of cleanup exist; an alternate mechanism which uses cleanup services offered by the ``repoze.tm2`` package is used in the SQLAlchemy-related ``paster`` templates generated by :mod:`repoze.bfg` and within :ref:`sql_tm2_cleanup` within the :ref:`bfg_sql_wiki_tutorial`. .. index:: pair: URL dispatch; security .. _using_security_with_urldispatch: Using :mod:`repoze.bfg` Security With URL Dispatch -------------------------------------------------- :mod:`repoze.bfg` provides its own security framework which consults a :term:`authorization policy` before allowing any application code to be called. This framework operates in terms of ACLs (Access Control Lists, see :ref:`security_chapter` for more information about the :mod:`repoze.bfg` authorization subsystem). A common thing to want to do is to attach an ``__acl__`` to the context 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``, :mod:`repoze.bfg` will generate an ``Article`` :term:`context` with an ACL on it that allows the ``editor`` principal the ``view`` permission. Obviously you can do more generic things that inspect the routes 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 :mod:`repoze.bfg` security and ACLs. Using Context Within a View Callable ------------------------------------ When using :term:`url dispatch` exclusively in an application (as opposed to using both url dispatch *and* :term:`traversal` in the same application), the :term:`context` of the view isn't always terribly interesting, particularly if you never use a ``factory`` attribute on your route definitions. However, if you do use a ``factory`` attribute on your route definitions, you may be very interested in the :term:`context` of the view. You can access the ``context`` of a view within the body of a view callable via ``request.context`` :mod:`repoze.bfg` also supports view callables defined with two arguments: ``context`` and ``request``. For example, the below function can be used as a view callable. .. code-block:: python :linenos: from webob import Response def hello_view(context, request): return Response('Hello!') The ``context`` passed to this view will be an instance returned by the default root factory or an instance returned by the ``factory`` argument to your route definition. See :ref:`request_and_context_view_definitions` for more information. References ---------- A tutorial showing how :term:`URL dispatch` can be used to create a :mod:`repoze.bfg` application exists in :ref:`bfg_sql_wiki_tutorial`.