summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt5
-rw-r--r--docs/api/exceptions.rst5
-rw-r--r--repoze/bfg/exceptions.py26
-rw-r--r--repoze/bfg/router.py3
-rw-r--r--repoze/bfg/tests/test_router.py30
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):