The URL dispatch feature of repoze.bfg allows you to either augment or replace traversal as a context finding mechanism, allowing URL pattern matching to have the “first crack” at resolving a given URL to context and 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 repoze.bfg features – such as declarative security via an authorization policy – that benefit from having any visible separation between context finding and view lookup. To this end, URL dispatch provides a handy syntax that allows you to effectively map URLs directly to view code in such a way that you needn’t think about your application in terms of “context finding” at all. This makes developing a repoze.bfg application seem more like developing an application in a system that is “context-free”, such as Pylons or Django.
Whether or not you care about “context”, it often makes a lot of sense to use URL dispatch instead of 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 route statements in a ZCML file used by your application or the presence of calls to the repoze.bfg.configuration.Configurator.add_route() method in imperative configuration within your application is a sign that you’re using URL dispatch.
If route configuration is present in an application, the repoze.bfg 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 request provided to repoze.bfg, a route-specific context and view name will be generated. In this circumstance, repoze.bfg will shortcut traversal, and will invoke view lookup using the context and view name generated by URL dispatch. If the matched route names a 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 request provided to repoze.bfg, it will fail over to using traversal to perform context finding and view lookup.
Route configuration is the act of adding a new 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 repoze.bfg application to uniquely identify a particular route when generating a URL. It also optionally has a factory, a set of route predicate parameters, and a set of view parameters.
A route configuration may be added to the system via imperative configuration or via ZCML. Both are completely equivalent.
The repoze.bfg.configuration.Configurator.add_route() method adds a single route configuration to the application registry. Here’s an example:
# "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)
Instead of using the imperative repoze.bfg.configuration.Configurator.add_route() method to add a new route, you can alternately use ZCML. For example, the following ZCML declaration causes a route to be added to the application.
1 2 3 4 5 | <route
name="myroute"
path="/prefix/:one/:two"
view=".views.myview"
/>
|
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 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 route for full route ZCML directive documentation.
When a route configuration declaration names a view attribute, the value of the attribute will reference a view callable. A view callable, as described in Views, is developer-supplied code that “does stuff” as the result of a request. For more information about how to create view callables, see Views.
Here’s an example route configuration that references a view callable:
1 2 3 4 5 | <route
name="myroute"
path="/prefix/:one/:two"
view="mypackage.views.myview"
/>
|
When a route configuration names a view attribute, the 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 context finding and 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.
When a view attribute is attached to a route configuration, repoze.bfg ensures that a view configuration is registered that will always be found when the route path pattern is matched during a request. To do so:
In this way, we supply a shortcut to the developer. Under the hood, repoze.bfg still consumes the context finding and view lookup subsystems provided by 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 URL dispatch and traversal in various exceptional cases as documented in Combining Traversal and URL Dispatch.
The syntax of the pattern matching language used by repoze.bfg URL dispatch in the path argument is straightforward; it is close to that of the Routes system used by 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:
:foo/bar/baz
and:
/: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 be a 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”):
foo/:baz/:bar
The above pattern will match these URLs, generating the following matchdicts:
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:
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:
foo/:bar
When matching the following URL:
foo/La%20Pe%C3%B1a
The matchdict will look like so (the value is URL-decoded / UTF-8 decoded):
{'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:
foo/:baz/:bar*fizzle
The above pattern will match these URLs, generating the following matchdicts:
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:
foo/*fizzle
When matching the following path:
/foo/La%20Pe%C3%B1a/a/b/c
Will generate the following matchdict:
{'fizzle':(u'La Pe\xf1a', u'a', u'b', u'c')}
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 traversal, which depends on emergent behavior which happens as a result of traversing a graph.
The order that routes are evaluated when they are defined via ZCML is the order in which they appear in the ZCML relative to each other. For routes added via the 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.
For example, route configuration statements with the following patterns might 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.
A “route” configuration declaration can mention a “factory”. When that route matches a request, and a factory is attached to a route, the root factory passed at startup time to the Configurator is ignored; instead the factory associated with the route is used to generate a root object. This object will usually be used as the context of the view callable ultimately found via view lookup.
<route
path="/abc"
name="abc"
view=".views.theview"
factory=".models.root_factory"
/>
In this way, each route can use a different factory, making it possible to supply a different 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 repoze.bfg authorization policy to provide declarative “context-sensitive” security checks; each context can maintain a separate ACL, as in Using repoze.bfg Security With URL Dispatch. It is also useful when you wish to combine URL dispatch with traversal as documented within Combining Traversal and URL Dispatch.
Route configuration statements may specify a large number of arguments.
Many of these arguments are route predicate arguments. A route predicate argument specifies that some aspect of the request must be true for the associated route to be considered a match during the route matching process.
Other arguments are view configuration related arguments. These only have an effect when the route configuration names a view.
Other arguments are name and factory. These arguments represent neither predicates nor view configuration information.
Non-Predicate Arguments
Predicate Arguments
View-Related Arguments
A reference to a class or an interface that the context of the view should match for the view named by the route to be used. This argument is only useful if the view attribute is used. If this attribute is not specified, the default (None) will be used.
If the view argument is not provided, this argument has no effect.
This attribute can also be spelled as for_ or view_for.
The permission name required to invoke the view associated with this route. e.g. edit. (see Using repoze.bfg Security With URL Dispatch for more information about permissions).
If the view attribute is not provided, this argument has no effect.
This argument can also be spelled as permission.
This is either a single string term (e.g. json) or a string implying a path or resource specification (e.g. templates/views.pt). If the renderer value is a single term (does not contain a dot .), the specified term will be used to look up a renderer implementation, and that renderer implementation will be used to construct a response from the view return value. If the renderer term contains a dot (.), the specified term will be treated as a path, and the filename extension of the last element in the path will be used to look up the renderer implementation, which will be passed the full path. The renderer implementation will be used to construct a response from the view return value. See Writing View Callables Which Use a Renderer for more information.
If the view argument is not provided, this argument has no effect.
This argument can also be spelled as renderer.
The view machinery defaults to using the __call__ method of the view callable (or the function itself, if the view callable is a function) to obtain a response dictionary. The attr value allows you to vary the method attribute used to obtain the response. For example, if your view was a class, and the class has a method named index and you wanted to use this method instead of the class’ __call__ method to return the response, you’d say attr="index" in the view configuration for the view. This is most useful when the view definition is a class.
If the view argument is not provided, this argument has no effect.
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.
The way that repoze.bfg does this is very simple. When a request enters the system, for each route configuration declaration present in the system, repoze.bfg checks the PATH_INFO against the pattern declared.
If any route matches, the route matching process stops. The request is decorated with a special interface which describes it as a “route request”, the context and view name are generated, and the context, the view name, and the resulting request are handed off to view lookup. This process is otherwise known as context finding. During view lookup, if any view argument was provided within the matched route configuration, the view callable it points to is called.
When a route configuration is declared, it may contain 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 route predicate arguments provided to a route configuration returns False, that route is skipped and route matching continues through the ordered set of routes.
If no route matches after all route patterns are exhausted, repoze.bfg falls back to traversal to do context finding and view lookup.
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 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.
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. The examples that follow assume that ZCML will be used to perform route configuration, although you can use imperative configuration equivalently if you like.
The simplest route declaration which configures a route match to directly result in a particular view callable being invoked:
1 2 3 4 5 | <route
name="idea"
path="site/:id"
view="mypackage.views.site_view"
/>
|
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 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:
1 2 3 4 | 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 Views for more information about views.
Below is an example of a more complicated set of route statements you might add to your application:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <route
name="idea"
path="ideas/:idea"
view="mypackage.views.idea_view"
/>
<route
name="user"
path="users/:user"
view="mypackage.views.user_view"
/>
<route
name="tag"
path="tags/:tag"
view="mypackage.views.tag_view"
/>
|
The above configuration will allow repoze.bfg to service URLs in these forms:
/ideas/:idea
/users/:user
/tags/:tag
In this example we’ve again associated each of our routes with a 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.
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 root factory configured at startup time (the root_factory argument to the 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 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:
1 2 3 4 5 6 | <route
name="idea"
path="ideas/:idea"
view=".views.idea_view"
factory=".models.Idea"
/>
|
The above route will manufacture an Idea model as a context, assuming that mypackage.models.Idea resolves to a class that accepts a request in its __init__. For example:
1 2 3 | class Idea(object):
def __init__(self, request):
pass
|
In a more complicated application, this root factory might be a class representing a SQLAlchemy model.
It is possible to create a route declaration without a view attribute, but associate the route with a view callable using a view declaration.
1 2 3 4 5 6 7 8 9 | <route
name="idea"
path="site/:id"
/>
<view
view="mypackage.views.site_view"
route_name="idea"
/>
|
This set of configuration parameters creates a configuration completely equivalent to this example provided in Example 1:
1 2 3 4 5 | <route
name="idea"
path="site/:id"
view="mypackage.views.site_view"
/>
|
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 Combining Traversal and URL Dispatch.
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:
1 2 3 4 5 | <route
path=""
name="root"
view=".views.root_view"
/>
|
Or provide the literal string / as the path:
1 2 3 4 5 | <route
path="/"
name="root"
view=".views.root_view"
/>
|
Use the 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.
1 2 | 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 repoze.bfg.url.route_url() API documentation for more information.
For behavior like Django’s APPEND_SLASH=True, use the repoze.bfg.view.append_slash_notfound_view() view as the 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, it does an HTTP redirect to the slash-appended PATH_INFO.
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:
1 2 3 4 5 6 7 8 9 | <route
view=".views.no_slash"
path="no_slash"
/>
<route
view=".views.has_slash"
path="has_slash/"
/>
|
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:
1 2 3 | <notfound
view="repoze.bfg.views.append_slash_notfound_view"
/>
|
See repoze.bfg.view and Changing the Not Found 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 repoze.bfg 1.1.
Often it’s required that some cleanup be performed at the end of a request when a database connection is involved. When traversal is used, this cleanup is often done as a side effect of the traversal 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 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:
1 2 3 4 5 6 7 8 9 10 11 | 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:
<subscriber for="repoze.bfg.interfaces.INewRequest"
handler="mypackage.run.handle_teardown"/>
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 repoze.bfg and within App Startup with run.py within the SQLAlchemy + URL Dispatch Wiki Tutorial.
repoze.bfg provides its own security framework which consults an 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 context object. 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:
1 2 3 4 5 6 | 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, repoze.bfg will generate an Article context 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.
Note
See Security for more information about repoze.bfg security and ACLs.
A tutorial showing how URL dispatch can be used to create a repoze.bfg application exists in SQLAlchemy + URL Dispatch Wiki Tutorial.