summaryrefslogtreecommitdiff
path: root/repoze
diff options
context:
space:
mode:
authorChris McDonough <chrism@agendaless.com>2010-09-05 04:58:23 +0000
committerChris McDonough <chrism@agendaless.com>2010-09-05 04:58:23 +0000
commit844e98b01c5c6aa1585a76ac77f92bb8c1ef9d90 (patch)
treed88407f6af193047b4892b328cbd76c101d2300d /repoze
parent2d4f61826a0ebc5330b869713abf7a36a69c0e6a (diff)
downloadpyramid-844e98b01c5c6aa1585a76ac77f92bb8c1ef9d90.tar.gz
pyramid-844e98b01c5c6aa1585a76ac77f92bb8c1ef9d90.tar.bz2
pyramid-844e98b01c5c6aa1585a76ac77f92bb8c1ef9d90.zip
Documentation
------------- - Add an API chapter for the ``repoze.bfg.request`` module, which includes documentation for the ``repoze.bfg.request.Request`` class (the "request object"). - Modify the "Request and Response" narrative chapter to reference the new ``repoze.bfg.request`` API chapter. Some content was moved from this chapter into the API documentation itself. Features -------- - A new ``repoze.bfg.request.Request.add_response_callback`` API has been added. This method is documented in the new ``repoze.bfg.request`` API chapter. It can be used to influence response values before a concrete response object has been created. Internal -------- - The (internal) feature which made it possible to attach a ``global_response_headers`` attribute to the request (which was assumed to contain a sequence of header key/value pairs which would later be added to the response by the router), has been removed. The functionality of ``repoze.bfg.request.Request.add_response_callback`` takes its place.
Diffstat (limited to 'repoze')
-rw-r--r--repoze/bfg/request.py72
-rw-r--r--repoze/bfg/router.py10
-rw-r--r--repoze/bfg/testing.py6
-rw-r--r--repoze/bfg/tests/test_authentication.py23
-rw-r--r--repoze/bfg/tests/test_request.py47
-rw-r--r--repoze/bfg/tests/test_router.py9
-rw-r--r--repoze/bfg/tests/test_testing.py6
7 files changed, 154 insertions, 19 deletions
diff --git a/repoze/bfg/request.py b/repoze/bfg/request.py
index 8939e7b88..c9422e24d 100644
--- a/repoze/bfg/request.py
+++ b/repoze/bfg/request.py
@@ -16,9 +16,74 @@ def make_request_ascii(event):
request.default_charset = None
class Request(WebobRequest):
+ """
+ A subclass of the :term:`WebOb` Request class. An instance of
+ this class is created by the :term:`router` and is provided to a
+ view callable (and to other subsystems) as the ``request``
+ argument.
+
+ The documentation below (save for the ``add_response_callback``
+ method, which is defined in this subclass itself, and the
+ attributes ``context``, ``registry``, ``root``, ``subpath``,
+ ``traversed``, ``view_name``, ``virtual_root`` , and
+ ``virtual_root_path``, each of which is added to the request at by
+ the :term:`router` at request ingress time) are autogenerated from
+ the WebOb source code used when this documentation was generated.
+
+ Due to technical constraints, we can't yet display the WebOb
+ version number from which this documentation is autogenerated, but
+ it will be the 'prevailing WebOb version' at the time of the
+ release of this :mod:`repoze.bfg` version. See
+ `http://http://pythonpaste.org/webob/
+ <http://pythonpaste.org/webob/>`_ for further information.
+ """
implements(IRequest)
+ response_callbacks = ()
default_charset = 'utf-8'
+ def add_response_callback(self, callback):
+ """
+ Add a callback to the set of callbacks to be called by the
+ :term:`router` at a point after a :term:`response` object is
+ successfully created. :mod:`repoze.bfg` does not have a
+ global response object: this functionality allows an
+ application to register an action to be performed against the
+ response once one is created.
+
+ A 'callback' is a callable which accepts two positional
+ parameters: ``request`` and ``response``. For example:
+
+ .. code-block:: python
+ :linenos:
+
+ def cache_callback(request, response):
+ 'Set the cache_control max_age for the response'
+ response.cache_control.max_age = 360
+ request.add_response_callback(cache_callback)
+
+ Response callbacks are called in the order they're added
+ (first-to-most-recently-added). No response callback is
+ called if an exception happens in application code, or if the
+ response object returned by :term:`view` code is invalid.
+
+ All response callbacks are called *before* the
+ :class:`repoze.bfg.interfaces.INewResponse` event is sent.
+
+ Errors raised by callbacks are not handled specially. They
+ will be propagated to the caller of the :mod:`repoze.bfg`
+ router application. """
+
+ callbacks = self.response_callbacks
+ if not callbacks:
+ callbacks = []
+ callbacks.append(callback)
+ self.response_callbacks = callbacks
+
+ def _process_response_callbacks(self, response):
+ for callback in self.response_callbacks:
+ callback(self, response)
+ self.response_callbacks = ()
+
# override default WebOb "environ['adhoc_attr']" mutation behavior
__getattr__ = object.__getattribute__
__setattr__ = object.__setattr__
@@ -87,9 +152,10 @@ def route_request_iface(name, bases=()):
return iface
def add_global_response_headers(request, headerlist):
- attrs = request.__dict__
- response_headers = attrs.setdefault('global_response_headers', [])
- response_headers.extend(headerlist)
+ def add_headers(request, response):
+ for k, v in headerlist:
+ response.headers.add(k, v)
+ request.add_response_callback(add_headers)
from repoze.bfg.threadlocal import get_current_request as get_request # b/c
diff --git a/repoze/bfg/router.py b/repoze/bfg/router.py
index ed187240c..8f96bb798 100644
--- a/repoze/bfg/router.py
+++ b/repoze/bfg/router.py
@@ -144,8 +144,6 @@ class Router(object):
attrs['exception'] = why
response = view_callable(why, request)
- # process the response
- has_listeners and registry.notify(NewResponse(response))
try:
headers = response.headerlist
app_iter = response.app_iter
@@ -155,9 +153,11 @@ class Router(object):
'Non-response object returned from view named %s '
'(and no renderer): %r' % (view_name, response))
- if 'global_response_headers' in attrs:
- headers = list(headers)
- headers.extend(attrs['global_response_headers'])
+ if request.response_callbacks:
+ request._process_response_callbacks(response)
+
+ # process the response
+ has_listeners and registry.notify(NewResponse(response))
start_response(status, headers)
return app_iter
diff --git a/repoze/bfg/testing.py b/repoze/bfg/testing.py
index d5ae7bd56..5502cb3d2 100644
--- a/repoze/bfg/testing.py
+++ b/repoze/bfg/testing.py
@@ -568,6 +568,7 @@ class DummyRequest(object):
application_url = 'http://example.com'
host = 'example.com:80'
content_length = 0
+ response_callbacks = ()
def __init__(self, params=None, environ=None, headers=None, path='/',
cookies=None, post=None, **kw):
if environ is None:
@@ -608,6 +609,11 @@ class DummyRequest(object):
self.registry = get_current_registry()
self.__dict__.update(kw)
+ def add_response_callback(self, callback):
+ if not self.response_callbacks:
+ self.response_callbacks = []
+ self.response_callbacks.append(callback)
+
def setUp(registry=None, request=None, hook_zca=True):
"""
Set :mod:`repoze.bfg` registry and request thread locals for the
diff --git a/repoze/bfg/tests/test_authentication.py b/repoze/bfg/tests/test_authentication.py
index a6f34970f..bce80ca20 100644
--- a/repoze/bfg/tests/test_authentication.py
+++ b/repoze/bfg/tests/test_authentication.py
@@ -416,9 +416,11 @@ class TestAuthTktCookieHelper(unittest.TestCase):
request = self._makeRequest({'HTTP_COOKIE':'auth_tkt=bogus'})
result = plugin.identify(request)
self.failUnless(result)
- response_headers = request.global_response_headers
- self.assertEqual(len(response_headers), 3)
- self.assertEqual(response_headers[0][0], 'Set-Cookie')
+ self.assertEqual(len(request.callbacks), 1)
+ response = DummyResponse()
+ request.callbacks[0](None, response)
+ self.assertEqual(len(response.headers.added), 3)
+ self.assertEqual(response.headers.added[0][0], 'Set-Cookie')
def test_remember(self):
plugin = self._makeOne('secret')
@@ -595,6 +597,10 @@ class DummyContext:
class DummyRequest:
def __init__(self, environ):
self.environ = environ
+ self.callbacks = []
+
+ def add_response_callback(self, callback):
+ self.callbacks.append(callback)
class DummyWhoPlugin:
def remember(self, environ, identity):
@@ -652,3 +658,14 @@ class DummyAuthTktModule(object):
class BadTicket(Exception):
pass
+class DummyHeaders:
+ def __init__(self):
+ self.added = []
+
+ def add(self, k, v):
+ self.added.append((k, v))
+
+class DummyResponse:
+ def __init__(self):
+ self.headers = DummyHeaders()
+
diff --git a/repoze/bfg/tests/test_request.py b/repoze/bfg/tests/test_request.py
index 7b3d0ce7b..8d23e360f 100644
--- a/repoze/bfg/tests/test_request.py
+++ b/repoze/bfg/tests/test_request.py
@@ -157,6 +157,33 @@ class TestRequest(unittest.TestCase):
result = inst.values()
self.assertEqual(result, environ.values())
+ def test_add_response_callback(self):
+ inst = self._makeOne({})
+ self.assertEqual(inst.response_callbacks, ())
+ def callback(request, response):
+ """ """
+ inst.add_response_callback(callback)
+ self.assertEqual(inst.response_callbacks, [callback])
+ inst.add_response_callback(callback)
+ self.assertEqual(inst.response_callbacks, [callback, callback])
+
+ def test__process_response_callbacks(self):
+ inst = self._makeOne({})
+ def callback1(request, response):
+ request.called1 = True
+ response.called1 = True
+ def callback2(request, response):
+ request.called2 = True
+ response.called2 = True
+ inst.response_callbacks = [callback1, callback2]
+ response = DummyResponse()
+ inst._process_response_callbacks(response)
+ self.assertEqual(inst.called1, True)
+ self.assertEqual(inst.called2, True)
+ self.assertEqual(response.called1, True)
+ self.assertEqual(response.called2, True)
+ self.assertEqual(inst.response_callbacks, ())
+
class Test_route_request_iface(unittest.TestCase):
def _callFUT(self, name):
from repoze.bfg.request import route_request_iface
@@ -175,10 +202,11 @@ class Test_add_global_response_headers(unittest.TestCase):
def test_it(self):
request = DummyRequest()
- headers = [('a', 1), ('b', 2)]
- request.global_response_headers = headers[:]
+ response = DummyResponse()
self._callFUT(request, [('c', 1)])
- self.assertEqual(request.global_response_headers, headers + [('c', 1)])
+ self.assertEqual(len(request.response_callbacks), 1)
+ request.response_callbacks[0](None, response)
+ self.assertEqual(response.headers.added, [('c', 1)] )
class DummyRequest:
def __init__(self, environ=None):
@@ -186,10 +214,21 @@ class DummyRequest:
environ = {}
self.environ = environ
+ def add_response_callback(self, callback):
+ self.response_callbacks = [callback]
+
class DummyNewRequestEvent:
def __init__(self, request):
self.request = request
-
+class DummyHeaders:
+ def __init__(self):
+ self.added = []
+ def add(self, k, v):
+ self.added.append((k, v))
+
+class DummyResponse:
+ def __init__(self):
+ self.headers = DummyHeaders()
diff --git a/repoze/bfg/tests/test_router.py b/repoze/bfg/tests/test_router.py
index 6e25b0584..1ce499e41 100644
--- a/repoze/bfg/tests/test_router.py
+++ b/repoze/bfg/tests/test_router.py
@@ -372,7 +372,7 @@ class TestRouter(unittest.TestCase):
why = exc_raised(NotFound, router, environ, start_response)
self.assertEqual(why[0], 'notfound')
- def test_call_request_has_global_response_headers(self):
+ def test_call_request_has_response_callbacks(self):
from zope.interface import Interface
from zope.interface import directlyProvides
class IContext(Interface):
@@ -385,15 +385,16 @@ class TestRouter(unittest.TestCase):
response = DummyResponse('200 OK')
response.headerlist = [('a', 1)]
def view(context, request):
- request.global_response_headers = [('b', 2)]
+ def callback(request, response):
+ response.called_back = True
+ request.response_callbacks = [callback]
return response
environ = self._makeEnviron()
self._registerView(view, '', IViewClassifier, IRequest, IContext)
router = self._makeOne()
start_response = DummyStartResponse()
router(environ, start_response)
- self.assertEqual(start_response.status, '200 OK')
- self.assertEqual(start_response.headers, [('a', 1), ('b', 2)])
+ self.assertEqual(response.called_back, True)
def test_call_eventsends(self):
from repoze.bfg.interfaces import INewRequest
diff --git a/repoze/bfg/tests/test_testing.py b/repoze/bfg/tests/test_testing.py
index b24843e77..b900a44a4 100644
--- a/repoze/bfg/tests/test_testing.py
+++ b/repoze/bfg/tests/test_testing.py
@@ -514,6 +514,12 @@ class TestDummyRequest(unittest.TestCase):
request = self._makeOne(water = 1)
self.assertEqual(request.water, 1)
+ def test_add_response_callback(self):
+ request = self._makeOne()
+ request.add_response_callback(1)
+ self.assertEqual(request.response_callbacks, [1])
+
+
class TestDummyTemplateRenderer(unittest.TestCase):
def _getTargetClass(self, ):
from repoze.bfg.testing import DummyTemplateRenderer