Using Hooks¶
"Hooks" can be used to influence the behavior of the Pyramid framework in various ways.
Changing the Not Found View¶
When Pyramid can't map a URL to view code, it invokes a Not Found View, which is a view callable. The default Not Found View can be overridden through application configuration.
If your application uses imperative configuration, you can replace the
Not Found View by using the
pyramid.config.Configurator.add_notfound_view()
method:
1def notfound(request):
2 return Response('Not Found', status='404 Not Found')
3
4def main(globals, **settings):
5 config = Configurator()
6 config.add_notfound_view(notfound)
The Not Found View callable is a view callable like any other.
If your application instead uses pyramid.view.view_config
decorators
and a scan, you can replace the Not Found View by using the
pyramid.view.notfound_view_config
decorator:
1from pyramid.view import notfound_view_config
2
3@notfound_view_config()
4def notfound(request):
5 return Response('Not Found', status='404 Not Found')
6
7def main(globals, **settings):
8 config = Configurator()
9 config.scan()
This does exactly what the imperative example above showed.
Your application can define multiple Not Found Views if necessary. Both
pyramid.config.Configurator.add_notfound_view()
and
pyramid.view.notfound_view_config
take most of the same arguments as
pyramid.config.Configurator.add_view
and
pyramid.view.view_config
, respectively. This means that Not Found
Views can carry predicates limiting their applicability. For example:
1from pyramid.view import notfound_view_config
2
3@notfound_view_config(request_method='GET')
4def notfound_get(request):
5 return Response('Not Found during GET', status='404 Not Found')
6
7@notfound_view_config(request_method='POST')
8def notfound_post(request):
9 return Response('Not Found during POST', status='404 Not Found')
10
11def main(globals, **settings):
12 config = Configurator()
13 config.scan()
The notfound_get
view will be called when a view could not be found and the
request method was GET
. The notfound_post
view will be called when a
view could not be found and the request method was POST
.
Like any other view, the Not Found View must accept at least a request
parameter, or both context
and request
. The request
is the current
request representing the denied action. The context
(if used in
the call signature) will be the instance of the
HTTPNotFound
exception that caused the view to
be called.
Both pyramid.config.Configurator.add_notfound_view()
and
pyramid.view.notfound_view_config
can be used to automatically
redirect requests to slash-appended routes. See
Redirecting to Slash-Appended Routes for examples.
Here's some sample code that implements a minimal Not Found View callable:
1from pyramid.httpexceptions import HTTPNotFound
2
3def notfound(request):
4 return HTTPNotFound()
注釈
When a Not Found View callable is invoked, it is passed a request.
The exception
attribute of the request will be an instance of the
HTTPNotFound
exception that caused the Not
Found View to be called. The value of request.exception.message
will be
a value explaining why the Not Found exception was raised. This message has
different values depending on whether the pyramid.debug_notfound
environment setting is true or false.
注釈
When a Not Found View callable accepts an argument list as described in
Alternate View Callable Argument/Calling Conventions, the context
passed as the
first argument to the view callable will be the
HTTPNotFound
exception instance. If
available, the resource context will still be available as
request.context
.
警告
The Not Found View callables are only invoked when a
HTTPNotFound
exception is raised. If the
exception is returned from a view then it will be treated as a regular
response object and it will not trigger the custom view.
Changing the Forbidden View¶
When Pyramid can't authorize execution of a view based on the authorization policy in use, it invokes a forbidden view. The default forbidden response has a 403 status code and is very plain, but the view which generates it can be overridden as necessary.
The forbidden view callable is a view callable like any other. The
view configuration which causes it to be a "forbidden" view consists of
using the pyramid.config.Configurator.add_forbidden_view()
API or the
pyramid.view.forbidden_view_config
decorator.
For example, you can add a forbidden view by using the
pyramid.config.Configurator.add_forbidden_view()
method to register a
forbidden view:
1def forbidden(request):
2 return Response('forbidden')
3
4def main(globals, **settings):
5 config = Configurator()
6 config.add_forbidden_view(forbidden)
If instead you prefer to use decorators and a scan, you can use the
pyramid.view.forbidden_view_config
decorator to mark a view callable
as a forbidden view:
1from pyramid.view import forbidden_view_config
2
3@forbidden_view_config()
4def forbidden(request):
5 return Response('forbidden')
6
7def main(globals, **settings):
8 config = Configurator()
9 config.scan()
Like any other view, the forbidden view must accept at least a request
parameter, or both context
and request
. If a forbidden view callable
accepts both context
and request
, the HTTP Exception is passed as
context. The context
as found by the router when the view was denied (which
you normally would expect) is available as request.context
. The
request
is the current request representing the denied action.
Here's some sample code that implements a minimal forbidden view:
1from pyramid.view import view_config
2from pyramid.response import Response
3
4def forbidden_view(request):
5 return Response('forbidden')
注釈
When a forbidden view callable is invoked, it is passed a request.
The exception
attribute of the request will be an instance of the
HTTPForbidden
exception that caused the
forbidden view to be called. The value of request.exception.message
will be a value explaining why the forbidden exception was raised, and
request.exception.result
will be extended information about the
forbidden exception. These messages have different values depending on
whether the pyramid.debug_authorization
environment setting is true or
false.
警告
The forbidden view callables are only invoked when a
HTTPForbidden
exception is raised. If the
exception is returned from a view then it will be treated as a regular
response object and it will not trigger the custom view.
Changing the Request Factory¶
Whenever Pyramid handles a request from a WSGI server, it
creates a request object based on the WSGI environment it has been
passed. By default, an instance of the pyramid.request.Request
class
is created to represent the request object.
The class (a.k.a., "factory") that Pyramid uses to create a request
object instance can be changed by passing a request_factory
argument to the
constructor of the configurator. This argument can be either a
callable or a dotted Python name representing a callable.
1from pyramid.request import Request
2
3class MyRequest(Request):
4 pass
5
6config = Configurator(request_factory=MyRequest)
If you're doing imperative configuration, and you'd rather do it after you've
already constructed a configurator, it can also be registered via the
pyramid.config.Configurator.set_request_factory()
method:
1from pyramid.config import Configurator
2from pyramid.request import Request
3
4class MyRequest(Request):
5 pass
6
7config = Configurator()
8config.set_request_factory(MyRequest)
Adding Methods or Properties to a Request Object¶
バージョン 1.4 で追加.
Since each Pyramid application can only have one request factory, changing the request factory is not that extensible, especially if you want to build composable features (e.g., Pyramid add-ons and plugins).
A lazy property can be registered to the request object via the
pyramid.config.Configurator.add_request_method()
API. This allows you to
specify a callable that will be available on the request object, but will not
actually execute the function until accessed.
警告
This will silently override methods and properties from request factory that have the same name.
1from pyramid.config import Configurator
2
3def total(request, *args):
4 return sum(args)
5
6def prop(request):
7 print("getting the property")
8 return "the property"
9
10config = Configurator()
11config.add_request_method(total)
12config.add_request_method(prop, reify=True)
In the above example, total
is added as a method. However, prop
is
added as a property and its result is cached per-request by setting
reify=True
. This way, we eliminate the overhead of running the function
multiple times.
>>> request.total(1, 2, 3)
6
>>> request.prop
getting the property
'the property'
>>> request.prop
'the property'
To not cache the result of request.prop
, set property=True
instead of
reify=True
.
Here is an example of passing a class to Configurator.add_request_method
:
1from pyramid.config import Configurator
2from pyramid.decorator import reify
3
4class ExtraStuff(object):
5
6 def __init__(self, request):
7 self.request = request
8
9 def total(self, *args):
10 return sum(args)
11
12 # use @property if you don't want to cache the result
13 @reify
14 def prop(self):
15 print("getting the property")
16 return "the property"
17
18config = Configurator()
19config.add_request_method(ExtraStuff, 'extra', reify=True)
We attach and cache an object named extra
to the request
object.
>>> request.extra.total(1, 2, 3)
6
>>> request.extra.prop
getting the property
'the property'
>>> request.extra.prop
'the property'
Changing the Response Factory¶
バージョン 1.6 で追加.
Whenever Pyramid returns a response from a view, it creates a
response object. By default, an instance of the
pyramid.response.Response
class is created to represent the response
object.
The factory that Pyramid uses to create a response object instance can
be changed by passing a pyramid.interfaces.IResponseFactory
argument
to the constructor of the configurator. This argument can be either a
callable or a dotted Python name representing a callable.
The factory takes a single positional argument, which is a Request
object. The argument may be None
.
1from pyramid.response import Response
2
3class MyResponse(Response):
4 pass
5
6config = Configurator(response_factory=lambda r: MyResponse())
If you're doing imperative configuration and you'd rather do it after you've
already constructed a configurator, it can also be registered via the
pyramid.config.Configurator.set_response_factory()
method:
1from pyramid.config import Configurator
2from pyramid.response import Response
3
4class MyResponse(Response):
5 pass
6
7config = Configurator()
8config.set_response_factory(lambda r: MyResponse())
Using the Before Render Event¶
Subscribers to the pyramid.events.BeforeRender
event may introspect
and modify the set of renderer globals before they are passed to a
renderer. This event object iself has a dictionary-like interface that
can be used for this purpose. For example:
1from pyramid.events import subscriber
2from pyramid.events import BeforeRender
3
4@subscriber(BeforeRender)
5def add_global(event):
6 event['mykey'] = 'foo'
An object of this type is sent as an event just before a renderer is invoked.
If a subscriber attempts to add a key that already exists in the renderer
globals dictionary, a KeyError
is raised. This limitation is enforced
because event subscribers do not possess any relative ordering. The set of
keys added to the renderer globals dictionary by all
pyramid.events.BeforeRender
subscribers and renderer globals factories
must be unique.
The dictionary returned from the view is accessible through the
rendering_val
attribute of a BeforeRender
event.
Suppose you return {'mykey': 'somevalue', 'mykey2': 'somevalue2'}
from your
view callable, like so:
1from pyramid.view import view_config
2
3@view_config(renderer='some_renderer')
4def myview(request):
5 return {'mykey': 'somevalue', 'mykey2': 'somevalue2'}
rendering_val
can be used to access these values from the
BeforeRender
object:
1from pyramid.events import subscriber
2from pyramid.events import BeforeRender
3
4@subscriber(BeforeRender)
5def read_return(event):
6 # {'mykey': 'somevalue'} is returned from the view
7 print(event.rendering_val['mykey'])
See the API documentation for the BeforeRender
event
interface at pyramid.interfaces.IBeforeRender
.
Using Response Callbacks¶
Unlike many other web frameworks, Pyramid does not eagerly create a global response object. Adding a response callback allows an application to register an action to be performed against whatever response object is returned by a view, usually in order to mutate the response.
The pyramid.request.Request.add_response_callback()
method is used to
register a response callback.
A response callback is a callable which accepts two positional parameters:
request
and response
. For example:
1def cache_callback(request, response):
2 """Set the cache_control max_age for the response"""
3 if request.exception is not None:
4 response.cache_control.max_age = 360
5request.add_response_callback(cache_callback)
No response callback is called if an unhandled exception happens in application
code, or if the response object returned by a view callable is invalid.
Response callbacks are, however, invoked when a exception view is
rendered successfully. In such a case, the request.exception
attribute
of the request when it enters a response callback will be an exception object
instead of its default value of None
.
Response callbacks are called in the order they're added
(first-to-most-recently-added). All response callbacks are called before the
NewResponse
event is sent. Errors raised by response
callbacks are not handled specially. They will be propagated to the caller of
the Pyramid router application.
A response callback has a lifetime of a single request. If you want a
response callback to happen as the result of every request, you must
re-register the callback into every new request (perhaps within a subscriber of
a NewRequest
event).
Using Finished Callbacks¶
A finished callback is a function that will be called unconditionally by the Pyramid router at the very end of request processing. A finished callback can be used to perform an action at the end of a request unconditionally.
The pyramid.request.Request.add_finished_callback()
method is used to
register a finished callback.
A finished callback is a callable which accepts a single positional parameter:
request
. For example:
1import logging
2
3log = logging.getLogger(__name__)
4
5def log_callback(request):
6 """Log information at the end of request"""
7 log.debug('Request is finished.')
8request.add_finished_callback(log_callback)
Finished callbacks are called in the order they're added (first-to-most-recently-added). Finished callbacks (unlike a response callback) are always called, even if an exception happens in application code that prevents a response from being generated.
The set of finished callbacks associated with a request are called very late
in the processing of that request; they are essentially the very last thing
called by the router before a request "ends". They are called after
response processing has already occurred in a top-level finally:
block
within the router request processing code. As a result, mutations performed to
the request
provided to a finished callback will have no meaningful effect,
because response processing will have already occurred, and the request's scope
will expire almost immediately after all finished callbacks have been
processed.
Errors raised by finished callbacks are not handled specially. They will be propagated to the caller of the Pyramid router application.
A finished callback has a lifetime of a single request. If you want a
finished callback to happen as the result of every request, you must
re-register the callback into every new request (perhaps within a subscriber of
a NewRequest
event).
Changing the Traverser¶
The default traversal algorithm that Pyramid uses is explained in The Traversal Algorithm. Though it is rarely necessary, this default algorithm can be swapped out selectively for a different traversal pattern via configuration.
1from pyramid.config import Configurator
2from myapp.traversal import Traverser
3config = Configurator()
4config.add_traverser(Traverser)
In the example above, myapp.traversal.Traverser
is assumed to be a class
that implements the following interface:
1class Traverser(object):
2 def __init__(self, root):
3 """ Accept the root object returned from the root factory """
4
5 def __call__(self, request):
6 """ Return a dictionary with (at least) the keys ``root``,
7 ``context``, ``view_name``, ``subpath``, ``traversed``,
8 ``virtual_root``, and ``virtual_root_path``. These values are
9 typically the result of a resource tree traversal. ``root``
10 is the physical root object, ``context`` will be a resource
11 object, ``view_name`` will be the view name used (a string),
12 ``subpath`` will be a sequence of strings that
13 followed the view name but were not traversed, ``traversed``
14 will be a sequence of strings that were traversed
15 (including the virtual root path, if any) ``virtual_root``
16 will be a resource object representing the virtual root (or the
17 physical root if traversal was not performed), and
18 ``virtual_root_path`` will be a sequence representing the
19 virtual root path (a sequence of strings) or ``None`` if
20 traversal was not performed.
21
22 Extra keys for special purpose functionality can be added as
23 necessary.
24
25 All values returned in the dictionary will be made available
26 as attributes of the ``request`` object.
27 """
More than one traversal algorithm can be active at the same time. For instance, if your root factory returns more than one type of object conditionally, you could claim that an alternative traverser adapter is "for" only one particular class or interface. When the root factory returned an object that implemented that class or interface, a custom traverser would be used. Otherwise the default traverser would be used. For example:
1from myapp.traversal import Traverser
2from myapp.resources import MyRoot
3from pyramid.config import Configurator
4config = Configurator()
5config.add_traverser(Traverser, MyRoot)
If the above stanza was added to a Pyramid __init__.py
file's main
function, Pyramid would use the myapp.traversal.Traverser
only when
the application root factory returned an instance of the
myapp.resources.MyRoot
object. Otherwise it would use the default
Pyramid traverser to do traversal.
Changing How pyramid.request.Request.resource_url()
Generates a URL¶
When you add a traverser as described in Changing the Traverser, it's
often convenient to continue to use the
pyramid.request.Request.resource_url()
API. However, since the way
traversal is done will have been modified, the URLs it generates by default may
be incorrect when used against resources derived from your custom traverser.
If you've added a traverser, you can change how
resource_url()
generates a URL for a specific
type of resource by adding a call to
pyramid.config.Configurator.add_resource_url_adapter()
.
For example:
1from myapp.traversal import ResourceURLAdapter
2from myapp.resources import MyRoot
3
4config.add_resource_url_adapter(ResourceURLAdapter, MyRoot)
In the above example, the myapp.traversal.ResourceURLAdapter
class will be
used to provide services to resource_url()
any
time the resource passed to resource_url
is of the class
myapp.resources.MyRoot
. The resource_iface
argument MyRoot
represents the type of interface that must be possessed by the resource for
this resource url factory to be found. If the resource_iface
argument is
omitted, this resource URL adapter will be used for all resources.
The API that must be implemented by a class that provides
IResourceURL
is as follows:
1class MyResourceURL(object):
2 """ An adapter which provides the virtual and physical paths of a
3 resource
4 """
5 def __init__(self, resource, request):
6 """ Accept the resource and request and set self.physical_path and
7 self.virtual_path """
8 self.virtual_path = some_function_of(resource, request)
9 self.virtual_path_tuple = some_function_of(resource, request)
10 self.physical_path = some_other_function_of(resource, request)
11 self.physical_path_tuple = some_function_of(resource, request)
The default context URL generator is available for perusal as the class
pyramid.traversal.ResourceURL
in the traversal module.
See pyramid.config.Configurator.add_resource_url_adapter()
for more
information.
Changing How Pyramid Treats View Responses¶
バージョン 1.1 で追加.
It is possible to control how Pyramid treats the result of calling a view
callable on a per-type basis by using a hook involving
pyramid.config.Configurator.add_response_adapter()
or the
response_adapter
decorator.
Pyramid, in various places, adapts the result of calling a view callable to the
IResponse
interface to ensure that the object
returned by the view callable is a "true" response object. The vast majority
of time, the result of this adaptation is the result object itself, as view
callables written by "civilians" who read the narrative documentation contained
in this manual will always return something that implements the
IResponse
interface. Most typically, this will be
an instance of the pyramid.response.Response
class or a subclass. If a
civilian returns a non-Response object from a view callable that isn't
configured to use a renderer, they will typically expect the router to
raise an error. However, you can hook Pyramid in such a way that users can
return arbitrary values from a view callable by providing an adapter which
converts the arbitrary return value into something that implements
IResponse
.
For example, if you'd like to allow view callables to return bare string objects (without requiring a renderer to convert a string to a response object), you can register an adapter which converts the string to a Response:
1from pyramid.response import Response
2
3def string_response_adapter(s):
4 response = Response(s)
5 return response
6
7# config is an instance of pyramid.config.Configurator
8
9config.add_response_adapter(string_response_adapter, str)
Likewise, if you want to be able to return a simplified kind of response object from view callables, you can use the IResponse hook to register an adapter to the more complex IResponse interface:
1from pyramid.response import Response
2
3class SimpleResponse(object):
4 def __init__(self, body):
5 self.body = body
6
7def simple_response_adapter(simple_response):
8 response = Response(simple_response.body)
9 return response
10
11# config is an instance of pyramid.config.Configurator
12
13config.add_response_adapter(simple_response_adapter, SimpleResponse)
If you want to implement your own Response object instead of using the
pyramid.response.Response
object in any capacity at all, you'll have
to make sure that the object implements every attribute and method outlined in
pyramid.interfaces.IResponse
and you'll have to ensure that it uses
zope.interface.implementer(IResponse)
as a class decorator.
1from pyramid.interfaces import IResponse
2from zope.interface import implementer
3
4@implementer(IResponse)
5class MyResponse(object):
6 # ... an implementation of every method and attribute
7 # documented in IResponse should follow ...
When an alternate response object implementation is returned by a view
callable, if that object asserts that it implements
IResponse
(via
zope.interface.implementer(IResponse)
) , an adapter needn't be registered
for the object; Pyramid will use it directly.
An IResponse adapter for webob.reponse.Response
(as opposed to
pyramid.response.Response
) is registered by Pyramid by default at
startup time, as by their nature, instances of this class (and instances of
subclasses of the class) will natively provide IResponse. The adapter
registered for webob.reponse.Response
simply returns the response object.
Instead of using pyramid.config.Configurator.add_response_adapter()
, you
can use the pyramid.response.response_adapter
decorator:
1from pyramid.response import Response
2from pyramid.response import response_adapter
3
4@response_adapter(str)
5def string_response_adapter(s):
6 response = Response(s)
7 return response
The above example, when scanned, has the same effect as:
config.add_response_adapter(string_response_adapter, str)
The response_adapter
decorator will have no effect
until activated by a scan.
Using a View Mapper¶
The default calling conventions for view callables are documented in the Views chapter. You can change the way users define view callables by employing a view mapper.
A view mapper is an object that accepts a set of keyword arguments and which
returns a callable. The returned callable is called with the view
callable object. The returned callable should itself return another callable
which can be called with the "internal calling protocol" (context,
request)
.
You can use a view mapper in a number of ways:
by setting a
__view_mapper__
attribute (which is the view mapper object) on the view callable itselfby passing the mapper object to
pyramid.config.Configurator.add_view()
(or its declarative and decorator equivalents) as themapper
argumentby registering a default view mapper
Here's an example of a view mapper that emulates (somewhat) a Pylons
"controller". The mapper is initialized with some keyword arguments. Its
__call__
method accepts the view object (which will be a class). It uses
the attr
keyword argument it is passed to determine which attribute should
be used as an action method. The wrapper method it returns accepts (context,
request)
and returns the result of calling the action method with keyword
arguments implied by the matchdict after popping the action
out of
it. This somewhat emulates the Pylons style of calling action methods with
routing parameters pulled out of the route matching dict as keyword arguments.
1# framework
2
3class PylonsControllerViewMapper(object):
4 def __init__(self, **kw):
5 self.kw = kw
6
7 def __call__(self, view):
8 attr = self.kw['attr']
9 def wrapper(context, request):
10 matchdict = request.matchdict.copy()
11 matchdict.pop('action', None)
12 inst = view(request)
13 meth = getattr(inst, attr)
14 return meth(**matchdict)
15 return wrapper
16
17class BaseController(object):
18 __view_mapper__ = PylonsControllerViewMapper
A user might make use of these framework components like so:
1# user application
2
3from pyramid.response import Response
4from pyramid.config import Configurator
5import pyramid_handlers
6from wsgiref.simple_server import make_server
7
8class MyController(BaseController):
9 def index(self, id):
10 return Response(id)
11
12if __name__ == '__main__':
13 config = Configurator()
14 config.include(pyramid_handlers)
15 config.add_handler('one', '/{id}', MyController, action='index')
16 config.add_handler('two', '/{action}/{id}', MyController)
17 server.make_server('0.0.0.0', 8080, config.make_wsgi_app())
18 server.serve_forever()
The pyramid.config.Configurator.set_view_mapper()
method can be used to
set a default view mapper (overriding the superdefault view mapper used by
Pyramid itself).
A single view registration can use a view mapper by passing the mapper as the
mapper
argument to add_view()
.
Registering Configuration Decorators¶
Decorators such as view_config
don't change the behavior
of the functions or classes they're decorating. Instead when a scan is
performed, a modified version of the function or class is registered with
Pyramid.
You may wish to have your own decorators that offer such behaviour. This is possible by using the Venusian package in the same way that it is used by Pyramid.
By way of example, let's suppose you want to write a decorator that registers the function it wraps with a Zope Component Architecture "utility" within the application registry provided by Pyramid. The application registry and the utility inside the registry is likely only to be available once your application's configuration is at least partially completed. A normal decorator would fail as it would be executed before the configuration had even begun.
However, using Venusian, the decorator could be written as follows:
1import venusian
2from mypackage.interfaces import IMyUtility
3
4class registerFunction(object):
5
6 def __init__(self, path):
7 self.path = path
8
9 def register(self, scanner, name, wrapped):
10 registry = scanner.config.registry
11 registry.getUtility(IMyUtility).register(
12 self.path, wrapped)
13
14 def __call__(self, wrapped):
15 venusian.attach(wrapped, self.register)
16 return wrapped
This decorator could then be used to register functions throughout your code:
1@registerFunction('/some/path')
2def my_function():
3 do_stuff()
However, the utility would only be looked up when a scan was performed, enabling you to set up the utility in advance:
1from zope.interface import implementer
2
3from wsgiref.simple_server import make_server
4from pyramid.config import Configurator
5from mypackage.interfaces import IMyUtility
6
7@implementer(IMyUtility)
8class UtilityImplementation:
9
10 def __init__(self):
11 self.registrations = {}
12
13 def register(self, path, callable_):
14 self.registrations[path] = callable_
15
16if __name__ == '__main__':
17 config = Configurator()
18 config.registry.registerUtility(UtilityImplementation())
19 config.scan()
20 app = config.make_wsgi_app()
21 server = make_server('0.0.0.0', 8080, app)
22 server.serve_forever()
For full details, please read the Venusian documentation.
Registering Tweens¶
バージョン 1.2 で追加: Tweens
A tween (a contraction of the word "between") is a bit of code that sits between the Pyramid router's main request handling function and the upstream WSGI component that uses Pyramid as its "app". This is a feature that may be used by Pyramid framework extensions to provide, for example, Pyramid-specific view timing support bookkeeping code that examines exceptions before they are returned to the upstream WSGI application. Tweens behave a bit like WSGI middleware, but they have the benefit of running in a context in which they have access to the Pyramid request, response, and application registry, as well as the Pyramid rendering machinery.
Creating a Tween¶
To create a tween, you must write a "tween factory". A tween factory must be a
globally importable callable which accepts two arguments: handler
and
registry
. handler
will be either the main Pyramid request handling
function or another tween. registry
will be the Pyramid application
registry represented by this Configurator. A tween factory must return the
tween (a callable object) when it is called.
A tween is called with a single argument, request
, which is the
request created by Pyramid's router when it receives a WSGI request. A
tween should return a response, usually the one generated by the
downstream Pyramid application.
You can write the tween factory as a simple closure-returning function:
1def simple_tween_factory(handler, registry):
2 # one-time configuration code goes here
3
4 def simple_tween(request):
5 # code to be executed for each request before
6 # the actual application code goes here
7
8 response = handler(request)
9
10 # code to be executed for each request after
11 # the actual application code goes here
12
13 return response
14
15 return simple_tween
Alternatively, the tween factory can be a class with the __call__
magic
method:
1class simple_tween_factory(object):
2 def __init__(self, handler, registry):
3 self.handler = handler
4 self.registry = registry
5
6 # one-time configuration code goes here
7
8 def __call__(self, request):
9 # code to be executed for each request before
10 # the actual application code goes here
11
12 response = self.handler(request)
13
14 # code to be executed for each request after
15 # the actual application code goes here
16
17 return response
You should avoid mutating any state on the tween instance. The tween is invoked once per request and any shared mutable state needs to be carefully handled to avoid any race conditions.
The closure style performs slightly better and enables you to conditionally omit the tween from the request processing pipeline (see the following timing tween example), whereas the class style makes it easier to have shared mutable state and allows subclassing.
Here's a complete example of a tween that logs the time spent processing each request:
1# in a module named myapp.tweens
2
3import time
4from pyramid.settings import asbool
5import logging
6
7log = logging.getLogger(__name__)
8
9def timing_tween_factory(handler, registry):
10 if asbool(registry.settings.get('do_timing')):
11 # if timing support is enabled, return a wrapper
12 def timing_tween(request):
13 start = time.time()
14 try:
15 response = handler(request)
16 finally:
17 end = time.time()
18 log.debug('The request took %s seconds' %
19 (end - start))
20 return response
21 return timing_tween
22 # if timing support is not enabled, return the original
23 # handler
24 return handler
In the above example, the tween factory defines a timing_tween
tween and
returns it if asbool(registry.settings.get('do_timing'))
is true. It
otherwise simply returns the handler which it was given. The
registry.settings
attribute is a handle to the deployment settings provided
by the user (usually in an .ini
file). In this case, if the user has
defined a do_timing
setting and that setting is True
, the user has said
they want to do timing, so the tween factory returns the timing tween; it
otherwise just returns the handler it has been provided, preventing any timing.
The example timing tween simply records the start time, calls the downstream handler, logs the number of seconds consumed by the downstream handler, and returns the response.
Registering an Implicit Tween Factory¶
Once you've created a tween factory, you can register it into the implicit
tween chain using the pyramid.config.Configurator.add_tween()
method
using its dotted Python name.
Here's an example of registering a tween factory as an "implicit" tween in a Pyramid application:
1from pyramid.config import Configurator
2config = Configurator()
3config.add_tween('myapp.tweens.timing_tween_factory')
Note that you must use a dotted Python name as the first argument to
pyramid.config.Configurator.add_tween()
; this must point at a tween
factory. You cannot pass the tween factory object itself to the method: it
must be dotted Python name that points to a globally importable object.
In the above example, we assume that a timing_tween_factory
tween factory
was defined in a module named myapp.tweens
, so the tween factory is
importable as myapp.tweens.timing_tween_factory
.
When you use pyramid.config.Configurator.add_tween()
, you're instructing
the system to use your tween factory at startup time unless the user has
provided an explicit tween list in their configuration. This is what's meant
by an "implicit" tween. A user can always elect to supply an explicit tween
list, reordering or disincluding implicitly added tweens. See
Explicit Tween Ordering for more information about explicit tween
ordering.
If more than one call to pyramid.config.Configurator.add_tween()
is made
within a single application configuration, the tweens will be chained together
at application startup time. The first tween factory added via add_tween
will be called with the Pyramid exception view tween factory as its handler
argument, then the tween factory added directly after that one will be called
with the result of the first tween factory as its handler
argument, and so
on, ad infinitum until all tween factories have been called. The Pyramid router
will use the outermost tween produced by this chain (the tween generated by the
very last tween factory added) as its request handler function. For example:
1from pyramid.config import Configurator
2
3config = Configurator()
4config.add_tween('myapp.tween_factory1')
5config.add_tween('myapp.tween_factory2')
The above example will generate an implicit tween chain that looks like this.
INGRESS (implicit)
myapp.tween_factory2
myapp.tween_factory1
pyramid.tweens.excview_tween_factory (implicit)
MAIN (implicit)
Suggesting Implicit Tween Ordering¶
By default, as described above, the ordering of the chain is controlled
entirely by the relative ordering of calls to
pyramid.config.Configurator.add_tween()
. However, the caller of
add_tween
can provide an optional hint that can influence the implicit
tween chain ordering by supplying under
or over
(or both) arguments to
add_tween()
. These hints are only used when
an explicit tween ordering is not used. See Explicit Tween Ordering for
a description of how to set an explicit tween ordering.
Allowable values for under
or over
(or both) are:
None
(the default),a dotted Python name to a tween factory: a string representing the predicted dotted name of a tween factory added in a call to
add_tween
in the same configuration session,one of the constants
pyramid.tweens.MAIN
,pyramid.tweens.INGRESS
, orpyramid.tweens.EXCVIEW
, oran iterable of any combination of the above. This allows the user to specify fallbacks if the desired tween is not included, as well as compatibility with multiple other tweens.
Effectively, over
means "closer to the request ingress than" and under
means "closer to the main Pyramid application than". You can think of an onion
with outer layers over the inner layers, the application being under all the
layers at the center.
For example, the following call to
add_tween()
will attempt to place the tween
factory represented by myapp.tween_factory
directly "above" (in ptweens
order) the main Pyramid request handler.
1import pyramid.tweens
2
3config.add_tween('myapp.tween_factory', over=pyramid.tweens.MAIN)
The above example will generate an implicit tween chain that looks like this.
INGRESS (implicit)
pyramid.tweens.excview_tween_factory (implicit)
myapp.tween_factory
MAIN (implicit)
Likewise, calling the following call to
add_tween()
will attempt to place this tween
factory "above" the main handler but "below" a separately added tween factory:
1import pyramid.tweens
2
3config.add_tween('myapp.tween_factory1',
4 over=pyramid.tweens.MAIN)
5config.add_tween('myapp.tween_factory2',
6 over=pyramid.tweens.MAIN,
7 under='myapp.tween_factory1')
The above example will generate an implicit tween chain that looks like this:
INGRESS (implicit)
pyramid.tweens.excview_tween_factory (implicit)
myapp.tween_factory1
myapp.tween_factory2
MAIN (implicit)
Specifying neither over
nor under
is equivalent to specifying
under=INGRESS
.
If all options for under
(or over
) cannot be found in the current
configuration, it is an error. If some options are specified purely for
compatibility with other tweens, just add a fallback of MAIN
or INGRESS
.
For example, under=('someothertween', 'someothertween2', INGRESS)
. This
constraint will require the tween to be located under the someothertween
tween, the someothertween2
tween, and INGRESS
. If any of these is not
in the current configuration, this constraint will only organize itself based
on the tweens that are present.
Explicit Tween Ordering¶
Implicit tween ordering is obviously only best-effort. Pyramid will attempt to
provide an implicit order of tweens as best it can using hints provided by
calls to add_tween()
. But because it's only
best-effort, if very precise tween ordering is required, the only surefire way
to get it is to use an explicit tween order. The deploying user can override
the implicit tween inclusion and ordering implied by calls to
add_tween()
entirely by using the
pyramid.tweens
settings value. When used, this settings value must be a
list of Python dotted names which will override the ordering (and inclusion) of
tween factories in the implicit tween chain. For example:
1[app:main]
2use = egg:MyApp
3pyramid.reload_templates = true
4pyramid.debug_authorization = false
5pyramid.debug_notfound = false
6pyramid.debug_routematch = false
7pyramid.debug_templates = true
8pyramid.tweens = myapp.my_cool_tween_factory
9 pyramid.tweens.excview_tween_factory
In the above configuration, calls made during configuration to
pyramid.config.Configurator.add_tween()
are ignored, and the user is
telling the system to use the tween factories he has listed in the
pyramid.tweens
configuration setting (each is a dotted Python name
which points to a tween factory) instead of any tween factories added via
pyramid.config.Configurator.add_tween()
. The first tween factory in
the pyramid.tweens
list will be used as the producer of the effective
Pyramid request handling function; it will wrap the tween factory
declared directly "below" it, ad infinitum. The "main" Pyramid request handler
is implicit, and always "at the bottom".
注釈
Pyramid's own exception view handling logic is implemented as a
tween factory function: pyramid.tweens.excview_tween_factory()
. If
Pyramid exception view handling is desired, and tween factories are
specified via the pyramid.tweens
configuration setting, the
pyramid.tweens.excview_tween_factory()
function must be added to the
pyramid.tweens
configuration setting list explicitly. If it is not
present, Pyramid will not perform exception view handling.
Tween Conflicts and Ordering Cycles¶
Pyramid will prevent the same tween factory from being added to the tween chain
more than once using configuration conflict detection. If you wish to add the
same tween factory more than once in a configuration, you should either: (a)
use a tween factory that is a separate globally importable instance object from
the factory that it conflicts with; (b) use a function or class as a tween
factory with the same logic as the other tween factory it conflicts with, but
with a different __name__
attribute; or (c) call
pyramid.config.Configurator.commit()
between calls to
pyramid.config.Configurator.add_tween()
.
If a cycle is detected in implicit tween ordering when over
and under
are used in any call to add_tween
, an exception will be raised at startup
time.
Displaying Tween Ordering¶
The ptweens
command-line utility can be used to report the current implicit
and explicit tween chains used by an application. See
ptweens: Displaying "Tweens".
Adding a Custom View, Route, or Subscriber Predicate¶
バージョン 1.4 で追加.
View and Route Predicates¶
View and route predicates used during configuration allow you to narrow the set
of circumstances under which a view or route will match. For example, the
request_method
view predicate can be used to ensure a view callable is only
invoked when the request's method is POST
:
@view_config(request_method='POST')
def someview(request):
...
Likewise, a similar predicate can be used as a route predicate:
config.add_route('name', '/foo', request_method='POST')
Many other built-in predicates exists (request_param
, and others). You can
add custom predicates to the list of available predicates by using one of
pyramid.config.Configurator.add_view_predicate()
or
pyramid.config.Configurator.add_route_predicate()
. The former adds a
view predicate, the latter a route predicate.
When using one of those APIs, you pass a name and a factory to add a predicate during Pyramid's configuration stage. For example:
config.add_view_predicate('content_type', ContentTypePredicate)
The above example adds a new predicate named content_type
to the list of
available predicates for views. This will allow the following view
configuration statement to work:
1@view_config(content_type='File')
2def aview(request): ...
The first argument to pyramid.config.Configurator.add_view_predicate()
,
the name, is a string representing the name that is expected to be passed to
view_config
(or its imperative analogue add_view
).
The second argument is a view or route predicate factory, or a dotted
Python name which refers to a view or route predicate factory. A view or
route predicate factory is most often a class with a constructor
(__init__
), a text
method, a phash
method, and a __call__
method. For example:
1class ContentTypePredicate(object):
2 def __init__(self, val, info):
3 self.val = val
4
5 def text(self):
6 return 'content_type = %s' % (self.val,)
7
8 phash = text
9
10 def __call__(self, context, request):
11 return request.content_type == self.val
The constructor of an pyramid.interfaces.IPredicateFactory`
takes two arguments: val
and info
.
The val
argument will be the argument passed to view_config
(or add_view
).
In the example above, it will be the string File
.
The second argument, info
, will be an pyramid.interfaces.IPredicateInfo
instance created relative to the action configuring the predicate.
This means the info.package
value is the package where the action is invoked passing in val
and info.maybe_dotted
is also relative to this package.
The text
method must return a string. It should be useful to describe the
behavior of the predicate in error messages.
The phash
method must return a string or a sequence of strings. It's most
often the same as text
, as long as text
uniquely describes the
predicate's name and the value passed to the constructor. If text
is more
general, or doesn't describe things that way, phash
should return a string
with the name and the value serialized. The result of phash
is not seen in
output anywhere, it just informs the uniqueness constraints for view
configuration.
The __call__
method differs depending on whether the predicate is used as
a view predicate or a route predicate:
When used as a route predicate, the
__call__
signature is(info, request)
. Theinfo
object is a dictionary containing two keys:match
androute
.info['match']
is the matchdict containing the patterns matched in the route pattern.info['route']
is thepyramid.interfaces.IRoute
object for the current route.When used as a view predicate, the
__call__
signature is(context, request)
. Thecontext
is the result of traversal performed using either the route's root factory or the app's default root factory.
In all cases the __call__
method is expected to return True
or
False
.
It is possible to use the same predicate factory as both a view predicate and
as a route predicate, but they'll need to handle the info
or context
argument specially (many predicates do not need this argument) and you'll need
to call add_view_predicate
and add_route_predicate
separately with
the same factory.
Subscriber Predicates¶
Subscriber predicates work almost exactly like view and route predicates. They narrow the set of circumstances in which a subscriber will be called. There are several minor differences between a subscriber predicate and a view or route predicate:
There are no default subscriber predicates. You must register one to use one.
The
__call__
method of a subscriber predicate accepts a singleevent
object instead of acontext
and arequest
.Not every subscriber predicate can be used with every event type. Some subscriber predicates will assume a certain event type.
Here's an example of a subscriber predicate that can be used in conjunction
with a subscriber that subscribes to the pyramid.events.NewRequest
event type.
1class RequestPathStartsWith(object):
2 def __init__(self, val, info):
3 self.val = val
4
5 def text(self):
6 return 'request_path_startswith = %s' % (self.val,)
7
8 phash = text
9
10 def __call__(self, event):
11 return event.request.path.startswith(self.val)
Once you've created a subscriber predicate, it may be registered via
pyramid.config.Configurator.add_subscriber_predicate()
. For example:
config.add_subscriber_predicate(
'request_path_startswith', RequestPathStartsWith)
Once a subscriber predicate is registered, you can use it in a call to
pyramid.config.Configurator.add_subscriber()
or to
pyramid.events.subscriber
. Here's an example of using the previously
registered request_path_startswith
predicate in a call to
add_subscriber()
:
1# define a subscriber in your code
2
3def yosubscriber(event):
4 event.request.yo = 'YO!'
5
6# and at configuration time
7
8config.add_subscriber(yosubscriber, NewRequest,
9 request_path_startswith='/add_yo')
Here's the same subscriber/predicate/event-type combination used via
subscriber
.
1from pyramid.events import subscriber
2
3@subscriber(NewRequest, request_path_startswith='/add_yo')
4def yosubscriber(event):
5 event.request.yo = 'YO!'
In either of the above configurations, the yosubscriber
callable will only
be called if the request path starts with /add_yo
. Otherwise the event
subscriber will not be called.
Note that the request_path_startswith
subscriber you defined can be used
with events that have a request
attribute, but not ones that do not. So,
for example, the predicate can be used with subscribers registered for
pyramid.events.NewRequest
and pyramid.events.ContextFound
events, but it cannot be used with subscribers registered for
pyramid.events.ApplicationCreated
because the latter type of event has
no request
attribute. The point being, unlike route and view predicates,
not every type of subscriber predicate will necessarily be applicable for use
in every subscriber registration. It is not the responsibility of the
predicate author to make every predicate make sense for every event type; it is
the responsibility of the predicate consumer to use predicates that make sense
for a particular event type registration.
View Derivers¶
バージョン 1.7 で追加.
Every URL processed by Pyramid is matched against a custom view
pipeline. See Request Processing for how this works. The view pipeline
itself is built from the user-supplied view callable, which is then
composed with view derivers. A view deriver is a
composable element of the view pipeline which is used to wrap a view with
added functionality. View derivers are very similar to the decorator
argument to pyramid.config.Configurator.add_view()
, except that they have
the option to execute for every view in the application.
It is helpful to think of a view deriver as middleware for views. Unlike tweens or WSGI middleware which are scoped to the application itself, a view deriver is invoked once per view in the application, and can use configuration options from the view to customize its behavior.
Built-in View Derivers¶
There are several built-in view derivers that Pyramid will automatically apply to any view. Below they are defined in order from furthest to closest to the user-defined view callable:
secured_view
Enforce the
permission
defined on the view. This element is a no-op if no permission is defined. Note there will always be a permission defined if a default permission was assigned viapyramid.config.Configurator.set_default_permission()
unless the view is an exception view.This element will also output useful debugging information when
pyramid.debug_authorization
is enabled.
csrf_view
Used to check the CSRF token provided in the request. This element is a no-op if
require_csrf
view option is notTrue
. Note there will always be arequire_csrf
option if a default value was assigned viapyramid.config.Configurator.set_default_csrf_options()
unless the view is an exception view.
owrapped_view
Invokes the wrapped view defined by the
wrapper
option.
http_cached_view
Applies cache control headers to the response defined by the
http_cache
option. This element is a no-op if thepyramid.prevent_http_cache
setting is enabled or thehttp_cache
option isNone
.
decorated_view
Wraps the view with the decorators from the
decorator
option.
rendered_view
Adapts the result of the view callable into a response object. Below this point the result may be any Python object.
mapped_view
Applies the view mapper defined by the
mapper
option or the application's default view mapper to the view callable. This is always the closest deriver to the user-defined view and standardizes the view pipeline interface to accept(context, request)
from all previous view derivers.
警告
Any view derivers defined under
the rendered_view
are not
guaranteed to receive a valid response object. Rather they will receive the
result from the view mapper which is likely the original response
returned from the view. This is possibly a dictionary for a renderer but it
may be any Python object that may be adapted into a response.
Custom View Derivers¶
It is possible to define custom view derivers which will affect all views in an
application. There are many uses for this, but most will likely be centered
around monitoring and security. In order to register a custom view
deriver, you should create a callable that conforms to the
pyramid.interfaces.IViewDeriver
interface, and then register it with
your application using pyramid.config.Configurator.add_view_deriver()
.
The callable should accept the view
to be wrapped and the info
object
which is an instance of pyramid.interfaces.IViewDeriverInfo
.
For example, below is a callable that can provide timing information for the
view pipeline:
1import time
2
3def timing_view(view, info):
4 if info.options.get('timed'):
5 def wrapper_view(context, request):
6 start = time.time()
7 response = view(context, request)
8 end = time.time()
9 response.headers['X-View-Performance'] = '%.3f' % (end - start,)
10 return response
11 return wrapper_view
12 return view
13
14timing_view.options = ('timed',)
15
16config.add_view_deriver(timing_view)
The setting of timed
on the timing_view signifies to Pyramid that timed
is a valid view_config
keyword argument now. The timing_view
custom
view deriver as registered above will only be active for any view defined with
a timed=True
value passed as one of its view_config
keywords.
For example, this view configuration will not be a timed view:
1@view_config(route_name='home')
2def home(request):
3 return Response('Home')
But this view will have timing information added to the response headers:
1@view_config(route_name='home', timed=True)
2def home(request):
3 return Response('Home')
View derivers are unique in that they have access to most of the options
passed to pyramid.config.Configurator.add_view()
in order to decide what
to do, and they have a chance to affect every view in the application.
Exception Views and View Derivers¶
A view deriver has the opportunity to wrap any view, including
an exception view. In general this is fine, but certain view derivers
may wish to avoid doing certain things when handling exceptions. For example,
the csrf_view
and secured_view
built-in view derivers will not perform
security checks on exception views unless explicitly told to do so.
You can check for info.exception_only
on the
pyramid.interfaces.IViewDeriverInfo
object when wrapping the view
to determine whether you are wrapping an exception view or a normal view.
Ordering View Derivers¶
By default, every new view deriver is added between the decorated_view
and
rendered_view
built-in derivers. It is possible to customize this ordering
using the over
and under
options. Each option can use the names of
other view derivers in order to specify an ordering. There should rarely be a
reason to worry about the ordering of the derivers except when the deriver
depends on other operations in the view pipeline.
Both over
and under
may also be iterables of constraints. For either
option, if one or more constraints was defined, at least one must be satisfied,
else a pyramid.exceptions.ConfigurationError
will be raised. This may
be used to define fallback constraints if another deriver is missing.
Two sentinel values exist, pyramid.viewderivers.INGRESS
and
pyramid.viewderivers.VIEW
, which may be used when specifying
constraints at the edges of the view pipeline. For example, to add a deriver
at the start of the pipeline you may use under=INGRESS
.
It is not possible to add a view deriver under the mapped_view
as the
view mapper is intimately tied to the signature of the user-defined
view callable. If you simply need to know what the original view
callable was, it can be found as info.original_view
on the provided
pyramid.interfaces.IViewDeriverInfo
object passed to every view
deriver.
警告
The default constraints for any view deriver are over='rendered_view'
and under='decorated_view'
. When escaping these constraints you must
take care to avoid cyclic dependencies between derivers. For example, if
you want to add a new view deriver before secured_view
then
simply specifying over='secured_view'
is not enough, because the
default is also under decorated view
there will be an unsatisfiable
cycle. You must specify a valid under
constraint as well, such as
under=INGRESS
to fall between INGRESS and secured_view
at the
beginning of the view pipeline.