diff options
author | Ryan Petrello <lists@ryanpetrello.com> | 2014-08-15 16:20:11 -0400 |
---|---|---|
committer | Ryan Petrello <lists@ryanpetrello.com> | 2014-08-15 17:23:47 -0400 |
commit | 11703d3740aa9ba8b37cb59ff9ff4b29a598130c (patch) | |
tree | 4db0be4c792449863823788ca15d27f7dceb00c2 /pecan | |
parent | 01c9a110fc605bbd59f31a341a55d634b3a8119f (diff) | |
download | pecan-11703d3740aa9ba8b37cb59ff9ff4b29a598130c.tar.gz |
Fix an infinite recursion error in PecanHook application.
Subclassing both `rest.RestController` and `hooks.HookController` results in an
infinite recursion error in hook application (which prevents your application
from starting).
Fixes bug 1357540
Change-Id: I6e26c6d8771b4b35943bfb85bf41e73d0982e74c
Diffstat (limited to 'pecan')
-rw-r--r-- | pecan/hooks.py | 11 | ||||
-rw-r--r-- | pecan/tests/test_hooks.py | 40 |
2 files changed, 50 insertions, 1 deletions
diff --git a/pecan/hooks.py b/pecan/hooks.py index 57392d7..f1f7073 100644 --- a/pecan/hooks.py +++ b/pecan/hooks.py @@ -1,3 +1,4 @@ +import types import sys from inspect import getmembers @@ -27,7 +28,15 @@ def walk_controller(root_class, controller, hooks): for hook in hooks: value._pecan.setdefault('hooks', set()).add(hook) elif hasattr(value, '__class__'): - if name.startswith('__') and name.endswith('__'): + # Skip non-exposed methods that are defined in parent classes; + # they're internal implementation details of that class, and + # not actual routable controllers, so we shouldn't bother + # assigning hooks to them. + if ( + isinstance(value, types.MethodType) and + any(filter(lambda c: value.__func__ in c.__dict__.values(), + value.im_class.mro()[1:])) + ): continue walk_controller(root_class, value, hooks) diff --git a/pecan/tests/test_hooks.py b/pecan/tests/test_hooks.py index 0bfd216..d3fe05b 100644 --- a/pecan/tests/test_hooks.py +++ b/pecan/tests/test_hooks.py @@ -1656,3 +1656,43 @@ class TestRequestViewerHook(PecanTestCase): viewer = RequestViewerHook(conf) assert viewer.items == ['url'] + + +class TestRestControllerWithHooks(PecanTestCase): + + def test_restcontroller_with_hooks(self): + + class SomeHook(PecanHook): + + def before(self, state): + state.response.headers['X-Testing'] = 'XYZ' + + class BaseController(rest.RestController): + + @expose() + def delete(self, _id): + return 'Deleting %s' % _id + + class RootController(BaseController, HookController): + + __hooks__ = [SomeHook()] + + @expose() + def get_all(self): + return 'Hello, World!' + + app = TestApp( + make_app( + RootController() + ) + ) + + response = app.get('/') + assert response.status_int == 200 + assert response.body == b_('Hello, World!') + assert response.headers['X-Testing'] == 'XYZ' + + response = app.delete('/100/') + assert response.status_int == 200 + assert response.body == b_('Deleting 100') + assert response.headers['X-Testing'] == 'XYZ' |