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. A view callable is a specific bit of code, defined in your application, that receives the request and returns a response object.
High-Level Operational Overview¶
If any 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 the view lookup process to find a matching view.
If no route pattern in the route map matches the information in the request provided in your application, Pyramid will fail over to using traversal to perform resource location and view lookup.
Route Configuration¶
Route configuration is the act of adding a new 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
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
also optionally has a factory
and a set of route predicate
attributes.
Configuring a Route to Match a View¶
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}')
config.add_view(myview, route_name='myroute')
When a view callable added to the configuration by way of
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.
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
scan to associate view callables with routes. For example, if this is
a portion of your project's __init__.py
:
config.add_route('myroute', '/prefix/{one}/{two}')
config.scan('mypackage')
Note that we don't call add_view()
in this
setup code. However, the above scan execution
config.scan('mypackage')
will pick up each configuration
decoration, including any objects decorated with the
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:
from pyramid.view import view_config
from pyramid.response import Response
@view_config(route_name='myroute')
def myview(request):
return Response('OK')
The above combination of add_route
and scan
is completely equivalent to
using the previous combination of add_route
and add_view
.
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
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 External routes 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
matchdict value."
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.
バージョン 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
):
foo/{baz}/{bar}
The above pattern will match these URLs, generating the following matchdicts:
foo/1/2 -> {'baz': '1', 'bar': '2'}
foo/abc/def -> {'baz': 'abc', 'bar': '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': '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:
http://example.com/foo/La%20Pe%C3%B1a
The matchdict will look like so (the value is URL-decoded / UTF-8 decoded):
{'bar': '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:
/Foo%20Bar/{baz}
You'll want to use something like this:
/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:
/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 plus the "real" character in the Unicode pattern in the source, like so:
/La Peña/{x}
Or you can ignore source file encoding and use equivalent Unicode escape characters in the pattern.
/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:
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': ('La Pe\xf1a', 'a', 'b', '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': '1', 'bar': '2', 'fizzle': ''}
foo/abc/def/a/b/c -> {'baz': 'abc', 'bar': 'def', 'fizzle': '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 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 Pyramid provides, named traversal, which does not depend on pattern ordering).
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 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 name
and factory
. These arguments represent
neither predicates nor view configuration information.
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. PATH_INFO
represents the path portion of the
URL that was requested.
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 request's PATH_INFO
against the pattern
declared. This checking happens in the order that the routes were declared
via pyramid.config.Configurator.add_route()
.
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 during a
check. If any predicate in the set of 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 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
pviews: Displaying Matching Views for a Given URL.
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.
注釈
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.
注釈
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:
1config.add_route('idea', 'site/{id}')
2config.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
view callable named as the view
attribute of the route
configuration will be invoked.
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'}
.
The mypackage.views
module referred to above might look like so:
1from pyramid.view import view_config
2from pyramid.response import Response
3
4@view_config(route_name='idea')
5def site_view(request):
6 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:
1config.add_route('idea', 'ideas/{idea}')
2config.add_route('user', 'users/{user}')
3config.add_route('tag', 'tags/{tag}')
4config.scan()
Here is an example of a corresponding mypackage.views
module:
1from pyramid.view import view_config
2from pyramid.response import Response
3
4@view_config(route_name='idea')
5def idea_view(request):
6 return Response(request.matchdict['idea'])
7
8@view_config(route_name='user')
9def user_view(request):
10 user = request.matchdict['user']
11 return Response('The user is {}.'.format(user))
12
13@view_config(route_name='tag')
14def tag_view(request):
15 tag = request.matchdict['tag']
16 return Response('The tag is {}.'.format(tag))
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:
1config.add_route('idea', 'ideas/{idea}', factory='myproject.resources.Idea')
2config.scan()
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:
1class Idea(object):
2 def __init__(self, request):
3 pass
In a more complicated application, this root factory might be a class
representing a SQLAlchemy model. The view mypackage.views.idea_view
might look like this:
1@view_config(route_name='idea')
2def idea_view(request):
3 idea = request.context
4 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 Route Factories for more details about how to use route factories.
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()
:
1config.add_route('root', '')
Or provide the literal string /
as the pattern:
1config.add_route('root', '/')
Generating Route URLs¶
Use the 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.
1url = 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
).
To generate only the path portion of a URL from a route, use the
pyramid.request.Request.route_path()
API instead of
route_url()
.
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:
config.add_route('la', '/La Peña/{city}')
And you later generate a URL using route_path
or route_url
like so:
url = request.route_path('la', city='Québec')
You will wind up with the path encoded to UTF-8 and URL-quoted like so:
/La%20Pe%C3%B1a/Qu%C3%A9bec
If you have a *stararg
remainder dynamic part of your route pattern:
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:
url = request.route_path('abc', foo='Québec/biz')
The value you pass will be URL-quoted except for embedded slashes in the result:
/a/b/c/Qu%C3%A9bec/biz
You can get a similar result by passing a tuple composed of path elements:
url = request.route_path('abc', foo=('Québec', 'biz'))
Each value in the tuple will be URL-quoted and joined by slashes in this case:
/a/b/c/Qu%C3%A9bec/biz
Static Routes¶
Routes may be added with a static
keyword argument. For example:
1config = Configurator()
2config.add_route('page', '/page/{action}', static=True)
Routes added with a True
static
keyword argument will never be
considered for matching at request time. Static routes are useful for URL
generation purposes only. As a result, it is usually nonsensical to provide
other non-name
and non-pattern
arguments to
add_route()
when static
is passed as
True
, as none of the other arguments will ever be employed. A single
exception to this rule is use of the pregenerator
argument, which is not
ignored when static
is True
.
External routes are implicitly static.
バージョン 1.1 で追加: the static
argument to add_route()
.
External Routes¶
バージョン 1.5 で追加.
Route patterns that are valid URLs, are treated as external routes. Like static routes they are useful for URL generation purposes only and are never considered for matching at request time.
1>>> config = Configurator()
2>>> config.add_route('youtube', 'https://youtube.com/watch/{video_id}')
3...
4>>> request.route_url('youtube', video_id='oHg5SJYRHA0')
5>>> "https://youtube.com/watch/oHg5SJYRHA0"
Most pattern replacements and calls to
pyramid.request.Request.route_url()
will work as expected. However, calls
to pyramid.request.Request.route_path()
against external patterns will
raise an exception, and passing _app_url
to
route_url()
to generate a URL against a route
that has an external pattern will also raise an exception.
Redirecting to Slash-Appended Routes¶
For behavior like Django's APPEND_SLASH=True
, use the append_slash
argument to pyramid.config.Configurator.add_notfound_view()
or the
equivalent append_slash
argument to the
pyramid.view.notfound_view_config
decorator.
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
pyramid.interfaces.IResponse
which will then be used in place of the
default class (pyramid.httpexceptions.HTTPFound
).
Let's use an example. If the following routes are configured in your application:
1from pyramid.httpexceptions import HTTPNotFound
2
3def notfound(request):
4 return HTTPNotFound()
5
6def no_slash(request):
7 return Response('No slash')
8
9def has_slash(request):
10 return Response('Has slash')
11
12def main(g, **settings):
13 config = Configurator()
14 config.add_route('noslash', 'no_slash')
15 config.add_route('hasslash', 'has_slash/')
16 config.add_view(no_slash, route_name='noslash')
17 config.add_view(has_slash, route_name='hasslash')
18 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" 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. As a result, the
notfound
view will never actually be called.
The following application uses the pyramid.view.notfound_view_config
and pyramid.view.view_config
decorators and a scan to do
exactly the same job:
1from pyramid.httpexceptions import HTTPNotFound
2from pyramid.view import notfound_view_config, view_config
3
4@notfound_view_config(append_slash=True)
5def notfound(request):
6 return HTTPNotFound()
7
8@view_config(route_name='noslash')
9def no_slash(request):
10 return Response('No slash')
11
12@view_config(route_name='hasslash')
13def has_slash(request):
14 return Response('Has slash')
15
16def main(g, **settings):
17 config = Configurator()
18 config.add_route('noslash', 'no_slash')
19 config.add_route('hasslash', 'has_slash/')
20 config.scan()
警告
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.
See pyramid.view and Changing the Not Found View for a more general description of how to configure a view and/or a Not Found View.
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 Pyramid application will be printed to the stderr
of the console
which you started the application from. For example:
1PYRAMID_DEBUG_ROUTEMATCH=true $VENV/bin/pserve development.ini
2Starting server in PID 13586.
3serving on 0.0.0.0:6543 view at http://127.0.0.1:6543
42010-12-16 14:45:19,956 no route matched for url \
5 http://localhost:6543/wontmatch
62010-12-16 14:45:20,010 no route matched for url \
7 http://localhost:6543/favicon.ico
82010-12-16 14:41:52,084 route matched for url \
9 http://localhost:6543/static/logo.png; \
10 route_name: 'static/', ....
See Environment Variables and .ini File Settings 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
proutes: Displaying All Application Routes.
Using a Route Prefix to Compose Applications¶
バージョン 1.2 で追加.
The pyramid.config.Configurator.include()
method allows configuration
statements to be included from separate files. See
Rules for Building an Extensible Application for information about this method. Using
pyramid.config.Configurator.include()
allows you to build your
application from small and potentially reusable components.
The 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
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:
1from pyramid.config import Configurator
2
3def users_include(config):
4 config.add_route('show_users', '/show')
5
6def main(global_config, **settings):
7 config = Configurator()
8 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
pyramid.request.Request.route_url()
function is called with the route
name show_users
, it will generate a URL with that same path.
To create a route that matches requests to the route_prefix
without a trailing slash, pass inherit_slash=True
to the call to add_route
.
1from pyramid.config import Configurator
2
3def users_include(config):
4 config.add_route('show_users', '', inherit_slash=True)
5
6def main(global_config, **settings):
7 config = Configurator()
8 config.include(users_include, route_prefix='/users')
The above configuration will match /users
instead of /users/
.
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:
1from pyramid.config import Configurator
2
3def timing_include(config):
4 config.add_route('show_times', '/times')
5
6def users_include(config):
7 config.add_route('show_users', '/show')
8 config.include(timing_include, route_prefix='/timing')
9
10def main(global_config, **settings):
11 config = Configurator()
12 config.include(users_include, route_prefix='/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
.
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
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:
1from pyramid.config import Configurator
2
3def timing_include(config):
4 config.add_route('timing.show_times', '/times')
5
6def users_include(config):
7 config.add_route('users.show_users', '/show')
8 config.include(timing_include, route_prefix='/timing')
9
10def main(global_config, **settings):
11 config = Configurator()
12 config.include(users_include, route_prefix='/users')
A convenience context manager exists to set the route prefix for any
pyramid.config.Configurator.add_route()
or
pyramid.config.Configurator.include()
calls within the context.
1from pyramid.config import Configurator
2
3def timing_include(config):
4 config.add_route('timing.show_times', '/times')
5
6def main(global_config, **settings)
7 config = Configurator()
8 with config.route_prefix_context('/timing'):
9 config.include(timing_include)
10 config.add_route('timing.average', '/average')
Custom Route Predicates¶
A predicate is a callable that accepts two arguments: a dictionary
conventionally named info
, and the current request object.
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 pyramid.interfaces.IRoute
for the API of such a route
object).
info['match']
is useful when predicates need access to the route match.
Imagine you want to define a route when a part of the URL matches some
specific values. You could define three view configurations, each one
with its own match_param
value (see Predicate Arguments), but
you want to think of it as one route, and associate it with one view.
See this code:
1class AnyOfPredicate:
2 def __init__(self, val, info):
3 self.segment_name = val[0]
4 self.allowed = tuple(val[1:])
5
6 def text(self):
7 args = (self.segment_name,) + self.allowed
8 return 'any_of = %s' % (args,)
9
10 phash = text
11
12 def __call__(self, info, request):
13 return info['match'][self.segment_name] in self.allowed
14
15
16config.add_route_predicate('any_of', AnyOfPredicate)
17config.add_route('route_to_num', '/{num}', any_of=('num', 'one', 'two', 'three'))
We register this class as a predicate factory with the any_of
keyword argument name.
Then we use that new keyword argument with add_route()
.
When the route is requested, Pyramid instantiates the AnyOfPredicate
class using the value passed to the any_of
argument.
(The info
parameter passed to the factory contains some metadata, you can ignore it for now.)
The resulting instance is a predicate.
It will determine whether incoming requests satisfy its condition.
In the example above, a request for /three
would match the route's URL pattern and satisfy the route's predicate because three
is one of the allowed values, so the route would be matched.
However a request for /millions
will match the route's URL pattern but would not satisfy the route's predicate, and the route would not be matched.
A custom route predicate may also modify the match
dictionary. For
instance, a predicate might do some type conversion of values:
1class IntegersPredicate:
2 def __init__(self, val, info):
3 self.segment_names = val
4
5 def text(self):
6 return 'integers = %s' % (self.segment_names,)
7
8 phash = text
9
10 def __call__(self, info, request):
11 match = info['match']
12 for segment_name in self.segment_names:
13 try:
14 match[segment_name] = int(match[segment_name])
15 except (TypeError, ValueError):
16 pass
17 return True
18
19
20config.add_route_predicate('integers', IntegersPredicate)
21config.add_route('ymd', '/{year}/{month}/{day}',
22 integers=('year', 'month', 'day'))
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:
1class IntegersPredicate:
2 def __init__(self, val, info):
3 self.segment_names = val
4
5 def text(self):
6 return 'integers = %s' % (self.segment_names,)
7
8 phash = text
9
10 def __call__(self, info, request):
11 match = info['match']
12 for segment_name in self.segment_names:
13 match[segment_name] = int(match[segment_name])
14 return True
15
16
17config.add_route_predicate('integers', IntegersPredicate)
18config.add_route('ymd', r'/{year:\d+}/{month:\d+}/{day:\d+}',
19 integers=('year', 'month', 'day'))
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.
警告
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:
1class TwentyTenPredicate:
2 def __init__(self, val, info):
3 pass
4
5 def text(self):
6 return "twenty_ten = True"
7
8 phash = text
9
10 def __call__(self, info, request):
11 if info['route'].name in ('ymd', 'ym', 'y'):
12 return info['match']['year'] == '2010'
13
14config.add_route_predicate('twenty_ten', TwentyTenPredicate)
15config.add_route('y', '/{year}', twenty_ten=True)
16config.add_route('ym', '/{year}/{month}', twenty_ten=True)
17config.add_route('ymd', '/{year}/{month}/{day}', twenty_ten=True)
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
.
The text
method is a way to caption the predicates. This will help you with the pviews
command (see proutes: Displaying All Application Routes) and the pyramid_debugtoolbar.
The phash
("predicate hash") method should return a string that uniquely identifies a specific predicate.
A good way to do that is to use the same argument name and value that are in the call to add_route
,
like in the examples above.
参考
See Adding a Custom View, Route, or Subscriber Predicate for more information about custom view, route, and subscriber predicates.
See also pyramid.interfaces.IRoute
for more API documentation
about route objects.
Route Factories¶
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 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.
1config.add_route('abc', '/abc',
2 factory='myproject.resources.root_factory')
3config.add_view('myproject.views.theview', route_name='abc')
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.
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:
1class Mine(object):
2 def __init__(self, request):
3 pass
A route factory is actually conceptually identical to the root factory described at The Resource Tree.
Supplying a different resource factory for 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.
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:
1class Article(object):
2 def __init__(self, request):
3 matchdict = request.matchdict
4 article = matchdict.get('article', None)
5 if article == '1':
6 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 route's match dict to
see if the article
argument matches a particular string. Our sample
Article
factory class is not very ambitious.
注釈
See Security for more information about Pyramid security and ACLs.
Route View Callable Registration and Lookup Details¶
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 this section. You can skip it if you're uninterested.
When a view is associated with 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 an
add_view
statement mentions aroute name
attribute, a view configuration is registered at startup time. This view configuration uses a 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.
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 resource location mechanism. A Pyramid resource location subsystem (i.e., URL dispatch or traversal) finds a resource object that is the context of a request. Once the context is determined, a separate subsystem named view lookup is then responsible for finding and invoking a 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 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 URL dispatch, Pyramid falls back to traversal to handle the request.
References¶
A tutorial showing how URL dispatch can be used to create a Pyramid application exists in SQLAlchemy + URL dispatch wiki tutorial.