URL Dispatch¶
URL dispatch provides a simple way to map URLs to 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 view callable is invoked.
URL dispatch is one of two ways to perform resource location in Pyramid; the other way is using traversal. If no route is matched using URL dispatch, Pyramid falls back to traversal to handle the request.
It is the responsibility of the resource location subsystem (i.e., URL dispatch or traversal) to find the resource object that is the context of the request. Once the context is determined, view lookup is then responsible for finding and invoking a view callable. A view callable is a specific bit of code, defined in your application, that receives the request and returns a response object.
Where appropriate, we will describe how view lookup interacts with resource location. The View Configuration chapter describes the details of view lookup.
High-Level Operational Overview¶
If route configuration is present in an application, the Pyramid 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, Pyramid will invoke view lookup using a context resource generated by the route match.
However, if no route pattern matches the information in the request provided to Pyramid, it will fail over to using traversal to perform resource location and view lookup.
Technically, URL dispatch is a resource location mechanism (it finds a context object). But ironically, using URL dispatch (instead of traversal) allows you to avoid thinking about your application in terms of “resources” entirely, because it allows you to directly map a view callable to a route.
Route Configuration¶
Route configuration is the act of adding a new route to an
application. A route has a pattern, representing 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
),
and a route name, which is used by developers within a Pyramid
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.
Configuring a Route via The add_route
Configurator Method¶
The pyramid.config.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
# pyramid.config.Configurator class; "myview" is assumed
# to be a "view callable" function
from views import myview
config.add_route('myroute', '/prefix/{one}/{two}', view=myview)
Changed in version 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.
Route Configuration That Names a View Callable¶
When a route configuration declaration names a view
attribute, the value
of the attribute will reference a view callable. This view callable
will be invoked when the route matches. A view callable, as described in
Views, is developer-supplied code that “does stuff” as the
result of a request.
Here’s an example route configuration that references a view callable:
1 2 3 4 5 | # "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)
|
You can also pass a dotted Python name as the view
argument
rather than an actual callable:
1 2 3 4 5 | # "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')
|
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 pattern matches during a request.
Route Pattern Syntax¶
The syntax of the pattern matching language used by Pyramid URL dispatch in the pattern argument is straightforward; it is close to that of the Routes system used by 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 equivalent:
{foo}/bar/baz
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.
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
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
):
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
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:
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
biz
, not biz.html
).
To capture both segments, two replacement markers can be used:
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}
.
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.
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.
Segments must contain at least one character in order to match a segment
replacement marker. For example, for the URL /abc/
:
/abc/{foo}
will not match./{foo}/
will match.
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:
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 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':u'1', 'bar':u'2', 'fizzle':()}
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
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')}
By default, the *stararg
will parse the remainder sections into a tuple
split by segment. Changing the regular expression used to match a marker can
also capture the remainder of the URL, for example:
foo/{baz}/{bar}{fizzle:.*}
The above pattern will match these URLs, generating the following matchdicts:
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')}
This occurs because the default regular expression for a marker is [^/]+
which will match everything up to the first /
, while {fizzle:.*}
will
result in a regular expression match of .*
capturing the remainder into a
single value.
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 traversal, which depends on emergent behavior which happens as a result of traversing a resource tree.
For routes added via the 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.
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 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 resource of the view callable ultimately found via view lookup.
1 2 | config.add_route('abc', '/abc', view='myproject.views.theview',
factory='myproject.resources.root_factory')
|
The factory can either be a Python object or a 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 context resource object to the view related to each particular route.
Supplying a different resource factory each route is useful when you’re trying to use a Pyramid authorization policy to provide declarative, “context sensitive” security checks; each resource can maintain a separate ACL, as documented in Using Pyramid 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 Arguments¶
Route configuration add_route
statements may specify a large number of
arguments. They are documented as part of the API documentation at
pyramid.config.Configurator.add_route()
.
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. 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
.
Other arguments are name
and factory
. These arguments represent
neither predicates nor view configuration information.
Custom Route Predicates¶
Each of the predicate callables fed to the custom_predicates
argument of
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
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
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:
1 2 3 4 5 6 7 8 9 10 | 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('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
add_route()
.
A custom route predicate may also modify the match
dictionary. For
instance, a predicate might do some type conversion of values:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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('num', '/{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:
1 2 3 4 5 6 7 8 9 10 11 12 | 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('num', '/{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:
1 2 3 4 5 6 7 8 | 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 pyramid.interfaces.IRoute
for more API documentation about
route objects.
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.
The way that Pyramid does this is very simple. When a request enters
the system, for each route configuration declaration present in the system,
Pyramid 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 resource is generated, and the context and the
resulting request are handed off to view lookup. 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, Pyramid falls back to traversal to do resource location and view lookup.
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 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
If no route URL pattern matches, the matchdict
object attached to the
request will be None
.
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 request object. Thus, request.matched_route
will be an object implementing the 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
If no route URL pattern matches, the matched_route
object attached to
the request will be None
.
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.
Example 1¶
The simplest route declaration which configures a route match to directly result in a particular view callable being invoked:
1 | config.add_route('idea', '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 pattern 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 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'}
.
The mypackage.views
module referred to above might look like so:
1 2 3 4 | from pyramid.response 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 pattern.
See Views, and View Configuration 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:
1 2 3 | config.add_route('idea', 'ideas/{idea}', view='mypackage.views.idea_view')
config.add_route('user', 'users/{user}', view='mypackage.views.user_view')
config.add_route('tag', 'tags/{tags}', view='mypackage.views.tag_view')
|
The above configuration will allow Pyramid to service URLs in these forms:
/ideas/{idea}
/users/{user}
/tags/{tag}
- When a URL matches the pattern
/ideas/{idea}
, the view callable available at the dotted Python pathnamemypackage.views.idea_view
will be called. For the specific URL/ideas/1
, thematchdict
generated and attached to the request will consist of{'idea':'1'}
. - When a URL matches the pattern
/users/{user}
, the view callable available at the dotted Python pathnamemypackage.views.user_view
will be called. For the specific URL/users/1
, thematchdict
generated and attached to the request will consist of{'user':'1'}
. - When a URL matches the pattern
/tags/{tag}
, the view callable available at the dotted Python pathnamemypackage.views.tag_view
will be called. For the specific URL/tags/1
, thematchdict
generated and attached to the request will consist of{'tag':'1'}
.
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.
Example 3¶
The 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 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
add_route()
method 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 resource used by the
view.
An example of using a route with a factory:
1 2 3 | config.add_route('idea', 'ideas/{idea}',
view='myproject.views.idea_view',
factory='myproject.resources.Idea')
|
The above route will manufacture an Idea
resource as a context,
assuming that mypackage.resources.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.
Example 4¶
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 | config.add_route('idea', 'site/{id}')
config.add_view(route_name='idea', view='mypackage.views.site_view')
|
This set of configuration parameters creates a configuration completely equivalent to this example provided in Example 1:
1 | config.add_route('idea', '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.
Matching the Root URL¶
It’s not entirely obvious how to use a route pattern to match the root URL
(“/”). To do so, give the empty string as a pattern in a call to
add_route()
:
1 | config.add_route('root', '', view='mypackage.views.root_view')
|
Or provide the literal string /
as the pattern:
1 | config.add_route('root', '/', view='mypackage.views.root_view')
|
Generating Route URLs¶
Use the pyramid.url.route_url()
function 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.
1 2 | from pyramid.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 route_url()
API documentation for more information.
Redirecting to Slash-Appended Routes¶
For behavior like Django’s APPEND_SLASH=True
, use the
append_slash_notfound_view()
view as the Not Found
view in your application. Defining this view as the 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
.
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 | config.add_route('noslash', 'no_slash',
view='myproject.views.no_slash')
config.add_route('hasslash', 'has_slash/',
view='myproject.views.has_slash')
|
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.
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.
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.
To configure the slash-appending not found view in your application, change the application’s startup configuration, adding the following stanza:
1 2 | config.add_view(context='pyramid.exceptions.NotFound',
view='pyramid.view.append_slash_notfound_view')
|
See pyramid.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.
Custom Not Found View With Slash Appended Routes¶
There can only be one Not Found view in any Pyramid
application. Even if you use append_slash_notfound_view()
as the Not Found view, 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.
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
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
AppendSlashNotFoundViewFactory
class as the Not Found
view, supplying a view callable to be used as the custom notfound
view as the first argument to its constructor. For instance:
1 2 3 4 5 6 7 8 | from pyramid.exceptions import NotFound
from pyramid.view import AppendSlashNotFoundViewFactory
def notfound_view(context, request):
return HTTPNotFound('It aint there, stop trying!')
custom_append_slash = AppendSlashNotFoundViewFactory(notfound_view)
config.add_view(custom_append_slash, context=NotFound)
|
The notfound_view
supplied must adhere to the two-argument view callable
calling convention of (context, request)
(context
will be the
exception object).
Cleaning Up After a Request¶
Sometimes it’s required that some cleanup be performed at the end of a request when a database connection is involved.
For example, let’s say you have a mypackage
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:
1 2 3 4 5 6 7 8 9 10 11 | from mypackage.models import DBSession
from pyramid.events import subscriber
from pyramid.events import NewRequest
def cleanup_callback(request):
DBSession.remove()
@subscriber(NewRequest)
def add_cleanup_callback(event):
event.request.add_finished_callback(cleanup_callback)
|
Registering the cleanup_callback
finished callback at the start of a
request (by causing the add_cleanup_callback
to receive a
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 pyramid.events.subscriber
decorator to “work”, the pyramid.config.Configurator.scan()
method must
be called against your mypackage
package during application
initialization.
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 Pyramid paster template, 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.
Using Pyramid Security With URL Dispatch¶
Pyramid 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 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:
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
, Pyramid will generate an Article
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.
Note
See Security for more information about Pyramid security and ACLs.
Debugging Route Matching¶
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
Pyramid application will be printed to the stderr
of the console
which you started the application from. For example:
1 2 3 4 5 6 7 8 9 10 11 | [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/', ....
|
See Environment Variables and .ini File Settings for more information about how, and where to set these values.
Displaying All Application Routes¶
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 The 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.
For example:
1 2 3 4 5 6 7 8 | [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.
Route View Callable Registration and Lookup Details¶
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 resource location and view lookup. When a route
names a view callable as a view
argument, and a request enters the system
which matches the pattern 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 this section. You can skip it if you’re uninterested.
When a view
attribute is attached to a route configuration,
Pyramid ensures that a view configuration is registered that
will always be found when the route pattern is matched during a request. To
do so:
- A special route-specific interface is created at startup time for each route configuration declaration.
- When a route configuration declaration mentions a
view
attribute, a view configuration is registered at startup time. This view configuration uses the route-specific interface as a request type. - At runtime, when a request causes any route to match, the 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 pattern.
In this way, we supply a shortcut to the developer. Under the hood, the resource location and view lookup subsystems provided by 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 URL dispatch and traversal in various exceptional cases as documented in Combining Traversal and URL Dispatch.
References¶
A tutorial showing how URL dispatch can be used to create a Pyramid application exists in SQLAlchemy + URL Dispatch Wiki Tutorial.