summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2012-09-16 13:04:14 -0400
committerChris McDonough <chrism@plope.com>2012-09-16 13:04:14 -0400
commit7259e7e9f3d8f8eb70bcf782f622f8613f99a51d (patch)
tree01f8d4260cb0c30fd04773acb872e59a777d8989
parent5da21f623e0aa71106bd54bf0d199435f003b1d4 (diff)
downloadpyramid-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.rst14
-rw-r--r--docs/narr/subrequest.rst83
-rw-r--r--pyramid/router.py4
-rw-r--r--pyramid/tests/pkgs/subrequestapp/__init__.py30
-rw-r--r--pyramid/tests/test_integration.py10
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):