diff options
| author | Chris McDonough <chrism@plope.com> | 2011-06-04 18:43:25 -0400 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2011-06-04 18:43:25 -0400 |
| commit | df15ed98612e7962e3122da52d8d5f5b9d8882b2 (patch) | |
| tree | b862f334a286fd0545a134a73c89d295a57d7a64 | |
| parent | 71738bc9418170cebfd532fbed6bb48ac8c3fb40 (diff) | |
| download | pyramid-df15ed98612e7962e3122da52d8d5f5b9d8882b2.tar.gz pyramid-df15ed98612e7962e3122da52d8d5f5b9d8882b2.tar.bz2 pyramid-df15ed98612e7962e3122da52d8d5f5b9d8882b2.zip | |
- It is now possible to control how the Pyramid router calls the WSGI
``start_response`` callable and obtains the WSGI ``app_iter`` based on
adapting the response object to the new ``pyramid.interfaces.IResponder``
interface. The default ``IResponder`` uses Pyramid 1.0's logic to do this.
To override the responder::
from pyramid.interfaces import IResponder
from pyramid.response import Response
from myapp import MyResponder
config.registry.registerAdapter(MyResponder, (Response,),
IResponder, name='')
This makes it possible to reuse response object implementations which have,
for example, their own ``__call__`` expected to be used as a WSGI
application (like ``pyramid.response.Response``), e.g.:
class MyResponder(object):
def __init__(self, response):
""" Obtain a reference to the response """
self.response = response
def __call__(self, request, start_response):
""" Call start_response and return an app_iter """
app_iter = self.response(request.environ, start_response)
return app_iter
| -rw-r--r-- | CHANGES.txt | 26 | ||||
| -rw-r--r-- | docs/api/interfaces.rst | 2 | ||||
| -rw-r--r-- | docs/glossary.rst | 4 | ||||
| -rw-r--r-- | docs/narr/hooks.rst | 36 | ||||
| -rw-r--r-- | pyramid/interfaces.py | 8 | ||||
| -rw-r--r-- | pyramid/response.py | 5 | ||||
| -rw-r--r-- | pyramid/router.py | 33 | ||||
| -rw-r--r-- | pyramid/tests/test_router.py | 31 |
8 files changed, 131 insertions, 14 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 15c86c13c..7840bc525 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -132,6 +132,32 @@ Features - The ``pyramid.request.Response`` class now has a ``RequestClass`` interface which points at ``pyramid.response.Request``. +- It is now possible to control how the Pyramid router calls the WSGI + ``start_response`` callable and obtains the WSGI ``app_iter`` based on + adapting the response object to the new ``pyramid.interfaces.IResponder`` + interface. The default ``IResponder`` uses Pyramid 1.0's logic to do this. + To override the responder:: + + from pyramid.interfaces import IResponder + from pyramid.response import Response + from myapp import MyResponder + + config.registry.registerAdapter(MyResponder, (Response,), + IResponder, name='') + + This makes it possible to reuse response object implementations which have, + for example, their own ``__call__`` expected to be used as a WSGI + application (like ``pyramid.response.Response``), e.g.: + + class MyResponder(object): + def __init__(self, response): + """ Obtain a reference to the response """ + self.response = response + def __call__(self, request, start_response): + """ Call start_response and return an app_iter """ + app_iter = self.response(request.environ, start_response) + return app_iter + Bug Fixes --------- diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index ac282fbcc..3a60fa4dc 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -57,4 +57,6 @@ Other Interfaces .. autointerface:: IMultiDict :members: + .. autointerface:: IResponder + :members: diff --git a/docs/glossary.rst b/docs/glossary.rst index 20b9bfd64..dbab331c1 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -900,5 +900,9 @@ Glossary http://docs.python.org/distutils/index.html for more information. :term:`setuptools` is actually an *extension* of the Distutils. + exception response + A :term:`response` that is generated as the result of a raised exception + being caught by an :term:`exception view`. + diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index d620b5672..aa151d281 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -522,6 +522,42 @@ The default context URL generator is available for perusal as the class :term:`Pylons` GitHub Pyramid repository. .. index:: + single: IResponder + +.. _using_iresponder: + +Changing How Pyramid Treats Response Objects +-------------------------------------------- + +It is possible to control how the Pyramid :term:`router` calls the WSGI +``start_response`` callable and obtains the WSGI ``app_iter`` based on +adapting the response object to the :class: `pyramid.interfaces.IResponder` +interface. The default ``IResponder`` uses the three attributes ``status``, +``headerlist``, and ``app_iter`` attached to the response object, and calls +``start_response`` with the status and headerlist, returning the +``app_iter``. To override the responder:: + + from pyramid.interfaces import IResponder + from pyramid.response import Response + from myapp import MyResponder + + config.registry.registerAdapter(MyResponder, (Response,), + IResponder, name='') + +Overriding makes it possible to reuse response object implementations which +have, for example, their own ``__call__`` expected to be used as a WSGI +application (like :class:`pyramid.response.Response`), e.g.: + + class MyResponder(object): + def __init__(self, response): + """ Obtain a reference to the response """ + self.response = response + def __call__(self, request, start_response): + """ Call start_response and return an app_iter """ + app_iter = self.response(request.environ, start_response) + return app_iter + +.. index:: single: view mapper .. _using_a_view_mapper: diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 237727b41..d5d382492 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -229,6 +229,14 @@ class IMultiDict(Interface): # docs-only interface dictionary. This is similar to the kind of dictionary often used to represent the variables in a web request. """ +class IResponder(Interface): + """ Adapter from IResponse to an IResponder. See :ref:`using_iresponder` + for usage details. New in Pyramid 1.1. + """ + def __call__(self, request, start_response): + """ Call the WSGI ``start_response`` callable passed as + ``start_response`` and return an ``app_iter``.""" + # internal interfaces class IRequest(Interface): diff --git a/pyramid/response.py b/pyramid/response.py index 41ac354f9..6e6af32c8 100644 --- a/pyramid/response.py +++ b/pyramid/response.py @@ -21,7 +21,6 @@ def _no_escape(value): value = str(value) return value - class HTTPException(Exception): # bw compat pass @@ -40,7 +39,7 @@ class WSGIHTTPException(Response, HTTPException): # as a result: # # - bases plaintext vs. html result on self.content_type rather than - # on request environ + # on request accept header # # - doesn't add request.environ keys to template substitutions unless # 'request' is passed as a constructor keyword argument. @@ -49,7 +48,7 @@ class WSGIHTTPException(Response, HTTPException): # in default body template) # # - sets a default app_iter if no body, app_iter, or unicode_body is - # passed + # passed using a template (ala the replaced version's "generate_response") # # - explicitly sets self.message = detail to prevent whining by Python # 2.6.5+ access of Exception.message diff --git a/pyramid/router.py b/pyramid/router.py index 9cd682623..92c6cc920 100644 --- a/pyramid/router.py +++ b/pyramid/router.py @@ -12,6 +12,7 @@ from pyramid.interfaces import IRoutesMapper from pyramid.interfaces import ITraverser from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier +from pyramid.interfaces import IResponder from pyramid.events import ContextFound from pyramid.events import NewRequest @@ -58,6 +59,7 @@ class Router(object): logger = self.logger manager = self.threadlocal_manager request = None + responder = default_responder threadlocals = {'registry':registry, 'request':request} manager.push(threadlocals) @@ -186,21 +188,30 @@ class Router(object): if request.response_callbacks: request._process_response_callbacks(response) - try: - headers = response.headerlist - app_iter = response.app_iter - status = response.status - except AttributeError: - raise ValueError( - 'Non-response object returned from view named %s ' - '(and no renderer): %r' % (view_name, response)) + responder = adapters.queryAdapter(response, IResponder) + if responder is None: + responder = default_responder(response) finally: if request is not None and request.finished_callbacks: request._process_finished_callbacks() - start_response(status, headers) - return app_iter - + return responder(request, start_response) + finally: manager.pop() + +def default_responder(response): + def inner(request, start_response): + try: + headers = response.headerlist + app_iter = response.app_iter + status = response.status + except AttributeError: + raise ValueError( + 'Non-response object returned from view ' + '(and no renderer): %r' % (response)) + start_response(status, headers) + return app_iter + return inner + diff --git a/pyramid/tests/test_router.py b/pyramid/tests/test_router.py index 106f7c57d..a89de7a36 100644 --- a/pyramid/tests/test_router.py +++ b/pyramid/tests/test_router.py @@ -449,6 +449,37 @@ class TestRouter(unittest.TestCase): exc_raised(NotImplementedError, router, environ, start_response) self.assertEqual(environ['called_back'], True) + def test_call_with_overridden_iresponder_factory(self): + from zope.interface import Interface + from zope.interface import directlyProvides + from pyramid.interfaces import IRequest + from pyramid.interfaces import IViewClassifier + from pyramid.interfaces import IResponder + context = DummyContext() + class IFoo(Interface): + pass + directlyProvides(context, IFoo) + self._registerTraverserFactory(context, subpath=['']) + class DummyResponder(object): + def __init__(self, response): + self.response = response + def __call__(self, request, start_response): + self.response.responder_used = True + return '123' + self.registry.registerAdapter(DummyResponder, (None,), + IResponder, name='') + response = DummyResponse('200 OK') + directlyProvides(response, IFoo) + def view(context, request): + return response + environ = self._makeEnviron() + self._registerView(view, '', IViewClassifier, IRequest, Interface) + router = self._makeOne() + start_response = DummyStartResponse() + result = router(environ, start_response) + self.assertTrue(response.responder_used) + self.assertEqual(result, '123') + def test_call_request_factory_raises(self): # making sure finally doesnt barf when a request cannot be created environ = self._makeEnviron() |
