diff options
| -rw-r--r-- | CHANGES.txt | 5 | ||||
| -rw-r--r-- | docs/api/exceptions.rst | 5 | ||||
| -rw-r--r-- | repoze/bfg/exceptions.py | 26 | ||||
| -rw-r--r-- | repoze/bfg/router.py | 3 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_router.py | 30 |
5 files changed, 64 insertions, 5 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 9daf23b0d..f050ab041 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -12,6 +12,11 @@ Features registration of "settings" values obtained via ``repoze.bfg.settings.get_settings()`` for use in unit tests. +- A new exception exists: ``repoze.bfg.exceptions.Respond``. This + exception can be raised during view execution return a response. + This is effectively a goto, useable by code that has no capability + to otherwise return a response. + Bug Fixes --------- diff --git a/docs/api/exceptions.rst b/docs/api/exceptions.rst index 63a3916ae..67ca085f4 100644 --- a/docs/api/exceptions.rst +++ b/docs/api/exceptions.rst @@ -5,7 +5,8 @@ .. automodule:: repoze.bfg.exceptions - .. autoclass:: NotFound - .. autoclass:: Forbidden + .. autoclass:: NotFound + + .. autoclass:: Respond diff --git a/repoze/bfg/exceptions.py b/repoze/bfg/exceptions.py index afd617a2c..bc05fa699 100644 --- a/repoze/bfg/exceptions.py +++ b/repoze/bfg/exceptions.py @@ -22,3 +22,29 @@ class NotFound(Exception): into the WSGI environment under the ``repoze.bfg.message`` key, for availability to the Not Found view.""" +class Respond(Exception): + """\ + Raise this exception during view execution to return a response + immediately without proceeeding any further through the codepath. + Use of this exception is effectively a 'goto': its target is the + exception handler within the :mod:`repoze.bfg' router that catches + the exception and returns a response immediately. Note that + because this exception is caught by the router, it will not + propagate to any WSGI middleware. Note that this exception is + typically only used by the framework itself and by authentication + plugins to the framework. + + The exception must be initialized which a single argument, which + is a :term:`response` object. + + An example: + + .. code-block:: python + :linenos: + + from webob.exc import HTTPFound + from repoze.bfg.exceptions import Respond + response = HTTPFound(location='http://example.com') + raise Respond(response) + """ + diff --git a/repoze/bfg/router.py b/repoze/bfg/router.py index 1b894129b..c87065e14 100644 --- a/repoze/bfg/router.py +++ b/repoze/bfg/router.py @@ -19,6 +19,7 @@ from repoze.bfg.events import NewResponse from repoze.bfg.events import WSGIApplicationCreatedEvent from repoze.bfg.exceptions import Forbidden from repoze.bfg.exceptions import NotFound +from repoze.bfg.exceptions import Respond from repoze.bfg.request import request_factory from repoze.bfg.threadlocal import manager from repoze.bfg.traversal import ModelGraphTraverser @@ -115,6 +116,8 @@ class Router(object): environ = getattr(request, 'environ', {}) environ['repoze.bfg.message'] = msg response = self.notfound_view(context, request) + except Respond, why: + response = why[0] registry.has_listeners and registry.notify(NewResponse(response)) diff --git a/repoze/bfg/tests/test_router.py b/repoze/bfg/tests/test_router.py index 151734b9b..76b33d204 100644 --- a/repoze/bfg/tests/test_router.py +++ b/repoze/bfg/tests/test_router.py @@ -334,7 +334,7 @@ class TestRouter(unittest.TestCase): self.assertEqual(start_response.status, '404 Not Found') self.failUnless('404' in result[0]) - def test_call_view_raises_unauthorized(self): + def test_call_view_raises_forbidden(self): from zope.interface import Interface from zope.interface import directlyProvides class IContext(Interface): @@ -372,6 +372,25 @@ class TestRouter(unittest.TestCase): self.assertEqual(start_response.status, '404 Not Found') self.assertEqual(environ['repoze.bfg.message'], 'notfound') + def test_call_view_raises_respond(self): + from zope.interface import Interface + from zope.interface import directlyProvides + class IContext(Interface): + pass + from repoze.bfg.interfaces import IRequest + context = DummyContext() + directlyProvides(context, IContext) + self._registerTraverserFactory(context, subpath=['']) + response = DummyResponse('200 OK') + raised = DummyResponse('201 Created') + view = DummyView(response, raise_respond=raised) + environ = self._makeEnviron() + self._registerView(view, '', IContext, IRequest) + router = self._makeOne() + start_response = DummyStartResponse() + response = router(environ, start_response) + self.assertEqual(start_response.status, '201 Created') + def test_call_eventsends(self): context = DummyContext() self._registerTraverserFactory(context) @@ -466,10 +485,11 @@ class DummyContext: class DummyView: def __init__(self, response, raise_unauthorized=False, - raise_notfound=False): + raise_notfound=False, raise_respond=False): self.response = response self.raise_unauthorized = raise_unauthorized self.raise_notfound = raise_notfound + self.raise_respond = raise_respond def __call__(self, context, request): if self.raise_unauthorized: @@ -478,6 +498,9 @@ class DummyView: if self.raise_notfound: from repoze.bfg.exceptions import NotFound raise NotFound('notfound') + if self.raise_respond: + from repoze.bfg.exceptions import Respond + raise Respond(self.raise_respond) return self.response class DummyRootFactory: @@ -495,9 +518,10 @@ class DummyStartResponse: self.headers = headers class DummyResponse: - status = '200 OK' headerlist = () app_iter = () + def __init__(self, status='200 OK'): + self.status = status class DummyThreadLocalManager: def __init__(self): |
