Dustin Ingram
2018-01-25 b31ecf09f50e4032ef83c744e36d6f7dde725205
commit | author | age
37d2c2 1 .. index::
CM 2    single: subrequest
3
4 .. _subrequest_chapter:
5
6 Invoking a Subrequest
7 =====================
8
e3ed0e 9 .. versionadded:: 1.4
37d2c2 10
CM 11 :app:`Pyramid` allows you to invoke a subrequest at any point during the
12 processing of a request.  Invoking a subrequest allows you to obtain a
13 :term:`response` object from a view callable within your :app:`Pyramid`
14 application while you're executing a different view callable within the same
15 application.
16
17 Here's an example application which uses a subrequest:
18
19 .. code-block:: python
65bb52 20    :linenos:
37d2c2 21
CM 22    from wsgiref.simple_server import make_server
23    from pyramid.config import Configurator
24    from pyramid.request import Request
25
26    def view_one(request):
27        subreq = Request.blank('/view_two')
64452e 28        response = request.invoke_subrequest(subreq)
37d2c2 29        return response
CM 30
31    def view_two(request):
32        request.response.body = 'This came from view_two'
33        return request.response
34
35    if __name__ == '__main__':
36        config = Configurator()
37        config.add_route('one', '/view_one')
38        config.add_route('two', '/view_two')
39        config.add_view(view_one, route_name='one')
40        config.add_view(view_two, route_name='two')
41        app = config.make_wsgi_app()
42        server = make_server('0.0.0.0', 8080, app)
43        server.serve_forever()
44
f3d20e 45 When ``/view_one`` is visted in a browser, the text printed in the browser pane
SP 46 will be ``This came from view_two``.  The ``view_one`` view used the
47 :meth:`pyramid.request.Request.invoke_subrequest` API to obtain a response from
48 another view (``view_two``) within the same application when it executed.  It
49 did so by constructing a new request that had a URL that it knew would match
50 the ``view_two`` view registration, and passed that new request along to
51 :meth:`pyramid.request.Request.invoke_subrequest`.  The ``view_two`` view
52 callable was invoked, and it returned a response.  The ``view_one`` view
53 callable then simply returned the response it obtained from the ``view_two``
54 view callable.
37d2c2 55
CM 56 Note that it doesn't matter if the view callable invoked via a subrequest
db2a03 57 actually returns a *literal* Response object.  Any view callable that uses a
37d2c2 58 renderer or which returns an object that can be interpreted by a response
db2a03 59 adapter when found and invoked via
33b89f 60 :meth:`pyramid.request.Request.invoke_subrequest` will return a Response
SP 61 object:
37d2c2 62
CM 63 .. code-block:: python
65bb52 64    :linenos:
b31ecf 65    :emphasize-lines: 11,18
37d2c2 66
CM 67    from wsgiref.simple_server import make_server
68    from pyramid.config import Configurator
69    from pyramid.request import Request
70
71    def view_one(request):
72        subreq = Request.blank('/view_two')
64452e 73        response = request.invoke_subrequest(subreq)
37d2c2 74        return response
CM 75
76    def view_two(request):
77        return 'This came from view_two'
78
79    if __name__ == '__main__':
80        config = Configurator()
81        config.add_route('one', '/view_one')
82        config.add_route('two', '/view_two')
83        config.add_view(view_one, route_name='one')
84        config.add_view(view_two, route_name='two', renderer='string')
85        app = config.make_wsgi_app()
86        server = make_server('0.0.0.0', 8080, app)
87        server.serve_forever()
88
f3d20e 89 Even though the ``view_two`` view callable returned a string, it was invoked in
SP 90 such a way that the ``string`` renderer associated with the view registration
91 that was found turned it into a "real" response object for consumption by
92 ``view_one``.
ab84e1 93
37d2c2 94 Being able to unconditionally obtain a response object by invoking a view
CM 95 callable indirectly is the main advantage to using
f3d20e 96 :meth:`pyramid.request.Request.invoke_subrequest` instead of simply importing a
SP 97 view callable and executing it directly.  Note that there's not much advantage
98 to invoking a view using a subrequest if you *can* invoke a view callable
99 directly.  Subrequests are slower and are less convenient if you actually do
100 want just the literal information returned by a function that happens to be a
101 view callable.
37d2c2 102
db2a03 103 Note that, by default, if a view callable invoked by a subrequest raises an
CM 104 exception, the exception will be raised to the caller of
105 :meth:`~pyramid.request.Request.invoke_subrequest` even if you have a
0966cf 106 :term:`exception view` configured:
1e59ef 107
CM 108 .. code-block:: python
65bb52 109    :linenos:
SD 110    :emphasize-lines: 11-16
1e59ef 111
CM 112    from wsgiref.simple_server import make_server
113    from pyramid.config import Configurator
114    from pyramid.request import Request
115
116    def view_one(request):
117        subreq = Request.blank('/view_two')
64452e 118        response = request.invoke_subrequest(subreq)
1e59ef 119        return response
CM 120
121    def view_two(request):
122        raise ValueError('foo')
123
0966cf 124    def excview(request):
CM 125        request.response.body = b'An exception was raised'
126        request.response.status_int = 500
127        return request.response
128
1e59ef 129    if __name__ == '__main__':
CM 130        config = Configurator()
131        config.add_route('one', '/view_one')
132        config.add_route('two', '/view_two')
133        config.add_view(view_one, route_name='one')
134        config.add_view(view_two, route_name='two', renderer='string')
0966cf 135        config.add_view(excview, context=Exception)
1e59ef 136        app = config.make_wsgi_app()
CM 137        server = make_server('0.0.0.0', 8080, app)
138        server.serve_forever()
139
db2a03 140 When we run the above code and visit ``/view_one`` in a browser, the
CM 141 ``excview`` :term:`exception view` will *not* be executed.  Instead, the call
142 to :meth:`~pyramid.request.Request.invoke_subrequest` will cause a
143 :exc:`ValueError` exception to be raised and a response will never be
f3d20e 144 generated.  We can change this behavior; how to do so is described below in our
SP 145 discussion of the ``use_tweens`` argument.
146
147 .. index::
148    pair: subrequest; use_tweens
149
150 Subrequests with Tweens
151 -----------------------
1e59ef 152
64452e 153 The :meth:`pyramid.request.Request.invoke_subrequest` API accepts two
f3d20e 154 arguments: a required positional argument ``request``, and an optional keyword
SP 155 argument ``use_tweens`` which defaults to ``False``.
37d2c2 156
f3d20e 157 The ``request`` object passed to the API must be an object that implements the
SP 158 Pyramid request interface (such as a :class:`pyramid.request.Request`
37d2c2 159 instance).  If ``use_tweens`` is ``True``, the request will be sent to the
CM 160 :term:`tween` in the tween stack closest to the request ingress.  If
161 ``use_tweens`` is ``False``, the request will be sent to the main router
7259e7 162 handler, and no tweens will be invoked.
CM 163
164 In the example above, the call to
db2a03 165 :meth:`~pyramid.request.Request.invoke_subrequest` will always raise an
CM 166 exception.  This is because it's using the default value for ``use_tweens``,
f3d20e 167 which is ``False``.  Alternatively, you can pass ``use_tweens=True`` to ensure
SP 168 that it will convert an exception to a Response if an :term:`exception view` is
169 configured, instead of raising the exception.  This is because exception views
db2a03 170 are called by the exception view :term:`tween` as described in
CM 171 :ref:`exception_views` when any view raises an exception.
7259e7 172
db2a03 173 We can cause the subrequest to be run through the tween stack by passing
CM 174 ``use_tweens=True`` to the call to
7259e7 175 :meth:`~pyramid.request.Request.invoke_subrequest`, like this:
CM 176
177 .. code-block:: python
65bb52 178    :linenos:
SD 179    :emphasize-lines: 7
7259e7 180
CM 181    from wsgiref.simple_server import make_server
182    from pyramid.config import Configurator
183    from pyramid.request import Request
184
185    def view_one(request):
186        subreq = Request.blank('/view_two')
db2a03 187        response = request.invoke_subrequest(subreq, use_tweens=True)
7259e7 188        return response
CM 189
190    def view_two(request):
191        raise ValueError('foo')
192
0966cf 193    def excview(request):
CM 194        request.response.body = b'An exception was raised'
195        request.response.status_int = 500
196        return request.response
197
7259e7 198    if __name__ == '__main__':
CM 199        config = Configurator()
200        config.add_route('one', '/view_one')
201        config.add_route('two', '/view_two')
202        config.add_view(view_one, route_name='one')
203        config.add_view(view_two, route_name='two', renderer='string')
0966cf 204        config.add_view(excview, context=Exception)
7259e7 205        app = config.make_wsgi_app()
CM 206        server = make_server('0.0.0.0', 8080, app)
207        server.serve_forever()
208
db2a03 209 In the above case, the call to ``request.invoke_subrequest(subreq)`` will not
CM 210 raise an exception.  Instead, it will retrieve a "500" response from the
211 attempted invocation of ``view_two``, because the tween which invokes an
212 exception view to generate a response is run, and therefore ``excview`` is
213 executed.
7259e7 214
f3d20e 215 This is one of the major differences between specifying the ``use_tweens=True``
SP 216 and ``use_tweens=False`` arguments to
7259e7 217 :meth:`~pyramid.request.Request.invoke_subrequest`.  ``use_tweens=True`` may
f3d20e 218 also imply invoking a transaction commit or abort for the logic executed in the
SP 219 subrequest if you've got ``pyramid_tm`` in the tween list, injecting debug HTML
220 if you've got ``pyramid_debugtoolbar`` in the tween list, and other
7259e7 221 tween-related side effects as defined by your particular tween list.
CM 222
db2a03 223 The :meth:`~pyramid.request.Request.invoke_subrequest` function also
f3d20e 224 unconditionally does the following:
SP 225
226 - It manages the threadlocal stack so that
37d2c2 227   :func:`~pyramid.threadlocal.get_current_request` and
f3d20e 228   :func:`~pyramid.threadlocal.get_current_registry` work during a request (they
SP 229   will return the subrequest instead of the original request).
37d2c2 230
f3d20e 231 - It adds a ``registry`` attribute and an ``invoke_subrequest`` attribute (a
SP 232   callable) to the request object to which it is handed.
37d2c2 233
f3d20e 234 - It sets request extensions (such as those added via
37d2c2 235   :meth:`~pyramid.config.Configurator.add_request_method` or
CM 236   :meth:`~pyramid.config.Configurator.set_request_property`) on the subrequest
f3d20e 237   object passed as ``request``.
37d2c2 238
f3d20e 239 - It causes a :class:`~pyramid.events.NewRequest` event to be sent at the
37d2c2 240   beginning of request processing.
CM 241
f3d20e 242 - It causes a :class:`~pyramid.events.ContextFound` event to be sent when a
37d2c2 243   context resource is found.
f758ec 244
f3d20e 245 - It ensures that the user implied by the request passed in has the necessary
SP 246   authorization to invoke the view callable before calling it.
db2a03 247
f3d20e 248 - It calls any :term:`response callback` functions defined within the
SP 249   subrequest's lifetime if a response is obtained from the Pyramid application.
37d2c2 250
f3d20e 251 - It causes a :class:`~pyramid.events.NewResponse` event to be sent if a
SP 252   response is obtained.
fc477b 253
f3d20e 254 - It calls any :term:`finished callback` functions defined within the
SP 255   subrequest's lifetime.
37d2c2 256
f3d20e 257 The invocation of a subrequest has more or less exactly the same effect as the
SP 258 invocation of a request received by the :app:`Pyramid` router from a web client
db2a03 259 when ``use_tweens=True``.  When ``use_tweens=False``, the tweens are skipped
CM 260 but all the other steps take place.
261
37d2c2 262 It's a poor idea to use the original ``request`` object as an argument to
f3d20e 263 :meth:`~pyramid.request.Request.invoke_subrequest`.  You should construct a new
SP 264 request instead as demonstrated in the above example, using
37d2c2 265 :meth:`pyramid.request.Request.blank`.  Once you've constructed a request
f3d20e 266 object, you'll need to massage it to match the view callable that you'd like to
SP 267 be executed during the subrequest.  This can be done by adjusting the
7259e7 268 subrequest's URL, its headers, its request method, and other attributes.  The
CM 269 documentation for :class:`pyramid.request.Request` exposes the methods you
f3d20e 270 should call and attributes you should set on the request that you create, then
SP 271 massage it into something that will actually match the view you'd like to call
272 via a subrequest.
3f3917 273
f3d20e 274 We've demonstrated use of a subrequest from within a view callable, but you can
SP 275 use the :meth:`~pyramid.request.Request.invoke_subrequest` API from within a
276 tween or an event handler as well.  Even though you can do it, it's usually a
277 poor idea to invoke :meth:`~pyramid.request.Request.invoke_subrequest` from
278 within a tween, because tweens already, by definition, have access to a
279 function that will cause a subrequest (they are passed a ``handle`` function).
280 It's fine to invoke :meth:`~pyramid.request.Request.invoke_subrequest` from
281 within an event handler, however.
542f86 282
dc3f79 283
SP 284 .. index::
285    pair: subrequest; exception view
286
542f86 287 Invoking an Exception View
MM 288 --------------------------
289
290 .. versionadded:: 1.7
291
dc3f79 292 :app:`Pyramid` apps may define :term:`exception views <exception view>` which
542f86 293 can handle any raised exceptions that escape from your code while processing
dc3f79 294 a request. By default an unhandled exception will be caught by the ``EXCVIEW``
SP 295 :term:`tween`, which will then lookup an exception view that can handle the
542f86 296 exception type, generating an appropriate error response.
MM 297
298 In :app:`Pyramid` 1.7 the :meth:`pyramid.request.Request.invoke_exception_view`
dc3f79 299 was introduced, allowing a user to invoke an exception view while manually
542f86 300 handling an exception. This can be useful in a few different circumstances:
MM 301
dc3f79 302 - Manually handling an exception losing the current call stack or flow.
542f86 303
dc3f79 304 - Handling exceptions outside of the context of the ``EXCVIEW`` tween. The
SP 305   tween only covers certain parts of the request processing pipeline (See
306   :ref:`router_chapter`). There are also some corner cases where an exception
307   can be raised that will still bubble up to middleware, and possibly to the
308   web server in which case a generic ``500 Internal Server Error`` will be
542f86 309   returned to the client.
MM 310
311 Below is an example usage of
312 :meth:`pyramid.request.Request.invoke_exception_view`:
313
314 .. code-block:: python
315    :linenos:
316
317    def foo(request):
318        try:
319            some_func_that_errors()
320            return response
321        except Exception:
322            response = request.invoke_exception_view()
323            if response is not None:
324                return response
325            else:
7175a5 326                # there is no exception view for this exception, simply
MM 327                # re-raise and let someone else handle it
542f86 328                raise
MM 329
dc3f79 330 Please note that in most cases you do not need to write code like this, and you
SP 331 may rely on the ``EXCVIEW`` tween to handle this for you.