Chris McDonough
2012-09-16 37d2c224b804dfebe9ee217c7a536364eacdee15
docs and test
2 files added
7 files modified
242 ■■■■■ changed files
CHANGES.txt 6 ●●●●● patch | view | raw | blame | history
docs/api/request.rst 6 ●●●●● patch | view | raw | blame | history
docs/index.rst 1 ●●●● patch | view | raw | blame | history
docs/latexindex.rst 1 ●●●● patch | view | raw | blame | history
docs/narr/subrequest.rst 143 ●●●●● patch | view | raw | blame | history
docs/whatsnew-1.4.rst 10 ●●●●● patch | view | raw | blame | history
pyramid/router.py 4 ●●● patch | view | raw | blame | history
pyramid/tests/pkgs/subrequestapp/__init__.py 20 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_integration.py 51 ●●●●● patch | view | raw | blame | history
CHANGES.txt
@@ -163,6 +163,9 @@
- Added the ``pyramid.testing.testConfig`` context manager, which can be used
  to generate a configurator in a test, e.g. ``with testing.testConfig(...):``.
- Users can now invoke a subrequest from within view code using the
  ``request.subrequest`` API.
Deprecations
------------
@@ -257,6 +260,9 @@
  how to show Pyramid-generated deprecation warnings while running tests and
  while running a server.
- Added a "Invoking a Subrequest" chapter to the documentation.  It describes
  how to use the new ``request.subrequest`` API.
