diff options
| author | Chris McDonough <chrism@agendaless.com> | 2010-09-13 02:39:26 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2010-09-13 02:39:26 +0000 |
| commit | 81d3b5412b43e4a104d0118ad4147402d787220e (patch) | |
| tree | bcf8b75f49b9015286a6439e67d71ad45f114292 /docs | |
| parent | ad6a6706391c60dbdb66073caff1306b771da0bd (diff) | |
| download | pyramid-81d3b5412b43e4a104d0118ad4147402d787220e.tar.gz pyramid-81d3b5412b43e4a104d0118ad4147402d787220e.tar.bz2 pyramid-81d3b5412b43e4a104d0118ad4147402d787220e.zip | |
Features
--------
- A ``request.matched_route`` attribute is now added to the request
when a route has matched. Its value is the "route" object that
matched (see the ``IRoute`` interface within
``repoze.bfg.interfaces`` API documentation for the API of a route
object).
- The ``exception`` attribute of the request is now set slightly
earlier and in a slightly different set of scenarios, for benefit of
"finished callbacks" and "response callbacks". In previous
versions, the ``exception`` attribute of the request was not set at
all if an exception view was not found. In this version, the
``request.exception`` attribute is set immediately when an exception
is caught by the router, even if an exception view could not be
found.
Backwards Incompatibilities
---------------------------
- The router no longer sets the value ``wsgiorg.routing_args`` into
the environ when a route matches. The value used to be something
like ``((), matchdict)``. This functionality was only ever
obliquely referred to in change logs; it was never documented as an
API.
- The ``exception`` attribute of the request now defaults to ``None``.
In prior versions, the ``request.exception`` attribute did not exist
if an exception was not raised by user code during request
processing; it only began existence once an exception view was
found.
Deprecations
------------
- References to the WSGI environment values ``bfg.routes.matchdict``
and ``bfg.routes.route`` were removed from documentation. These
will stick around internally for several more releases, but it is
``request.matchdict`` and ``request.matched_route`` are now the
"official" way to obtain the matchdict and the route object which
resulted in the match.
Documentation
-------------
- Added two sections to the "Hooks" chapter of the documentation:
"Using Response Callbacks" and "Using Finished Callbacks".
- Added documentation of the ``request.exception`` attribute to the
``repoze.bfg.request.Request`` API documentation.
- Added glossary entries for "response callback" and "finished
callback".
- The "Request Processing" narrative chapter has been updated to note
finished and response callback steps.
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/api/request.rst | 15 | ||||
| -rw-r--r-- | docs/glossary.rst | 10 | ||||
| -rw-r--r-- | docs/narr/hooks.rst | 105 | ||||
| -rw-r--r-- | docs/narr/router.rst | 85 |
4 files changed, 188 insertions, 27 deletions
diff --git a/docs/api/request.rst b/docs/api/request.rst index 86202b830..08d8d76e1 100644 --- a/docs/api/request.rst +++ b/docs/api/request.rst @@ -73,6 +73,21 @@ :ref:`vhosting_chapter` for more information about virtual roots. + .. attribute:: exception + + If an exception was raised by a :term:`root factory` or a + :term:`view callable`, or at various other points where + :mod:`repoze.bfg` executes user-defined code during the + processing of a request, the exception object which was caught + will be available as the ``exception`` attribute of the request + within a :term:`exception view`, a :term:`response callback` or a + :term:`finished callback`. If no exception occurred, the value + of ``request.exception`` will be ``None`` within response and + finished callbacks. + + .. note:: The exception attribute is new in :mod:`repoze.bfg` + 1.3. + .. autofunction:: make_request_ascii diff --git a/docs/glossary.rst b/docs/glossary.rst index a703f187c..158af2230 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -774,3 +774,13 @@ Glossary Values injected as names into a renderer based on application policy. See :ref:`adding_renderer_globals` for more information. + + response callback + A user-defined callback executed by the :term:`router` at a + point after a :term:`response` object is successfully created. + See :ref:`using_response_callbacks`. + + finished callback + A user-defined callback executed by the :term:`router` + unconditionally at the very end of request processing . See + :ref:`using_finished_callbacks`. diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index b00fc6727..965a4c77f 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -461,6 +461,111 @@ method: config = Configurator() config.set_renderer_globals_factory(renderer_globals_factory) +.. _using_response_callbacks: + +Using Response Callbacks +------------------------ + +Unlike many other web frameworks, :mod:`repoze.bfg` 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. + +The :meth:`repoze.bfg.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: + +.. code-block:: python + :linenos: + + def cache_callback(request, response): + """Set the cache_control max_age for the response""" + if request.exception is not None: + 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``. + +Response callbacks are called in the order they're added +(first-to-most-recently-added). All response callbacks are called +*before* the :class:`repoze.bfg.interfaces.INewResponse` event is +sent. Errors raised by response callbacks are not handled specially. +They will be propagated to the caller of the :mod:`repoze.bfg` 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:`repoze.bfg.interfaces.INewRequest` event). + +.. _using_finished_callbacks: + +Using Finished Callbacks +------------------------ + +A :term:`finished callback` is a function that will be called +unconditionally by the :mod:`repoze.bfg` :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:`repoze.bfg.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: + +.. code-block:: python + :linenos: + + import transaction + + 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) + +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 :mod:`repoze.bfg` 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:`repoze.bfg.interfaces.INewRequest` event). + .. _registering_configuration_decorators: Registering Configuration Decorators diff --git a/docs/narr/router.rst b/docs/narr/router.rst index af8f057b0..8bb76b366 100644 --- a/docs/narr/router.rst +++ b/docs/narr/router.rst @@ -26,30 +26,37 @@ processing? #. A :term:`request` object is created based on the WSGI environment. +#. The :term:`registry` associated with the application and the + :term:`request` object created in the last step are pushed on to the + :term:`thread local` stack that :mod:`repoze.bfg` uses to allow the + functions named :func:`repoze.bfg.threadlocal.get_current_request` + and :func:`repoze.bfg.threadlocal.get_current_registry` to work. + #. A :class:`repoze.bfg.interfaces.INewRequest` :term:`event` is sent to any subscribers. #. If any :term:`route` has been defined within application configuration, the :mod:`repoze.bfg` :term:`router` calls a :term:`URL dispatch` "route mapper." The job of the mapper is to - examine the ``PATH_INFO`` implied by the request to determine - whether any user-defined :term:`route` pattern matches the current - WSGI environment. The :term:`router` passes the request as an - argument to the mapper. - -#. If any route matches, the WSGI environment is mutated; a - ``bfg.routes.route`` key and a ``bfg.routes.matchdict`` are added - to the WSGI environment, and an attribute named ``matchdict`` is - added to the request. A root object associated with the route - found is also generated. If the :term:`route configuration` - which matched has an associated a ``factory`` argument, this - factory is used to generate the root object, otherwise a default - :term:`root factory` is used. + examine the request to determine whether any user-defined + :term:`route` matches the current WSGI environment. The + :term:`router` passes the request as an argument to the mapper. + +#. If any route matches, the request is mutated; a ``matchdict`` and + ``matched_route`` attributes are added to the request object; the + former contains a dictionary representign the matched dynamic + elements of the request's ``PATH_INFO`` value, the latter contains + the :class:`repoze.bfg.interfaces.IRoute` object representing the + route which matched. The root object associated with the route + found is also generated: if the :term:`route configuration` which + matched has an associated a ``factory`` argument, this factory is + used to generate the root object, otherwise a default :term:`root + factory` is used. #. If a route match was *not* found, and a ``root_factory`` argument was passed to the :term:`Configurator` constructor, that callable is used to generate the root object. If the ``root_factory`` - argument passed to the Configurator constructor is ``None``, a + argument passed to the Configurator constructor was ``None``, a default root factory is used to generate a root object. #. The :mod:`repoze.bfg` router calls a "traverser" function with the @@ -75,11 +82,12 @@ processing? doesn't exist for this combination of objects (based on the type of the context, the type of the request, and the value of the view name, and any :term:`predicate` attributes applied to the view - configuration), :mod:`repoze.bfg` uses a "not found" view callable - to generate a response, and returns that response. + configuration), :mod:`repoze.bfg` raises a + :class:`repoze.bfg.exceptions.NotFound` exception, which is meant + to be caught by a surrounding exception handler. -#. If a view callable was found, :mod:`repoze.bfg` calls the view - function. +#. If a view callable was found, :mod:`repoze.bfg` attempts to call + the view function. #. If an :term:`authorization policy` is in use, and the view was protected by a :term:`permission`, :mod:`repoze.bfg` passes the @@ -88,15 +96,38 @@ processing? requesting user, based on credential information in the request and security information attached to the context. If it returns ``True``, :mod:`repoze.bfg` calls the view callable to obtain a - response. If it returns ``False``, it uses a :term:`forbidden - view` callable to generate a response. - -#. A :class:`repoze.bfg.interfaces.INewResponse` :term:`event` is sent - to any subscribers. - -#. The response object's ``app_iter``, ``status``, and ``headerlist`` - attributes are used to generate a WSGI response. The response is - sent back to the upstream WSGI server. + response. If it returns ``False``, it raises a + :class:`repoze.bfg.exceptions.Forbidden` exception, which is meant + to be called by a surrounding exception handler. + +#. If any exception was raised within a :term:`root factory`, by + :term:`traversal`, by a :term:`view callable` or by + :mod:`repoze.bfg` itself (such as when it raises + :class:`repoze.bfg.exceptions.NotFound` or + :class:`repoze.bfg.exceptions.Forbidden`), the router catches the + exception, and attaches it to the request as the ``exception`` + attribute. It then attempts to find a :term:`exception view` for + the exception that was caught. If it finds an exception view + callable, that callable is called, and is presumed to generate a + response. If an :term:`exception view` that matches the exception + cannot be found, the exception is reraised. + +#. The following steps occur only when a :term:`response` could be + successfully generated by a normal :term:`view callable` or an + :term:`exception view` callable. :mod:`repoze.bfg` will attempt to + execute any :term:`response callback` functions attached via + :meth:`repoze.bfg.request.Request.add_response_callback`. A + :class:`repoze.bfg.interfaces.INewResponse` :term:`event` is then + sent to any subscribers. The response object's ``app_iter``, + ``status``, and ``headerlist`` attributes are then used to generate + a WSGI response. The response is sent back to the upstream WSGI + server. + +#. :mod:`repoze.bfg` will attempt to execute any :term:`finished + callback` functions attached via + :meth:`repoze.bfg.request.Request.add_finished_callback`. + +#. The :term:`thread local` stack is popped. .. image:: router.png |
