diff options
author | Kiall Mac Innes <kiall@hp.com> | 2013-07-19 11:14:42 +0100 |
---|---|---|
committer | Kiall Mac Innes <kiall@hp.com> | 2013-07-20 12:03:28 +0100 |
commit | 2b505a5a246f51e74a5e7e0a610a70e7bbaec53d (patch) | |
tree | d9224c8368cc9c204d23b97a478fa8b75a0bc969 | |
parent | f5269b65e2f318e6cb17f5737ad838df25d7ac00 (diff) | |
download | pecan-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.rst | 6 | ||||
-rw-r--r-- | pecan/core.py | 17 | ||||
-rw-r--r-- | pecan/tests/test_hooks.py | 30 |
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 = [] |