diff options
| author | Chris McDonough <chrism@plope.com> | 2012-09-16 13:04:14 -0400 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2012-09-16 13:04:14 -0400 |
| commit | 7259e7e9f3d8f8eb70bcf782f622f8613f99a51d (patch) | |
| tree | 01f8d4260cb0c30fd04773acb872e59a777d8989 | |
| parent | 5da21f623e0aa71106bd54bf0d199435f003b1d4 (diff) | |
| download | pyramid-7259e7e9f3d8f8eb70bcf782f622f8613f99a51d.tar.gz pyramid-7259e7e9f3d8f8eb70bcf782f622f8613f99a51d.tar.bz2 pyramid-7259e7e9f3d8f8eb70bcf782f622f8613f99a51d.zip | |
make use_tweens=True the default, add some more tests
| -rw-r--r-- | docs/api/request.rst | 14 | ||||
| -rw-r--r-- | docs/narr/subrequest.rst | 83 | ||||
| -rw-r--r-- | pyramid/router.py | 4 | ||||
| -rw-r--r-- | pyramid/tests/pkgs/subrequestapp/__init__.py | 30 | ||||
| -rw-r--r-- | pyramid/tests/test_integration.py | 10 |
5 files changed, 118 insertions, 23 deletions
diff --git a/docs/api/request.rst b/docs/api/request.rst index 1718d0743..8af81cdac 100644 --- a/docs/api/request.rst +++ b/docs/api/request.rst @@ -161,7 +161,7 @@ request, the value of this attribute will be ``None``. See :ref:`matched_route`. - .. method:: invoke_subrequest(request, use_tweens=False) + .. method:: invoke_subrequest(request, use_tweens=True) .. warning:: @@ -174,9 +174,9 @@ ``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. This isn't *actually* a method of the Request object; it's - a callable added when the Pyramid router is invoked, or when a - subrequest is invoked. This function also: + be invoked. + + This function also: - manages the threadlocal stack (so that :func:`~pyramid.threadlocal.get_current_request` and @@ -208,7 +208,11 @@ - Calls any :term:`finished callback` functions defined within the request's lifetime. - See also :ref:`subrequest_chapter`. + ``invoke_subrequest`` isn't *actually* a method of the Request object; + it's a callable added when the Pyramid router is invoked, or when a + subrequest is invoked. This means that it's not available for use on a + request provided by e.g. the ``pshell`` environment. For more + information, see :ref:`subrequest_chapter`. .. automethod:: add_response_callback diff --git a/docs/narr/subrequest.rst b/docs/narr/subrequest.rst index bd50b6053..1c26da73a 100644 --- a/docs/narr/subrequest.rst +++ b/docs/narr/subrequest.rst @@ -91,14 +91,14 @@ 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 -the view callable and executing it directly. Note that there's not much +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 if a view callable invoked by a subrequest raises an exception, the -exception will usually bubble up to the invoking code: +exception will usually be converted to a response: .. code-block:: python @@ -124,23 +124,69 @@ exception will usually bubble up to the invoking code: server = make_server('0.0.0.0', 8080, app) server.serve_forever() -In the above application, the call to ``request.invoke_subrequest(subreq)`` -will actually raise a :exc:`ValueError` exception instead of retrieving a -"500" response from the attempted invocation of ``view_two``. +Because the exception view handling tween is generally in the tween list, if +we run the above code, the default :term:`exception view` will generate a +"500" error response, which will be returned to us within ``view_one``: a +Python exception will not be raised. The :meth:`pyramid.request.Request.invoke_subrequest` API accepts two arguments: a positional argument ``request`` that must be provided, and and -``use_tweens`` keyword argument that is optional; it defaults to ``False``. +``use_tweens`` keyword argument that is optional; it defaults to ``True``. 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. It's usually best to not invoke any -tweens when executing a subrequest, because the original request will invoke -any tween logic as necessary. The -:meth:`pyramid.request.Request.invoke_subrequest` function also: +handler, and no tweens will be invoked. + +In the example above, the call to +:meth:`~pyramid.request.Request.invoke_subrequest` will generally always +return a Response object, even when the view it invokes raises an exception, +because it uses the default ``use_tweens=True``. + +We can cause the subrequest to not be run through the tween stack by passing +``use_tweens=False`` to the call to +:meth:`~pyramid.request.Request.invoke_subrequest`, like this: + +.. code-block:: python + + 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=False) + return response + + def view_two(request): + raise ValueError('foo') + + 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() + +In the above case, the call to ``request.invoke_subrequest(subreq)`` will +actually raise a :exc:`ValueError` exception instead of retrieving a "500" +response from the attempted invocation of ``view_two``, because the tween +which invokes an exception view to generate a response is never run. + +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 transaction commit/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: - manages the threadlocal stack so that :func:`~pyramid.threadlocal.get_current_request` and @@ -176,11 +222,18 @@ 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 the it to match the view callable 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. See -the documentation for :class:`pyramid.request.Request` to understand how to -massage your new request object into something that will match the view you'd -like to call via a subrequest. +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 you create to +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. +within a tween or an event handler as well. 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), but you can +do it. It's fine to invoke +:meth:`~pyramid.request.Request.invoke_subrequest` from within an event +handler, however. diff --git a/pyramid/router.py b/pyramid/router.py index 18624376c..8ee826a2c 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -162,7 +162,7 @@ class Router(object): return response - def invoke_subrequest(self, request, use_tweens=False): + def invoke_subrequest(self, request, use_tweens=True): """ Obtain a response object from the Pyramid application based on information in the ``request`` object provided. The ``request`` @@ -212,10 +212,12 @@ class Router(object): manager.push(threadlocals) request.registry = registry request.invoke_subrequest = self.invoke_subrequest + if use_tweens: handle_request = self.handle_request else: handle_request = self.orig_handle_request + try: try: diff --git a/pyramid/tests/pkgs/subrequestapp/__init__.py b/pyramid/tests/pkgs/subrequestapp/__init__.py index 92f4bbe68..dceaa9e45 100644 --- a/pyramid/tests/pkgs/subrequestapp/__init__.py +++ b/pyramid/tests/pkgs/subrequestapp/__init__.py @@ -3,17 +3,45 @@ from pyramid.request import Request def view_one(request): subreq = Request.blank('/view_two') - response = request.invoke_subrequest(subreq) + response = request.invoke_subrequest(subreq, use_tweens=False) return response def view_two(request): return 'This came from view_two' +def view_three(request): + subreq = Request.blank('/view_four') + response = request.invoke_subrequest(subreq, use_tweens=True) + return response + +def view_four(request): + raise ValueError('foo') + +def view_five(request): + subreq = Request.blank('/view_four') + try: + return request.invoke_subrequest(subreq, use_tweens=False) + except ValueError: + request.response.body = b'Value error raised' + return request.response + +def excview(request): + request.response.status_int = 500 + request.response.body = b'Bad stuff happened' + return request.response + def main(): config = Configurator() config.add_route('one', '/view_one') config.add_route('two', '/view_two') + config.add_route('three', '/view_three') + config.add_route('four', '/view_four') + config.add_route('five', '/view_five') + config.add_view(excview, context=Exception) config.add_view(view_one, route_name='one') config.add_view(view_two, route_name='two', renderer='string') + config.add_view(view_three, route_name='three') + config.add_view(view_four, route_name='four') + config.add_view(view_five, route_name='five') return config diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py index 3f26791de..9a8f842aa 100644 --- a/pyramid/tests/test_integration.py +++ b/pyramid/tests/test_integration.py @@ -590,10 +590,18 @@ class SubrequestAppTest(unittest.TestCase): def tearDown(self): self.config.end() - def test_it(self): + def test_one(self): res = self.testapp.get('/view_one', status=200) self.assertTrue(b'This came from view_two' in res.body) + def test_three(self): + res = self.testapp.get('/view_three', status=500) + self.assertTrue(b'Bad stuff happened' in res.body) + + def test_five(self): + res = self.testapp.get('/view_five', status=200) + self.assertTrue(b'Value error raised' in res.body) + class RendererScanAppTest(IntegrationBase, unittest.TestCase): package = 'pyramid.tests.pkgs.rendererscanapp' def test_root(self): |