Dependencies
------------
docs/api/request.rst
@@ -163,6 +163,10 @@
   .. method:: subrequest(request, use_tweens=False)
      .. warning::
         This API was added in Pyramid 1.4a1.
      Obtain a response object from the Pyramid application based on
      information in the ``request`` object provided.  The ``request`` object
      must be an object that implements the Pyramid request interface (such
@@ -204,6 +208,8 @@
      - Calls any :term:`finished callback` functions defined within the
        request's lifetime.
      See also :ref:`subrequest_chapter`.
   .. automethod:: add_response_callback
   .. automethod:: add_finished_callback
docs/index.rst
@@ -83,6 +83,7 @@
   narr/traversal
   narr/security
   narr/hybrid
   narr/subrequest
   narr/hooks
   narr/introspector
   narr/extending
docs/latexindex.rst
@@ -54,6 +54,7 @@
   narr/traversal
   narr/security
   narr/hybrid
   narr/subrequest
   narr/hooks
   narr/introspector
   narr/extending
docs/narr/subrequest.rst
New file
@@ -0,0 +1,143 @@
.. index::
   single: subrequest
.. _subrequest_chapter:
Invoking a Subrequest
=====================
.. warning::
   This feature was added in Pyramid 1.4a1.
:app:`Pyramid` allows you to invoke a subrequest at any point during the
processing of a request.  Invoking a subrequest allows you to obtain a
:term:`response` object from a view callable within your :app:`Pyramid`
application while you're executing a different view callable within the same
application.
Here's an example application which uses a subrequest:
.. 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.subrequest(subreq)
       return response
   def view_two(request):
       request.response.body = 'This came from view_two'
       return request.response
   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')
       app = config.make_wsgi_app()
       server = make_server('0.0.0.0', 8080, app)
       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
:meth:`pyramid.request.Request.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
:meth:`pyramid.request.Request.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 will work too:
.. 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.subrequest(subreq)
       return response
   def view_two(request):
       return 'This came from view_two'
   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()
Being able to unconditionally obtain a response object by invoking a view
callable indirectly is the main advantage to using
:meth:`pyramid.request.Request.subrequest` instead of simply importing it 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.  It's
much slower to use a subrequest.
The :meth:`pyramid.request.Request.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``.
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.subrequest`
function also:
- manages the threadlocal stack so that
  :func:`~pyramid.threadlocal.get_current_request` and
  :func:`~pyramid.threadlocal.get_current_registry` work during a request
  (they will return the subrequest instead of the original request)
- Adds a ``registry`` attribute and a ``subrequest`` attribute to the request
  object it's handed.
- sets request extensions (such as those added via
  :meth:`~pyramid.config.Configurator.add_request_method` or
  :meth:`~pyramid.config.Configurator.set_request_property`) on the subrequest
  object passed as ``request``
- causes a :class:`~pyramid.event.NewRequest` event to be sent at the
  beginning of request processing.
- causes a :class:`~pyramid.event.ContextFound` event to be sent when a
  context resource is found.
- causes a :class:`~pyramid.event.NewResponse` event to be sent when the
  Pyramid application returns a response.
- Calls any :term:`response callback` functions defined within the subrequest's
  lifetime if a response is obtained from the Pyramid application.
- Calls any :term:`finished callback` functions defined within the subrequest's
  lifetime.
It's a poor idea to use the original ``request`` object as an argument to
:meth:`~pyramid.request.Request.subrequest`.  You should construct a 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.
docs/whatsnew-1.4.rst
@@ -65,6 +65,14 @@
  defined as ``macroname`` within the ``template.pt`` template instead of the
  entire templae.
Subrequest Support
~~~~~~~~~~~~~~~~~~
- Developers may invoke a subrequest by using the
  :meth:`pyramid.request.Request.subrequest` API.  This allows a developer to
  obtain a response from one view callable by issuing a subrequest from within
  a different view callable.
Minor Feature Additions
-----------------------
@@ -239,6 +247,8 @@
  how to show Pyramid-generated deprecation warnings while running tests and
  while running a server.
- Added a :ref:`subrequest_chapter` chapter to the narrative documentation.
- Many cleanups and improvements to narrative and API docs.
Dependency Changes
pyramid/router.py
@@ -201,7 +201,9 @@
        - Calls any :term:`finished callback` functions defined within the
          request's lifetime.
          """
        See also :ref:`subrequest_chapter`.
        """
        registry = self.registry
        has_listeners = self.registry.has_listeners
        notify = self.registry.notify
pyramid/tests/pkgs/subrequestapp/__init__.py
New file
@@ -0,0 +1,20 @@
from pyramid.config import Configurator
from pyramid.request import Request
def view_one(request):
    subreq = Request.blank('/view_two')
    response = request.subrequest(subreq)
    return response
def view_two(request):
    request.response.body = 'This came from view_two'
    return request.response
def 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')
    return config
pyramid/tests/test_integration.py
@@ -578,26 +578,41 @@
        res = self.testapp.get('/hello', status=200)
        self.assertTrue(b'Hello' in res.body)
if os.name != 'java': # uses chameleon
    class RendererScanAppTest(IntegrationBase, unittest.TestCase):
        package = 'pyramid.tests.pkgs.rendererscanapp'
        def test_root(self):
            res = self.testapp.get('/one', status=200)
            self.assertTrue(b'One!' in res.body)
class SubrequestAppTest(unittest.TestCase):
    def setUp(self):
        from pyramid.tests.pkgs.subrequestapp import main
        config = main()
        app = config.make_wsgi_app()
        from webtest import TestApp
        self.testapp = TestApp(app)
        self.config = config
        def test_two(self):
            res = self.testapp.get('/two', status=200)
            self.assertTrue(b'Two!' in res.body)
    def tearDown(self):
        self.config.end()
        def test_rescan(self):
            self.config.scan('pyramid.tests.pkgs.rendererscanapp')
            app = self.config.make_wsgi_app()
            from webtest import TestApp
            testapp = TestApp(app)
            res = testapp.get('/one', status=200)
            self.assertTrue(b'One!' in res.body)
            res = testapp.get('/two', status=200)
            self.assertTrue(b'Two!' in res.body)
    def test_it(self):
        res = self.testapp.get('/view_one', status=200)
        self.assertTrue(b'This came from view_two' in res.body)
class RendererScanAppTest(IntegrationBase, unittest.TestCase):
    package = 'pyramid.tests.pkgs.rendererscanapp'
    def test_root(self):
        res = self.testapp.get('/one', status=200)
        self.assertTrue(b'One!' in res.body)
    def test_two(self):
        res = self.testapp.get('/two', status=200)
        self.assertTrue(b'Two!' in res.body)
    def test_rescan(self):
        self.config.scan('pyramid.tests.pkgs.rendererscanapp')
        app = self.config.make_wsgi_app()
        from webtest import TestApp
        testapp = TestApp(app)
        res = testapp.get('/one', status=200)
        self.assertTrue(b'One!' in res.body)
        res = testapp.get('/two', status=200)
        self.assertTrue(b'Two!' in res.body)
class DummyContext(object):
    pass