diff options
| author | Christoph Zwerschke <cito@online.de> | 2016-04-19 20:07:12 +0200 |
|---|---|---|
| committer | Christoph Zwerschke <cito@online.de> | 2016-04-19 20:07:12 +0200 |
| commit | 3629c49e46207ff5162a82883c14937e6ef4c186 (patch) | |
| tree | 1306181202cb8313f16080789f5b9ab1eeb61d53 /docs/narr/hooks.rst | |
| parent | 804ba0b2f434781e77d2b5191f1cd76a490f6610 (diff) | |
| parent | 6c16fb020027fac47e4d2e335cd9e264dba8aa3b (diff) | |
| download | pyramid-3629c49e46207ff5162a82883c14937e6ef4c186.tar.gz pyramid-3629c49e46207ff5162a82883c14937e6ef4c186.tar.bz2 pyramid-3629c49e46207ff5162a82883c14937e6ef4c186.zip | |
Merge remote-tracking branch 'refs/remotes/Pylons/master'
Diffstat (limited to 'docs/narr/hooks.rst')
| -rw-r--r-- | docs/narr/hooks.rst | 1553 |
1 files changed, 1290 insertions, 263 deletions
diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index be139ad74..b776f99e8 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -14,61 +14,122 @@ in various ways. Changing the Not Found View --------------------------- -When :app:`Pyramid` can't map a URL to view code, it invokes a :term:`not -found view`, which is a :term:`view callable`. A default notfound view -exists. The default not found view can be overridden through application -configuration. +When :app:`Pyramid` can't map a URL to view code, it invokes a :term:`Not Found +View`, which is a :term:`view callable`. The default Not Found View can be +overridden through application configuration. + +If your application uses :term:`imperative configuration`, you can replace the +Not Found View by using the +:meth:`pyramid.config.Configurator.add_notfound_view` method: + +.. code-block:: python + :linenos: -The :term:`not found view` callable is a view callable like any other. The -:term:`view configuration` which causes it to be a "not found" view consists -only of naming the :exc:`pyramid.exceptions.NotFound` class as the -``context`` of the view configuration. + def notfound(request): + return Response('Not Found, dude', status='404 Not Found') -If your application uses :term:`imperative configuration`, you can replace -the Not Found view by using the :meth:`pyramid.config.Configurator.add_view` -method to register an "exception view": + def main(globals, **settings): + config = Configurator() + config.add_notfound_view(notfound) + +The :term:`Not Found View` callable is a view callable like any other. + +If your application instead uses :class:`pyramid.view.view_config` decorators +and a :term:`scan`, you can replace the Not Found View by using the +:class:`pyramid.view.notfound_view_config` decorator: .. code-block:: python :linenos: - from pyramid.exceptions import NotFound - from helloworld.views import notfound_view - config.add_view(notfound_view, context=NotFound) + from pyramid.view import notfound_view_config + + @notfound_view_config() + def notfound(request): + return Response('Not Found, dude', status='404 Not Found') -Replace ``helloworld.views.notfound_view`` with a reference to the -:term:`view callable` you want to use to represent the Not Found view. + def main(globals, **settings): + config = Configurator() + config.scan() -Like any other view, the notfound view must accept at least a ``request`` -parameter, or both ``context`` and ``request``. The ``request`` is the -current :term:`request` representing the denied action. The ``context`` (if -used in the call signature) will be the instance of the -:exc:`~pyramid.exceptions.NotFound` exception that caused the view to be -called. +This does exactly what the imperative example above showed. -Here's some sample code that implements a minimal NotFound view callable: +Your application can define *multiple* Not Found Views if necessary. Both +:meth:`pyramid.config.Configurator.add_notfound_view` and +:class:`pyramid.view.notfound_view_config` take most of the same arguments as +:class:`pyramid.config.Configurator.add_view` and +:class:`pyramid.view.view_config`, respectively. This means that Not Found +Views can carry predicates limiting their applicability. For example: + +.. code-block:: python + :linenos: + + from pyramid.view import notfound_view_config + + @notfound_view_config(request_method='GET') + def notfound_get(request): + return Response('Not Found during GET, dude', status='404 Not Found') + + @notfound_view_config(request_method='POST') + def notfound_post(request): + return Response('Not Found during POST, dude', status='404 Not Found') + + def main(globals, **settings): + config = Configurator() + 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 +:term:`request` representing the denied action. The ``context`` (if used in +the call signature) will be the instance of the +:exc:`~pyramid.httpexceptions.HTTPNotFound` exception that caused the view to +be called. + +Both :meth:`pyramid.config.Configurator.add_notfound_view` and +:class:`pyramid.view.notfound_view_config` can be used to automatically +redirect requests to slash-appended routes. See +:ref:`redirecting_to_slash_appended_routes` for examples. + +Here's some sample code that implements a minimal :term:`Not Found View` +callable: .. code-block:: python :linenos: from pyramid.httpexceptions import HTTPNotFound - def notfound_view(request): + def notfound(request): return HTTPNotFound() -.. note:: When a NotFound view callable is invoked, it is passed a - :term:`request`. The ``exception`` attribute of the request will - be an instance of the :exc:`~pyramid.exceptions.NotFound` - exception that caused the not found view to be called. The value - of ``request.exception.args[0]`` will be a value explaining why the - not found error was raised. This message will be different when - the ``debug_notfound`` environment setting is true than it is when - it is false. - -.. warning:: When a NotFound view callable accepts an argument list as - described in :ref:`request_and_context_view_definitions`, the ``context`` - passed as the first argument to the view callable will be the - :exc:`~pyramid.exceptions.NotFound` exception instance. If available, the - resource context will still be available as ``request.context``. +.. note:: + + When a Not Found View callable is invoked, it is passed a :term:`request`. + The ``exception`` attribute of the request will be an instance of the + :exc:`~pyramid.httpexceptions.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. + +.. note:: + + Both :meth:`pyramid.config.Configurator.add_notfound_view` and + :class:`pyramid.view.notfound_view_config` are new as of Pyramid 1.3. + Older Pyramid documentation instructed users to use ``add_view`` instead, + with a ``context`` of ``HTTPNotFound``. This still works; the convenience + method and decorator are just wrappers around this functionality. + +.. warning:: + + When a Not Found View callable accepts an argument list as described in + :ref:`request_and_context_view_definitions`, the ``context`` passed as the + first argument to the view callable will be the + :exc:`~pyramid.httpexceptions.HTTPNotFound` exception instance. If + available, the resource context will still be available as + ``request.context``. .. index:: single: forbidden view @@ -79,55 +140,75 @@ Changing the Forbidden View --------------------------- When :app:`Pyramid` can't authorize execution of a view based on the -:term:`authorization policy` in use, it invokes a :term:`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. +:term:`authorization policy` in use, it invokes a :term:`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 :term:`forbidden view` callable is a view callable like any other. The -:term:`view configuration` which causes it to be a "forbidden" view consists -only of naming the :exc:`pyramid.exceptions.Forbidden` class as the -``context`` of the view configuration. +:term:`view configuration` which causes it to be a "forbidden" view consists of +using the :meth:`pyramid.config.Configurator.add_forbidden_view` API or the +:class:`pyramid.view.forbidden_view_config` decorator. + +For example, you can add a forbidden view by using the +:meth:`pyramid.config.Configurator.add_forbidden_view` method to register a +forbidden view: + +.. code-block:: python + :linenos: -You can replace the forbidden view by using the -:meth:`pyramid.config.Configurator.add_view` method to register an "exception -view": + def forbidden(request): + return Response('forbidden') + + def main(globals, **settings): + config = Configurator() + config.add_forbidden_view(forbidden_view) + +If instead you prefer to use decorators and a :term:`scan`, you can use the +:class:`pyramid.view.forbidden_view_config` decorator to mark a view callable +as a forbidden view: .. code-block:: python :linenos: - from helloworld.views import forbidden_view - from pyramid.exceptions import Forbidden - config.add_view(forbidden_view, context=Forbidden) + from pyramid.view import forbidden_view_config + + @forbidden_view_config() + def forbidden(request): + return Response('forbidden') -Replace ``helloworld.views.forbidden_view`` with a reference to the Python -:term:`view callable` you want to use to represent the Forbidden view. + def main(globals, **settings): + config = Configurator() + config.scan() Like any other view, the forbidden view must accept at least a ``request`` -parameter, or both ``context`` and ``request``. The ``context`` (available -as ``request.context`` if you're using the request-only view argument -pattern) is the context found by the router when the view invocation was -denied. The ``request`` is the current :term:`request` representing the -denied action. +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 :term:`request` representing the denied action. Here's some sample code that implements a minimal forbidden view: .. code-block:: python :linenos: - from pyramid.views import view_config + from pyramid.view import view_config from pyramid.response import Response def forbidden_view(request): return Response('forbidden') -.. note:: When a forbidden view callable is invoked, it is passed a - :term:`request`. The ``exception`` attribute of the request will - be an instance of the :exc:`~pyramid.exceptions.Forbidden` - exception that caused the forbidden view to be called. The value - of ``request.exception.args[0]`` will be a value explaining why the - forbidden was raised. This message will be different when the - ``debug_authorization`` environment setting is true than it is when - it is false. +.. note:: + + When a forbidden view callable is invoked, it is passed a :term:`request`. + The ``exception`` attribute of the request will be an instance of the + :exc:`~pyramid.httpexceptions.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. .. index:: single: request factory @@ -137,13 +218,13 @@ Here's some sample code that implements a minimal forbidden view: Changing the Request Factory ---------------------------- -Whenever :app:`Pyramid` handles a :term:`WSGI` request, it creates a -:term:`request` object based on the WSGI environment it has been passed. By -default, an instance of the :class:`pyramid.request.Request` class is created -to represent the request object. +Whenever :app:`Pyramid` handles a request from a :term:`WSGI` server, it +creates a :term:`request` object based on the WSGI environment it has been +passed. By default, an instance of the :class:`pyramid.request.Request` class +is created to represent the request object. -The class (aka "factory") that :app:`Pyramid` uses to create a request object -instance can be changed by passing a ``request_factory`` argument to the +The class (a.k.a., "factory") that :app:`Pyramid` uses to create a request +object instance can be changed by passing a ``request_factory`` argument to the constructor of the :term:`configurator`. This argument can be either a callable or a :term:`dotted Python name` representing a callable. @@ -158,7 +239,7 @@ callable or a :term:`dotted Python name` representing a callable. config = Configurator(request_factory=MyRequest) If you're doing imperative configuration, and you'd rather do it after you've -already constructed a :term:`configurator` it can also be registered via the +already constructed a :term:`configurator`, it can also be registered via the :meth:`pyramid.config.Configurator.set_request_factory` method: .. code-block:: python @@ -174,75 +255,162 @@ already constructed a :term:`configurator` it can also be registered via the config.set_request_factory(MyRequest) .. index:: - single: renderer globals + single: request method + +.. _adding_request_method: + +Adding Methods or Properties to a Request Object +------------------------------------------------ + +.. versionadded:: 1.4 -.. _adding_renderer_globals: +Since each Pyramid application can only have one :term:`request` factory, +:ref:`changing the 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). -Adding Renderer Globals ------------------------ +A lazy property can be registered to the request object via the +:meth:`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. -Whenever :app:`Pyramid` handles a request to perform a rendering (after a -view with a ``renderer=`` configuration attribute is invoked, or when any -of the methods beginning with ``render`` within the :mod:`pyramid.renderers` -module are called), *renderer globals* can be injected into the *system* -values sent to the renderer. By default, no renderer globals are injected, -and the "bare" system values (such as ``request``, ``context``, and -``renderer_name``) are the only values present in the system dictionary -passed to every renderer. +.. warning:: -A callback that :app:`Pyramid` will call every time a renderer is invoked can -be added by passing a ``renderer_globals_factory`` argument to the -constructor of the :term:`configurator`. This callback can either be a -callable object or a :term:`dotted Python name` representing such a callable. + This will silently override methods and properties from :term:`request + factory` that have the same name. .. code-block:: python :linenos: - def renderer_globals_factory(system): - return {'a': 1} + from pyramid.config import Configurator - config = Configurator( - renderer_globals_factory=renderer_globals_factory) + def total(request, *args): + return sum(args) -Such a callback must accept a single positional argument (notionally named -``system``) which will contain the original system values. It must return a -dictionary of values that will be merged into the system dictionary. See -:ref:`renderer_system_values` for description of the values present in the -system dictionary. + def prop(request): + print("getting the property") + return "the property" -If you're doing imperative configuration, and you'd rather do it after you've -already constructed a :term:`configurator` it can also be registered via the -:meth:`pyramid.config.Configurator.set_renderer_globals_factory` method: + config = Configurator() + config.add_request_method(total) + config.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``: .. code-block:: python :linenos: from pyramid.config import Configurator + from pyramid.decorator import reify + + class ExtraStuff(object): + + def __init__(self, request): + self.request = request + + def total(self, *args): + return sum(args) - def renderer_globals_factory(system): - return {'a': 1} + # use @property if you don't want to cache the result + @reify + def prop(self): + print("getting the property") + return "the property" config = Configurator() - config.set_renderer_globals_factory(renderer_globals_factory) + config.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 + +.. index:: + single: response factory + +.. _changing_the_response_factory: + +Changing the Response Factory +----------------------------- + +.. versionadded:: 1.6 + +Whenever :app:`Pyramid` returns a response from a view, it creates a +:term:`response` object. By default, an instance of the +:class:`pyramid.response.Response` class is created to represent the response +object. + +The factory that :app:`Pyramid` uses to create a response object instance can +be changed by passing a :class:`pyramid.interfaces.IResponseFactory` argument +to the constructor of the :term:`configurator`. This argument can be either a +callable or a :term:`dotted Python name` representing a callable. + +The factory takes a single positional argument, which is a :term:`Request` +object. The argument may be ``None``. + +.. code-block:: python + :linenos: + + from pyramid.response import Response + + class MyResponse(Response): + pass + + config = Configurator(response_factory=lambda r: MyResponse()) -Another mechanism which allows event subscribers to add renderer global values -exists in :ref:`beforerender_event`. +If you're doing imperative configuration and you'd rather do it after you've +already constructed a :term:`configurator`, it can also be registered via the +:meth:`pyramid.config.Configurator.set_response_factory` method: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + from pyramid.response import Response + + class MyResponse(Response): + pass + + config = Configurator() + config.set_response_factory(lambda r: MyResponse()) .. index:: single: before render event + single: adding renderer globals .. _beforerender_event: -Using The Before Render Event +Using the Before Render Event ----------------------------- Subscribers to the :class:`pyramid.events.BeforeRender` event may introspect and modify the set of :term:`renderer globals` before they are passed to a -:term:`renderer`. This event object iself has a dictionary-like interface -that can be used for this purpose. For example: +:term:`renderer`. This event object iself has a dictionary-like interface that +can be used for this purpose. For example: .. code-block:: python - :linenos: + :linenos: from pyramid.events import subscriber from pyramid.events import BeforeRender @@ -252,23 +420,48 @@ that can be used for this purpose. For example: event['mykey'] = 'foo' An object of this type is sent as an event just before a :term:`renderer` is -invoked (but *after* the application-level renderer globals factory added via -:class:`~pyramid.config.Configurator.set_renderer_globals_factory`, if any, -has injected its own keys into the renderer globals dictionary). +invoked. -If a subscriber attempts to add a key that already exist in the renderer +If a subscriber attempts to add a key that already exists in the renderer globals dictionary, a :exc:`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 -:class:`pyramid.events.BeforeRender` subscribers and renderer globals -factories must be unique. +:class:`pyramid.events.BeforeRender` subscribers and renderer globals factories +must be unique. + +The dictionary returned from the view is accessible through the +:attr:`rendering_val` attribute of a :class:`~pyramid.events.BeforeRender` +event. + +Suppose you return ``{'mykey': 'somevalue', 'mykey2': 'somevalue2'}`` from your +view callable, like so: + +.. code-block:: python + :linenos: + + from pyramid.view import view_config + + @view_config(renderer='some_renderer') + def myview(request): + return {'mykey': 'somevalue', 'mykey2': 'somevalue2'} + +:attr:`rendering_val` can be used to access these values from the +:class:`~pyramid.events.BeforeRender` object: + +.. code-block:: python + :linenos: + + from pyramid.events import subscriber + from pyramid.events import BeforeRender + + @subscriber(BeforeRender) + def read_return(event): + # {'mykey': 'somevalue'} is returned from the view + print(event.rendering_val['mykey']) See the API documentation for the :class:`~pyramid.events.BeforeRender` event interface at :class:`pyramid.interfaces.IBeforeRender`. -Another mechanism which allows event subscribers more control when adding -renderer global values exists in :ref:`adding_renderer_globals`. - .. index:: single: response callback @@ -279,8 +472,8 @@ Using Response Callbacks Unlike many other web frameworks, :app:`Pyramid` does not eagerly create a global response object. Adding a :term:`response callback` allows an -application to register an action to be performed against a response object -once it is created, usually in order to mutate it. +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 :meth:`pyramid.request.Request.add_response_callback` method is used to register a response callback. @@ -297,24 +490,23 @@ A response callback is a callable which accepts two positional parameters: response.cache_control.max_age = 360 request.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 :term:`view -callable` is invalid. Response callbacks *are*, however, invoked when a -:term:`exception view` is rendered successfully: in such a case, the -:attr:`request.exception` attribute of the request when it enters a response -callback will be an exception object instead of its default value of -``None``. +No response callback is called if an unhandled exception happens in application +code, or if the response object returned by a :term:`view callable` is invalid. +Response callbacks *are*, however, invoked when a :term:`exception view` is +rendered successfully. In such a case, the :attr:`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 *after* -the :class:`~pyramid.events.NewResponse` event is sent. Errors raised by -response callbacks are not handled specially. They will be propagated to the -caller of the :app:`Pyramid` router application. +(first-to-most-recently-added). All response callbacks are called *before* the +:class:`~pyramid.events.NewResponse` event is sent. Errors raised by response +callbacks are not handled specially. They will be propagated to the caller of +the :app:`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 :class:`~pyramid.events.NewRequest` event). +re-register the callback into every new request (perhaps within a subscriber of +a :class:`~pyramid.events.NewRequest` event). .. index:: single: finished callback @@ -325,59 +517,50 @@ Using Finished Callbacks ------------------------ A :term:`finished callback` is a function that will be called unconditionally -by the :app:`Pyramid` :term:`router` at the very end of request processing. -A finished callback can be used to perform an action at the end of a request +by the :app:`Pyramid` :term:`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 :meth:`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: +A finished callback is a callable which accepts a single positional parameter: +``request``. For example: .. code-block:: python :linenos: - import transaction + import logging - def commit_callback(request): - '''commit or abort the transaction associated with request''' - if request.exception is not None: - transaction.abort() - else: - transaction.commit() - request.add_finished_callback(commit_callback) + log = logging.getLogger(__name__) + + def log_callback(request): + """Log information at the end of request""" + log.debug('Request is finished.') + request.add_finished_callback(log_callback) Finished callbacks are called in the order they're added -(first-to-most-recently-added). Finished callbacks (unlike a -:term:`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 :term:`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. - -It is often necessary to tell whether an exception occurred within -:term:`view callable` code from within a finished callback: in such a case, -the :attr:`request.exception` attribute of the request when it enters a -response callback will be an exception object instead of its default value of -``None``. - -Errors raised by finished callbacks are not handled specially. They -will be propagated to the caller of the :app:`Pyramid` router -application. +(first-to-most-recently-added). Finished callbacks (unlike a :term:`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 :term:`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 :app:`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 :class:`~pyramid.events.NewRequest` event). +re-register the callback into every new request (perhaps within a subscriber of +a :class:`~pyramid.events.NewRequest` event). .. index:: single: traverser @@ -389,17 +572,16 @@ Changing the Traverser The default :term:`traversal` algorithm that :app:`Pyramid` uses is explained in :ref:`traversal_algorithm`. Though it is rarely necessary, this default -algorithm can be swapped out selectively for a different traversal pattern -via configuration. +algorithm can be swapped out selectively for a different traversal pattern via +configuration. .. code-block:: python :linenos: - from pyramid.interfaces import ITraverser - from zope.interface import Interface + from pyramid.config import Configurator from myapp.traversal import Traverser - - config.registry.registerAdapter(Traverser, (Interface,), ITraverser) + config = Configurator() + config.add_traverser(Traverser) In the example above, ``myapp.traversal.Traverser`` is assumed to be a class that implements the following interface: @@ -437,90 +619,208 @@ that implements the following interface: More than one traversal algorithm can be active at the same time. For instance, if your :term:`root factory` returns more than one type of object -conditionally, you could claim that an alternate traverser adapter is ``for`` +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: +used. Otherwise the default traverser would be used. For example: .. code-block:: python :linenos: - from pyramid.interfaces import ITraverser - from zope.interface import Interface from myapp.traversal import Traverser from myapp.resources import MyRoot - - config.registry.registerAdapter(Traverser, (MyRoot,), ITraverser) + from pyramid.config import Configurator + config = Configurator() + config.add_traverser(Traverser, MyRoot) If the above stanza was added to a Pyramid ``__init__.py`` file's ``main`` -function, :app:`Pyramid` would use the ``myapp.traversal.Traverser`` only -when the application :term:`root factory` returned an instance of the +function, :app:`Pyramid` would use the ``myapp.traversal.Traverser`` only when +the application :term:`root factory` returned an instance of the ``myapp.resources.MyRoot`` object. Otherwise it would use the default :app:`Pyramid` traverser to do traversal. .. index:: - single: url generator + single: URL generator .. _changing_resource_url: -Changing How :mod:`pyramid.url.resource_url` Generates a URL ------------------------------------------------------------- +Changing How :meth:`pyramid.request.Request.resource_url` Generates a URL +------------------------------------------------------------------------- When you add a traverser as described in :ref:`changing_the_traverser`, it's -often convenient to continue to use the :func:`pyramid.url.resource_url` API. -However, since the way traversal is done will have been modified, the URLs it -generates by default may be incorrect. +often convenient to continue to use the +:meth:`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 -:func:`~pyramid.url.resource_url` generates a URL for a specific type of -resource by adding a registerAdapter call for -:class:`pyramid.interfaces.IContextURL` to your application: +:meth:`~pyramid.request.Request.resource_url` generates a URL for a specific +type of resource by adding a call to +:meth:`pyramid.config.Configurator.add_resource_url_adapter`. + +For example: .. code-block:: python :linenos: - from pyramid.interfaces import ITraverser - from zope.interface import Interface - from myapp.traversal import URLGenerator + from myapp.traversal import ResourceURLAdapter from myapp.resources import MyRoot - config.registry.registerAdapter(URLGenerator, (MyRoot, Interface), - IContextURL) + config.add_resource_url_adapter(ResourceURLAdapter, MyRoot) -In the above example, the ``myapp.traversal.URLGenerator`` class will be used -to provide services to :func:`~pyramid.url.resource_url` any time the -:term:`context` passed to ``resource_url`` is of class -``myapp.resources.MyRoot``. The second argument in the ``(MyRoot, -Interface)`` tuple represents the type of interface that must be possessed by -the :term:`request` (in this case, any interface, represented by -``zope.interface.Interface``). +In the above example, the ``myapp.traversal.ResourceURLAdapter`` class will be +used to provide services to :meth:`~pyramid.request.Request.resource_url` any +time the :term:`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 -:class:`~pyramid.interfaces.IContextURL` is as follows: +:class:`~pyramid.interfaces.IResourceURL` is as follows: .. code-block:: python :linenos: - from zope.interface import Interface - - class IContextURL(Interface): - """ An adapter which deals with URLs related to a context. + class MyResourceURL(object): + """ An adapter which provides the virtual and physical paths of a + resource """ - def __init__(self, context, request): - """ Accept the context and request """ - - def virtual_root(self): - """ Return the virtual root object related to a request and the - current context""" - - def __call__(self): - """ Return a URL that points to the context """ + def __init__(self, resource, request): + """ Accept the resource and request and set self.physical_path and + self.virtual_path """ + self.virtual_path = some_function_of(resource, request) + self.physical_path = some_other_function_of(resource, request) The default context URL generator is available for perusal as the class -:class:`pyramid.traversal.TraversalContextURL` in the `traversal module -<http://github.com/Pylons/pyramid/blob/master/pyramid/traversal.py>`_ of the +:class:`pyramid.traversal.ResourceURL` in the `traversal module +<https://github.com/Pylons/pyramid/blob/master/pyramid/traversal.py>`_ of the :term:`Pylons` GitHub Pyramid repository. +See :meth:`pyramid.config.Configurator.add_resource_url_adapter` for more +information. + +.. index:: + single: IResponse + single: special view responses + +.. _using_iresponse: + +Changing How Pyramid Treats View Responses +------------------------------------------ + +.. versionadded:: 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 +:meth:`pyramid.config.Configurator.add_response_adapter` or the +:class:`~pyramid.response.response_adapter` decorator. + +Pyramid, in various places, adapts the result of calling a view callable to the +:class:`~pyramid.interfaces.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 +:class:`~pyramid.interfaces.IResponse` interface. Most typically, this will be +an instance of the :class:`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 :term:`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 +:class:`~pyramid.interfaces.IResponse`. + +For example, if you'd like to allow view callables to return bare string +objects (without requiring a :term:`renderer` to convert a string to a response +object), you can register an adapter which converts the string to a Response: + +.. code-block:: python + :linenos: + + from pyramid.response import Response + + def string_response_adapter(s): + response = Response(s) + return response + + # config is an instance of pyramid.config.Configurator + + config.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: + +.. code-block:: python + :linenos: + + from pyramid.response import Response + + class SimpleResponse(object): + def __init__(self, body): + self.body = body + + def simple_response_adapter(simple_response): + response = Response(simple_response.body) + return response + + # config is an instance of pyramid.config.Configurator + + config.add_response_adapter(simple_response_adapter, SimpleResponse) + +If you want to implement your own Response object instead of using the +:class:`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 +:class:`pyramid.interfaces.IResponse` and you'll have to ensure that it uses +``zope.interface.implementer(IResponse)`` as a class decorator. + +.. code-block:: python + :linenos: + + from pyramid.interfaces import IResponse + from zope.interface import implementer + + @implementer(IResponse) + class MyResponse(object): + # ... an implementation of every method and attribute + # documented in IResponse should follow ... + +When an alternate response object implementation is returned by a view +callable, if that object asserts that it implements +:class:`~pyramid.interfaces.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.Response`` (as opposed to +:class:`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.Response`` simply returns the response object. + +Instead of using :meth:`pyramid.config.Configurator.add_response_adapter`, you +can use the :class:`pyramid.response.response_adapter` decorator: + +.. code-block:: python + :linenos: + + from pyramid.response import Response + from pyramid.response import response_adapter + + @response_adapter(str) + def string_response_adapter(s): + response = Response(s) + return response + +The above example, when scanned, has the same effect as: + +.. code-block:: python + + config.add_response_adapter(string_response_adapter, str) + +The :class:`~pyramid.response.response_adapter` decorator will have no effect +until activated by a :term:`scan`. + .. index:: single: view mapper @@ -535,31 +835,29 @@ callables by employing a :term:`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 :term:`view -callable` object. The returned callable should itself return another -callable which can be called with the "internal calling protocol" ``(context, +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 itself +- by setting a ``__view_mapper__`` attribute (which is the view mapper object) + on the view callable itself -- by passing the mapper object to - :meth:`pyramid.config.Configurator.add_view` (or its declarative/decorator - equivalents) as the ``mapper`` argument. +- by passing the mapper object to :meth:`pyramid.config.Configurator.add_view` + (or its declarative and decorator equivalents) as the ``mapper`` argument -- by registering a *default* view mapper. +- by 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 :term:`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. +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 :term:`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. .. code-block:: python :linenos: @@ -575,7 +873,7 @@ as keyword arguments. def wrapper(context, request): matchdict = request.matchdict.copy() matchdict.pop('action', None) - inst = view() + inst = view(request) meth = getattr(inst, attr) return meth(**matchdict) return wrapper @@ -590,10 +888,10 @@ A user might make use of these framework components like so: # user application - from webob import Response + from pyramid.response import Response from pyramid.config import Configurator import pyramid_handlers - from paste.httpserver import serve + from wsgiref.simple_server import make_server class MyController(BaseController): def index(self, id): @@ -604,14 +902,15 @@ A user might make use of these framework components like so: config.include(pyramid_handlers) config.add_handler('one', '/{id}', MyController, action='index') config.add_handler('two', '/{action}/{id}', MyController) - serve(config.make_wsgi_app()) + server.make_server('0.0.0.0', 8080, config.make_wsgi_app()) + server.serve_forever() The :meth:`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 :meth:`~pyramid.config.Configuration.add_view`. +A *single* view registration can use a view mapper by passing the mapper as the +``mapper`` argument to :meth:`~pyramid.config.Configurator.add_view`. .. index:: single: configuration decorator @@ -621,14 +920,14 @@ the ``mapper`` argument to :meth:`~pyramid.config.Configuration.add_view`. Registering Configuration Decorators ------------------------------------ -Decorators such as :class:`~pyramid.view.view_config` don't change the -behavior of the functions or classes they're decorating. Instead, when a -:term:`scan` is performed, a modified version of the function or class is -registered with :app:`Pyramid`. +Decorators such as :class:`~pyramid.view.view_config` don't change the behavior +of the functions or classes they're decorating. Instead when a :term:`scan` is +performed, a modified version of the function or class is registered with +:app:`Pyramid`. You may wish to have your own decorators that offer such behaviour. This is -possible by using the :term:`Venusian` package in the same way that it is -used by :app:`Pyramid`. +possible by using the :term:`Venusian` package in the same way that it is used +by :app:`Pyramid`. By way of example, let's suppose you want to write a decorator that registers the function it wraps with a :term:`Zope Component Architecture` "utility" @@ -638,8 +937,7 @@ 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 :term:`Venusian`, the decorator could be written as -follows: +However, using :term:`Venusian`, the decorator could be written as follows: .. code-block:: python :linenos: @@ -661,43 +959,772 @@ follows: venusian.attach(wrapped, self.register) return wrapped -This decorator could then be used to register functions throughout -your code: +This decorator could then be used to register functions throughout your code: .. code-block:: python :linenos: @registerFunction('/some/path') def my_function(): - do_stuff() + do_stuff() -However, the utility would only be looked up when a :term:`scan` was -performed, enabling you to set up the utility in advance: +However, the utility would only be looked up when a :term:`scan` was performed, +enabling you to set up the utility in advance: .. code-block:: python :linenos: - from paste.httpserver import serve + from zope.interface import implementer + + from wsgiref.simple_server import make_server from pyramid.config import Configurator from mypackage.interfaces import IMyUtility + @implementer(IMyUtility) class UtilityImplementation: - implements(IMyUtility) - def __init__(self): - self.registrations = {} + self.registrations = {} def register(self, path, callable_): - self.registrations[path] = callable_ + self.registrations[path] = callable_ if __name__ == '__main__': config = Configurator() config.registry.registerUtility(UtilityImplementation()) config.scan() app = config.make_wsgi_app() - serve(app, host='0.0.0.0') + server = make_server('0.0.0.0', 8080, app) + server.serve_forever() For full details, please read the `Venusian documentation <http://docs.repoze.org/venusian>`_. +.. _registering_tweens: + +Registering Tweens +------------------ + +.. versionadded:: 1.2 + Tweens + +A :term:`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 :app:`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 :term:`WSGI` :term:`middleware`, but they have the benefit of +running in a context in which they have access to the Pyramid :term:`request`, +:term:`response`, and :term:`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 :term:`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 +:term:`request` created by Pyramid's router when it receives a WSGI request. A +tween should return a :term:`response`, usually the one generated by the +downstream Pyramid application. + +You can write the tween factory as a simple closure-returning function: + +.. code-block:: python + :linenos: + + def simple_tween_factory(handler, registry): + # one-time configuration code goes here + + def simple_tween(request): + # code to be executed for each request before + # the actual application code goes here + + response = handler(request) + + # code to be executed for each request after + # the actual application code goes here + + return response + + return simple_tween + +Alternatively, the tween factory can be a class with the ``__call__`` magic +method: + +.. code-block:: python + :linenos: + + class simple_tween_factory(object): + def __init__(self, handler, registry): + self.handler = handler + self.registry = registry + + # one-time configuration code goes here + + def __call__(self, request): + # code to be executed for each request before + # the actual application code goes here + + response = self.handler(request) + + # code to be executed for each request after + # the actual application code goes here + + 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: + +.. code-block:: python + :linenos: + + # in a module named myapp.tweens + + import time + from pyramid.settings import asbool + import logging + + log = logging.getLogger(__name__) + + def timing_tween_factory(handler, registry): + if asbool(registry.settings.get('do_timing')): + # if timing support is enabled, return a wrapper + def timing_tween(request): + start = time.time() + try: + response = handler(request) + finally: + end = time.time() + log.debug('The request took %s seconds' % + (end - start)) + return response + return timing_tween + # if timing support is not enabled, return the original + # handler + 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 :meth:`pyramid.config.Configurator.add_tween` method +using its :term:`dotted Python name`. + +Here's an example of registering a tween factory as an "implicit" tween in a +Pyramid application: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + config = Configurator() + config.add_tween('myapp.tweens.timing_tween_factory') + +Note that you must use a :term:`dotted Python name` as the first argument to +:meth:`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 :term:`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 :meth:`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 +:ref:`explicit_tween_ordering` for more information about explicit tween +ordering. + +If more than one call to :meth:`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: + +.. code-block:: python + :linenos: + + from pyramid.config import Configurator + + config = Configurator() + config.add_tween('myapp.tween_factory1') + config.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 +:meth:`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 +:meth:`~pyramid.config.Configurator.add_tween`. These hints are only used when +an explicit tween ordering is not used. See :ref:`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 :term:`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 :attr:`pyramid.tweens.MAIN`, + :attr:`pyramid.tweens.INGRESS`, or :attr:`pyramid.tweens.EXCVIEW`, or + +- an 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 +:meth:`~pyramid.config.Configurator.add_tween` will attempt to place the tween +factory represented by ``myapp.tween_factory`` directly "above" (in ``ptweens`` +order) the main Pyramid request handler. + +.. code-block:: python + :linenos: + + import pyramid.tweens + + config.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 +:meth:`~pyramid.config.Configurator.add_tween` will attempt to place this tween +factory "above" the main handler but "below" a separately added tween factory: + +.. code-block:: python + :linenos: + + import pyramid.tweens + + config.add_tween('myapp.tween_factory1', + over=pyramid.tweens.MAIN) + config.add_tween('myapp.tween_factory2', + over=pyramid.tweens.MAIN, + 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 +compatibilty 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: + +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 :meth:`~pyramid.config.Configurator.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 +:meth:`~pyramid.config.Configurator.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: + +.. code-block:: ini + :linenos: + + [app:main] + use = egg:MyApp + pyramid.reload_templates = true + pyramid.debug_authorization = false + pyramid.debug_notfound = false + pyramid.debug_routematch = false + pyramid.debug_templates = true + pyramid.tweens = myapp.my_cool_tween_factory + pyramid.tweens.excview_tween_factory + +In the above configuration, calls made during configuration to +:meth:`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 :term:`dotted Python name` +which points to a tween factory) instead of any tween factories added via +:meth:`pyramid.config.Configurator.add_tween`. The *first* tween factory in +the ``pyramid.tweens`` list will be used as the producer of the effective +:app:`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". + +.. note:: + + Pyramid's own :term:`exception view` handling logic is implemented as a + tween factory function: :func:`pyramid.tweens.excview_tween_factory`. If + Pyramid exception view handling is desired, and tween factories are + specified via the ``pyramid.tweens`` configuration setting, the + :func:`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 +:meth:`pyramid.config.Configurator.commit` between calls to +:meth:`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 implict +and explicit tween chains used by an application. See +:ref:`displaying_tweens`. + +.. _registering_thirdparty_predicates: + +Adding a Third Party View, Route, or Subscriber Predicate +--------------------------------------------------------- + +.. versionadded:: 1.4 + +.. _view_and_route_predicates: + +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``: + +.. code-block:: python + + @view_config(request_method='POST') + def someview(request): + ... + +Likewise, a similar predicate can be used as a *route* predicate: + +.. code-block:: python + + config.add_route('name', '/foo', request_method='POST') + +Many other built-in predicates exists (``request_param``, and others). You can +add third-party predicates to the list of available predicates by using one of +:meth:`pyramid.config.Configurator.add_view_predicate` or +:meth:`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: + +.. code-block:: python + + 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: + +.. code-block:: python + :linenos: + + @view_config(content_type='File') + def aview(request): ... + +The first argument to :meth:`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 :term:`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: + +.. code-block:: python + :linenos: + + class ContentTypePredicate(object): + def __init__(self, val, config): + self.val = val + + def text(self): + return 'content_type = %s' % (self.val,) + + phash = text + + def __call__(self, context, request): + return getattr(context, 'content_type', None) == self.val + +The constructor of a predicate factory takes two arguments: ``val`` and +``config``. 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, ``config``, will be the Configurator instance +at the time of configuration. + +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 of a predicate factory must accept a resource +(``context``) and a request, and must return ``True`` or ``False``. It is the +"meat" of the predicate. + +You can use the same predicate factory as both a view predicate and as a route +predicate, but you'll need to call ``add_view_predicate`` and +``add_route_predicate`` separately with the same factory. + +.. _subscriber_predicates: + +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 single ``event`` + object instead of a ``context`` and a ``request``. + +- 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 :class:`pyramid.events.NewRequest` +event type. + +.. code-block:: python + :linenos: + + class RequestPathStartsWith(object): + def __init__(self, val, config): + self.val = val + + def text(self): + return 'path_startswith = %s' % (self.val,) + + phash = text + + def __call__(self, event): + return event.request.path.startswith(self.val) + +Once you've created a subscriber predicate, it may registered via +:meth:`pyramid.config.Configurator.add_subscriber_predicate`. For example: + +.. code-block:: python + + config.add_subscriber_predicate( + 'request_path_startswith', RequestPathStartsWith) + +Once a subscriber predicate is registered, you can use it in a call to +:meth:`pyramid.config.Configurator.add_subscriber` or to +:class:`pyramid.events.subscriber`. Here's an example of using the previously +registered ``request_path_startswith`` predicate in a call to +:meth:`~pyramid.config.Configurator.add_subscriber`: + +.. code-block:: python + :linenos: + + # define a subscriber in your code + + def yosubscriber(event): + event.request.yo = 'YO!' + + # and at configuration time + + config.add_subscriber(yosubscriber, NewRequest, + request_path_startswith='/add_yo') + +Here's the same subscriber/predicate/event-type combination used via +:class:`~pyramid.events.subscriber`. + +.. code-block:: python + :linenos: + + from pyramid.events import subscriber + + @subscriber(NewRequest, request_path_startswith='/add_yo') + def yosubscriber(event): + 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 +:class:`pyramid.events.NewRequest` and :class:`pyramid.events.ContextFound` +events, but it cannot be used with subscribers registered for +:class:`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. + + +.. index:: + single: view derivers + +.. _view_derivers: + +View Derivers +------------- + +.. versionadded:: 1.7 + +Every URL processed by :app:`Pyramid` is matched against a custom view +pipeline. See :ref:`router_chapter` for how this works. The view pipeline +itself is built from the user-supplied :term:`view callable`, which is then +composed with :term:`view derivers <view deriver>`. 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 :meth:`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 :term:`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 :app:`Pyramid` will automatically +apply to any view. Below they are defined in order from furthest to closest to +the user-defined :term:`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 via + :meth:`pyramid.config.Configurator.set_default_permission`. + + 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 both the ``require_csrf`` view option and the + ``pyramid.require_default_csrf`` setting are disabled. + +``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 the ``pyramid.prevent_http_cache`` setting + is enabled or the ``http_cache`` option is ``None``. + +``decorated_view`` + + Wraps the view with the decorators from the ``decorator`` option. + +``rendered_view`` + + Adapts the result of the :term:`view callable` into a :term:`response` + object. Below this point the result may be any Python object. + +``mapped_view`` + + Applies the :term:`view mapper` defined by the ``mapper`` option or the + application's default view mapper to the :term:`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. + +.. warning:: + + 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 :term:`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 :term:`view +deriver`, you should create a callable that conforms to the +:class:`pyramid.interfaces.IViewDeriver` interface, and then register it with +your application using :meth:`pyramid.config.Configurator.add_view_deriver`. +For example, below is a callable that can provide timing information for the +view pipeline: + +.. code-block:: python + :linenos: + + import time + + def timing_view(view, info): + if info.options.get('timed'): + def wrapper_view(context, request): + start = time.time() + response = view(context, request) + end = time.time() + response.headers['X-View-Performance'] = '%.3f' % (end - start,) + return response + return wrapper_view + return view + + timing_view.options = ('timed',) + + config.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: + +.. code-block:: python + :linenos: + + @view_config(route_name='home') + def home(request): + return Response('Home') + +But this view *will* have timing information added to the response headers: + +.. code-block:: python + :linenos: + + @view_config(route_name='home', timed=True) + def home(request): + return Response('Home') + +View derivers are unique in that they have access to most of the options +passed to :meth:`pyramid.config.Configurator.add_view` in order to decide what +to do, and they have a chance to affect every view in the application. + +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 :class:`pyramid.exceptions.ConfigurationError` will be raised. This may +be used to define fallback constraints if another deriver is missing. + +Two sentinel values exist, :attr:`pyramid.viewderivers.INGRESS` and +:attr:`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 +:term:`view mapper` is intimately tied to the signature of the user-defined +:term:`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 +:class:`pyramid.interfaces.IViewDeriverInfo` object passed to every view +deriver. + +.. warning:: + + 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. |
