summaryrefslogtreecommitdiff
path: root/docs/narr/subrequest.rst
diff options
context:
space:
mode:
authorChristoph Zwerschke <cito@online.de>2016-04-19 20:07:12 +0200
committerChristoph Zwerschke <cito@online.de>2016-04-19 20:07:12 +0200
commit3629c49e46207ff5162a82883c14937e6ef4c186 (patch)
tree1306181202cb8313f16080789f5b9ab1eeb61d53 /docs/narr/subrequest.rst
parent804ba0b2f434781e77d2b5191f1cd76a490f6610 (diff)
parent6c16fb020027fac47e4d2e335cd9e264dba8aa3b (diff)
downloadpyramid-3629c49e46207ff5162a82883c14937e6ef4c186.tar.gz
pyramid-3629c49e46207ff5162a82883c14937e6ef4c186.tar.bz2
pyramid-3629c49e46207ff5162a82883c14937e6ef4c186.zip
Merge remote-tracking branch 'refs/remotes/Pylons/master'
Diffstat (limited to 'docs/narr/subrequest.rst')
-rw-r--r--docs/narr/subrequest.rst331
1 files changed, 331 insertions, 0 deletions
diff --git a/docs/narr/subrequest.rst b/docs/narr/subrequest.rst
new file mode 100644
index 000000000..7c847de50
--- /dev/null
+++ b/docs/narr/subrequest.rst
@@ -0,0 +1,331 @@
+.. index::
+ single: subrequest
+
+.. _subrequest_chapter:
+
+Invoking a Subrequest
+=====================
+
+.. versionadded:: 1.4
+
+:app:`Pyramid` allows you to invoke a subrequest at any point during the
+processing of a request. Invoking a subrequest allows you to obtain a
+:term:`response` object from a view callable within your :app:`Pyramid`
+application while you're executing a different view callable within the same
+application.
+
+Here's an example application which uses a subrequest:
+
+.. code-block:: python
+ :linenos:
+
+ from wsgiref.simple_server import make_server
+ from pyramid.config import Configurator
+ from pyramid.request import Request
+
+ def view_one(request):
+ subreq = Request.blank('/view_two')
+ response = request.invoke_subrequest(subreq)
+ return response
+
+ def view_two(request):
+ request.response.body = 'This came from view_two'
+ return request.response
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.add_route('one', '/view_one')
+ config.add_route('two', '/view_two')
+ config.add_view(view_one, route_name='one')
+ config.add_view(view_two, route_name='two')
+ app = config.make_wsgi_app()
+ server = make_server('0.0.0.0', 8080, app)
+ server.serve_forever()
+
+When ``/view_one`` is visted in a browser, the text printed in the browser pane
+will be ``This came from view_two``. The ``view_one`` view used the
+:meth:`pyramid.request.Request.invoke_subrequest` API to obtain a response from
+another view (``view_two``) within the same application when it executed. It
+did so by constructing a new request that had a URL that it knew would match
+the ``view_two`` view registration, and passed that new request along to
+:meth:`pyramid.request.Request.invoke_subrequest`. The ``view_two`` view
+callable was invoked, and it returned a response. The ``view_one`` view
+callable then simply returned the response it obtained from the ``view_two``
+view callable.
+
+Note that it doesn't matter if the view callable invoked via a subrequest
+actually returns a *literal* Response object. Any view callable that uses a
+renderer or which returns an object that can be interpreted by a response
+adapter when found and invoked via
+:meth:`pyramid.request.Request.invoke_subrequest` will return a Response
+object:
+
+.. code-block:: python
+ :linenos:
+ :emphasize-lines: 11
+
+ from wsgiref.simple_server import make_server
+ from pyramid.config import Configurator
+ from pyramid.request import Request
+
+ def view_one(request):
+ subreq = Request.blank('/view_two')
+ response = request.invoke_subrequest(subreq)
+ return response
+
+ def view_two(request):
+ return 'This came from view_two'
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.add_route('one', '/view_one')
+ config.add_route('two', '/view_two')
+ config.add_view(view_one, route_name='one')
+ config.add_view(view_two, route_name='two', renderer='string')
+ app = config.make_wsgi_app()
+ server = make_server('0.0.0.0', 8080, app)
+ server.serve_forever()
+
+Even though the ``view_two`` view callable returned a string, it was invoked in
+such a way that the ``string`` renderer associated with the view registration
+that was found turned it into a "real" response object for consumption by
+``view_one``.
+
+Being able to unconditionally obtain a response object by invoking a view
+callable indirectly is the main advantage to using
+:meth:`pyramid.request.Request.invoke_subrequest` instead of simply importing a
+view callable and executing it directly. Note that there's not much advantage
+to invoking a view using a subrequest if you *can* invoke a view callable
+directly. Subrequests are slower and are less convenient if you actually do
+want just the literal information returned by a function that happens to be a
+view callable.
+
+Note that, by default, if a view callable invoked by a subrequest raises an
+exception, the exception will be raised to the caller of
+:meth:`~pyramid.request.Request.invoke_subrequest` even if you have a
+:term:`exception view` configured:
+
+.. code-block:: python
+ :linenos:
+ :emphasize-lines: 11-16
+
+ from wsgiref.simple_server import make_server
+ from pyramid.config import Configurator
+ from pyramid.request import Request
+
+ def view_one(request):
+ subreq = Request.blank('/view_two')
+ response = request.invoke_subrequest(subreq)
+ return response
+
+ def view_two(request):
+ raise ValueError('foo')
+
+ def excview(request):
+ request.response.body = b'An exception was raised'
+ request.response.status_int = 500
+ return request.response
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.add_route('one', '/view_one')
+ config.add_route('two', '/view_two')
+ config.add_view(view_one, route_name='one')
+ config.add_view(view_two, route_name='two', renderer='string')
+ config.add_view(excview, context=Exception)
+ app = config.make_wsgi_app()
+ server = make_server('0.0.0.0', 8080, app)
+ server.serve_forever()
+
+When we run the above code and visit ``/view_one`` in a browser, the
+``excview`` :term:`exception view` will *not* be executed. Instead, the call
+to :meth:`~pyramid.request.Request.invoke_subrequest` will cause a
+:exc:`ValueError` exception to be raised and a response will never be
+generated. We can change this behavior; how to do so is described below in our
+discussion of the ``use_tweens`` argument.
+
+.. index::
+ pair: subrequest; use_tweens
+
+Subrequests with Tweens
+-----------------------
+
+The :meth:`pyramid.request.Request.invoke_subrequest` API accepts two
+arguments: a required positional argument ``request``, and an optional keyword
+argument ``use_tweens`` which defaults to ``False``.
+
+The ``request`` object passed to the API must be an object that implements the
+Pyramid request interface (such as a :class:`pyramid.request.Request`
+instance). If ``use_tweens`` is ``True``, the request will be sent to the
+:term:`tween` in the tween stack closest to the request ingress. If
+``use_tweens`` is ``False``, the request will be sent to the main router
+handler, and no tweens will be invoked.
+
+In the example above, the call to
+:meth:`~pyramid.request.Request.invoke_subrequest` will always raise an
+exception. This is because it's using the default value for ``use_tweens``,
+which is ``False``. Alternatively, you can pass ``use_tweens=True`` to ensure
+that it will convert an exception to a Response if an :term:`exception view` is
+configured, instead of raising the exception. This is because exception views
+are called by the exception view :term:`tween` as described in
+:ref:`exception_views` when any view raises an exception.
+
+We can cause the subrequest to be run through the tween stack by passing
+``use_tweens=True`` to the call to
+:meth:`~pyramid.request.Request.invoke_subrequest`, like this:
+
+.. code-block:: python
+ :linenos:
+ :emphasize-lines: 7
+
+ from wsgiref.simple_server import make_server
+ from pyramid.config import Configurator
+ from pyramid.request import Request
+
+ def view_one(request):
+ subreq = Request.blank('/view_two')
+ response = request.invoke_subrequest(subreq, use_tweens=True)
+ return response
+
+ def view_two(request):
+ raise ValueError('foo')
+
+ def excview(request):
+ request.response.body = b'An exception was raised'
+ request.response.status_int = 500
+ return request.response
+
+ if __name__ == '__main__':
+ config = Configurator()
+ config.add_route('one', '/view_one')
+ config.add_route('two', '/view_two')
+ config.add_view(view_one, route_name='one')
+ config.add_view(view_two, route_name='two', renderer='string')
+ config.add_view(excview, context=Exception)
+ app = config.make_wsgi_app()
+ server = make_server('0.0.0.0', 8080, app)
+ server.serve_forever()
+
+In the above case, the call to ``request.invoke_subrequest(subreq)`` will not
+raise an exception. Instead, it will retrieve a "500" response from the
+attempted invocation of ``view_two``, because the tween which invokes an
+exception view to generate a response is run, and therefore ``excview`` is
+executed.
+
+This is one of the major differences between specifying the ``use_tweens=True``
+and ``use_tweens=False`` arguments to
+:meth:`~pyramid.request.Request.invoke_subrequest`. ``use_tweens=True`` may
+also imply invoking a transaction commit or abort for the logic executed in the
+subrequest if you've got ``pyramid_tm`` in the tween list, injecting debug HTML
+if you've got ``pyramid_debugtoolbar`` in the tween list, and other
+tween-related side effects as defined by your particular tween list.
+
+The :meth:`~pyramid.request.Request.invoke_subrequest` function also
+unconditionally does the following:
+
+- It manages the threadlocal stack so that
+ :func:`~pyramid.threadlocal.get_current_request` and
+ :func:`~pyramid.threadlocal.get_current_registry` work during a request (they
+ will return the subrequest instead of the original request).
+
+- It adds a ``registry`` attribute and an ``invoke_subrequest`` attribute (a
+ callable) to the request object to which it is handed.
+
+- It sets request extensions (such as those added via
+ :meth:`~pyramid.config.Configurator.add_request_method` or
+ :meth:`~pyramid.config.Configurator.set_request_property`) on the subrequest
+ object passed as ``request``.
+
+- It causes a :class:`~pyramid.events.NewRequest` event to be sent at the
+ beginning of request processing.
+
+- It causes a :class:`~pyramid.events.ContextFound` event to be sent when a
+ context resource is found.
+
+- It ensures that the user implied by the request passed in has the necessary
+ authorization to invoke the view callable before calling it.
+
+- It calls any :term:`response callback` functions defined within the
+ subrequest's lifetime if a response is obtained from the Pyramid application.
+
+- It causes a :class:`~pyramid.events.NewResponse` event to be sent if a
+ response is obtained.
+
+- It calls any :term:`finished callback` functions defined within the
+ subrequest's lifetime.
+
+The invocation of a subrequest has more or less exactly the same effect as the
+invocation of a request received by the :app:`Pyramid` router from a web client
+when ``use_tweens=True``. When ``use_tweens=False``, the tweens are skipped
+but all the other steps take place.
+
+It's a poor idea to use the original ``request`` object as an argument to
+:meth:`~pyramid.request.Request.invoke_subrequest`. You should construct a new
+request instead as demonstrated in the above example, using
+:meth:`pyramid.request.Request.blank`. Once you've constructed a request
+object, you'll need to massage it to match the view callable that you'd like to
+be executed during the subrequest. This can be done by adjusting the
+subrequest's URL, its headers, its request method, and other attributes. The
+documentation for :class:`pyramid.request.Request` exposes the methods you
+should call and attributes you should set on the request that you create, then
+massage it into something that will actually match the view you'd like to call
+via a subrequest.
+
+We've demonstrated use of a subrequest from within a view callable, but you can
+use the :meth:`~pyramid.request.Request.invoke_subrequest` API from within a
+tween or an event handler as well. Even though you can do it, it's usually a
+poor idea to invoke :meth:`~pyramid.request.Request.invoke_subrequest` from
+within a tween, because tweens already, by definition, have access to a
+function that will cause a subrequest (they are passed a ``handle`` function).
+It's fine to invoke :meth:`~pyramid.request.Request.invoke_subrequest` from
+within an event handler, however.
+
+
+.. index::
+ pair: subrequest; exception view
+
+Invoking an Exception View
+--------------------------
+
+.. versionadded:: 1.7
+
+:app:`Pyramid` apps may define :term:`exception views <exception view>` which
+can handle any raised exceptions that escape from your code while processing
+a request. By default an unhandled exception will be caught by the ``EXCVIEW``
+:term:`tween`, which will then lookup an exception view that can handle the
+exception type, generating an appropriate error response.
+
+In :app:`Pyramid` 1.7 the :meth:`pyramid.request.Request.invoke_exception_view`
+was introduced, allowing a user to invoke an exception view while manually
+handling an exception. This can be useful in a few different circumstances:
+
+- Manually handling an exception losing the current call stack or flow.
+
+- Handling exceptions outside of the context of the ``EXCVIEW`` tween. The
+ tween only covers certain parts of the request processing pipeline (See
+ :ref:`router_chapter`). There are also some corner cases where an exception
+ can be raised that will still bubble up to middleware, and possibly to the
+ web server in which case a generic ``500 Internal Server Error`` will be
+ returned to the client.
+
+Below is an example usage of
+:meth:`pyramid.request.Request.invoke_exception_view`:
+
+.. code-block:: python
+ :linenos:
+
+ def foo(request):
+ try:
+ some_func_that_errors()
+ return response
+ except Exception:
+ response = request.invoke_exception_view()
+ if response is not None:
+ return response
+ else:
+ # there is no exception view for this exception, simply
+ # re-raise and let someone else handle it
+ raise
+
+Please note that in most cases you do not need to write code like this, and you
+may rely on the ``EXCVIEW`` tween to handle this for you.