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. |