summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKiall Mac Innes <kiall@hp.com>2013-07-19 11:14:42 +0100
committerKiall Mac Innes <kiall@hp.com>2013-07-20 12:03:28 +0100
commit2b505a5a246f51e74a5e7e0a610a70e7bbaec53d (patch)
treed9224c8368cc9c204d23b97a478fa8b75a0bc969
parentf5269b65e2f318e6cb17f5737ad838df25d7ac00 (diff)
downloadpecan-2b505a5a246f51e74a5e7e0a610a70e7bbaec53d.tar.gz
Allow on_error hooks to return a Response.
This allows on_error hooks to prevent the exception from being re-raised, and instead causes the returned Response to be provided to the end user. The first on_error hook to return a response "wins", and the remaining on_error hooks are not ran.
-rw-r--r--docs/source/hooks.rst6
-rw-r--r--pecan/core.py17
-rw-r--r--pecan/tests/test_hooks.py30
3 files changed, 47 insertions, 6 deletions
diff --git a/docs/source/hooks.rst b/docs/source/hooks.rst
index 9a9d099..e1c54c4 100644
--- a/docs/source/hooks.rst
+++ b/docs/source/hooks.rst
@@ -46,8 +46,10 @@ object which includes useful information, such as
the request and response objects, and which controller was selected by
Pecan's routing.
-:func:`on_error` is passed a shared state object **and** the original exception.
-
+:func:`on_error` is passed a shared state object **and** the original exception. If
+an :func:`on_error` handler returns a Response object, this response will be returned
+to the end user and no furthur :func:`on_error` hooks will be executed.
+
Attaching Hooks
---------------
diff --git a/pecan/core.py b/pecan/core.py
index c09763d..29930b6 100644
--- a/pecan/core.py
+++ b/pecan/core.py
@@ -299,7 +299,11 @@ class Pecan(object):
hooks = reversed(state.hooks)
for hook in hooks:
- getattr(hook, hook_type)(*args)
+ result = getattr(hook, hook_type)(*args)
+ # on_error hooks can choose to return a Response, which will
+ # be used instead of the standard error pages.
+ if hook_type == 'on_error' and isinstance(result, Response):
+ return result
def get_args(self, req, all_params, remainder, argspec, im_self):
'''
@@ -564,11 +568,16 @@ class Pecan(object):
environ['pecan.original_exception'] = e
# if this is not an internal redirect, run error hooks
+ on_error_result = None
if not isinstance(e, ForwardRequestException):
- self.handle_hooks('on_error', state, e)
+ on_error_result = self.handle_hooks('on_error', state, e)
- if not isinstance(e, exc.HTTPException):
- raise
+ # if the on_error handler returned a Response, use it.
+ if isinstance(on_error_result, Response):
+ state.response = on_error_result
+ else:
+ if not isinstance(e, exc.HTTPException):
+ raise
finally:
# handle "after" hooks
self.handle_hooks('after', state)
diff --git a/pecan/tests/test_hooks.py b/pecan/tests/test_hooks.py
index 66947ca..246af76 100644
--- a/pecan/tests/test_hooks.py
+++ b/pecan/tests/test_hooks.py
@@ -1,7 +1,10 @@
from webtest import TestApp
from six import b as b_
+from six import u as u_
from six.moves import cStringIO as StringIO
+from webob import Response
+
from pecan import make_app, expose, redirect, abort
from pecan.hooks import (
PecanHook, TransactionHook, HookController, RequestViewerHook
@@ -133,6 +136,33 @@ class TestHooks(PecanTestCase):
assert run_hook[0] == 'on_route'
assert run_hook[1] == 'error'
+ def test_on_error_response_hook(self):
+ run_hook = []
+
+ class RootController(object):
+ @expose()
+ def causeerror(self):
+ return [][1]
+
+ class ErrorHook(PecanHook):
+ def on_error(self, state, e):
+ run_hook.append('error')
+
+ r = Response()
+ r.text = u_('on_error')
+
+ return r
+
+ app = TestApp(make_app(RootController(), hooks=[
+ ErrorHook()
+ ]))
+
+ response = app.get('/causeerror')
+
+ assert len(run_hook) == 1
+ assert run_hook[0] == 'error'
+ assert response.text == 'on_error'
+
def test_prioritized_hooks(self):
run_hook = []