summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Petrello <lists@ryanpetrello.com>2014-08-15 16:20:11 -0400
committerRyan Petrello <lists@ryanpetrello.com>2014-08-15 17:23:47 -0400
commit11703d3740aa9ba8b37cb59ff9ff4b29a598130c (patch)
tree4db0be4c792449863823788ca15d27f7dceb00c2
parent01c9a110fc605bbd59f31a341a55d634b3a8119f (diff)
downloadpecan-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
-rw-r--r--pecan/hooks.py11
-rw-r--r--pecan/tests/test_hooks.py40
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'