summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2011-06-04 18:43:25 -0400
committerChris McDonough <chrism@plope.com>2011-06-04 18:43:25 -0400
commitdf15ed98612e7962e3122da52d8d5f5b9d8882b2 (patch)
treeb862f334a286fd0545a134a73c89d295a57d7a64
parent71738bc9418170cebfd532fbed6bb48ac8c3fb40 (diff)
downloadpyramid-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.txt26
-rw-r--r--docs/api/interfaces.rst2
-rw-r--r--docs/glossary.rst4
-rw-r--r--docs/narr/hooks.rst36
-rw-r--r--pyramid/interfaces.py8
-rw-r--r--pyramid/response.py5
-rw-r--r--pyramid/router.py33
-rw-r--r--pyramid/tests/test_router.py31
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()