Invoking a Subrequest

バージョン 1.4 で追加.

Pyramid allows you to invoke a subrequest at any point during the processing of a request. Invoking a subrequest allows you to obtain a response object from a view callable within your Pyramid application while you're executing a different view callable within the same application.

Here's an example application which uses a subrequest:

 1from wsgiref.simple_server import make_server
 2from pyramid.config import Configurator
 3from pyramid.request import Request
 4
 5def view_one(request):
 6    subreq = Request.blank('/view_two')
 7    response = request.invoke_subrequest(subreq)
 8    return response
 9
10def view_two(request):
11    request.response.body = 'This came from view_two'
12    return request.response
13
14if __name__ == '__main__':
15    config = Configurator()
16    config.add_route('one', '/view_one')
17    config.add_route('two', '/view_two')
18    config.add_view(view_one, route_name='one')
19    config.add_view(view_two, route_name='two')
20    app = config.make_wsgi_app()
21    server = make_server('0.0.0.0', 8080, app)
22    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 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 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 pyramid.request.Request.invoke_subrequest() will return a Response object:

 1from wsgiref.simple_server import make_server
 2from pyramid.config import Configurator
 3from pyramid.request import Request
 4
 5def view_one(request):
 6    subreq = Request.blank('/view_two')
 7    response = request.invoke_subrequest(subreq)
 8    return response
 9
10def view_two(request):
11    return 'This came from view_two'
12
13if __name__ == '__main__':
14    config = Configurator()
15    config.add_route('one', '/view_one')
16    config.add_route('two', '/view_two')
17    config.add_view(view_one, route_name='one')
18    config.add_view(view_two, route_name='two', renderer='string')
19    app = config.make_wsgi_app()
20    server = make_server('0.0.0.0', 8080, app)
21    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 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 invoke_subrequest() even if you have a exception view configured:

 1from wsgiref.simple_server import make_server
 2from pyramid.config import Configurator
 3from pyramid.request import Request
 4
 5def view_one(request):
 6    subreq = Request.blank('/view_two')
 7    response = request.invoke_subrequest(subreq)
 8    return response
 9
10def view_two(request):
11    raise ValueError('foo')
12
13def excview(request):
14    request.response.body = b'An exception was raised'
15    request.response.status_int = 500
16    return request.response
17
18if __name__ == '__main__':
19    config = Configurator()
20    config.add_route('one', '/view_one')
21    config.add_route('two', '/view_two')
22    config.add_view(view_one, route_name='one')
23    config.add_view(view_two, route_name='two', renderer='string')
24    config.add_view(excview, context=Exception)
25    app = config.make_wsgi_app()
26    server = make_server('0.0.0.0', 8080, app)
27    server.serve_forever()

When we run the above code and visit /view_one in a browser, the excview exception view will not be executed. Instead, the call to invoke_subrequest() will cause a 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.

Subrequests with Tweens

The 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 pyramid.request.Request instance). If use_tweens is True, the request will be sent to the 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 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 exception view is configured, instead of raising the exception. This is because exception views are called by the exception view tween as described in Custom 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 invoke_subrequest(), like this:

 1from wsgiref.simple_server import make_server
 2from pyramid.config import Configurator
 3from pyramid.request import Request
 4
 5def view_one(request):
 6    subreq = Request.blank('/view_two')
 7    response = request.invoke_subrequest(subreq, use_tweens=True)
 8    return response
 9
10def view_two(request):
11    raise ValueError('foo')
12
13def excview(request):
14    request.response.body = b'An exception was raised'
15    request.response.status_int = 500
16    return request.response
17
18if __name__ == '__main__':
19    config = Configurator()
20    config.add_route('one', '/view_one')
21    config.add_route('two', '/view_two')
22    config.add_view(view_one, route_name='one')
23    config.add_view(view_two, route_name='two', renderer='string')
24    config.add_view(excview, context=Exception)
25    app = config.make_wsgi_app()
26    server = make_server('0.0.0.0', 8080, app)
27    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 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 invoke_subrequest() function also unconditionally does the following:

  • It manages the threadlocal stack so that get_current_request() and 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 add_request_method()) on the subrequest object passed as request.

  • It causes a NewRequest event to be sent at the beginning of request processing.

  • It causes a 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 response callback functions defined within the subrequest's lifetime if a response is obtained from the Pyramid application.

  • It causes a NewResponse event to be sent if a response is obtained.

  • It calls any 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 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 invoke_subrequest(). You should construct a new request instead as demonstrated in the above example, using 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 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 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 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 invoke_subrequest() from within an event handler, however.

Invoking an Exception View

バージョン 1.7 で追加.

Pyramid apps may define exception views 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 tween, which will then lookup an exception view that can handle the exception type, generating an appropriate error response.

In Pyramid 1.7 the 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 Request Processing). 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 pyramid.request.Request.invoke_exception_view():

 1def foo(request):
 2    try:
 3        some_func_that_errors()
 4        return response
 5    except Exception:
 6        response = request.invoke_exception_view()
 7        if response is not None:
 8            return response
 9        else:
10            # there is no exception view for this exception, simply
11            # re-raise and let someone else handle it
12            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.