summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTihomir Trifonov <t.trifonov@gmail.com>2014-06-17 00:06:51 +0300
committerTihomir Trifonov <t.trifonov@gmail.com>2014-06-17 00:09:10 +0300
commit12d4a34ca4710b991805ae39a1f107594c0168ac (patch)
tree2e9f15e26d893de865c1c7eb01b06a12518e3112
parent9bc09ea9c6d0cf311c508e6e0a4dce8df9855384 (diff)
downloadpecan-12d4a34ca4710b991805ae39a1f107594c0168ac.tar.gz
Added inheritance for hooks from parent classes
also added hook inheritance from mixins, and adding hooks to child controllers, added as sub-controllers. Fixes bug 1330673 Change-Id: I709cece7bcce26943b254b15dc8ddac5613b1202
-rw-r--r--docs/source/hooks.rst101
-rw-r--r--pecan/hooks.py13
-rw-r--r--pecan/tests/test_hooks.py72
3 files changed, 182 insertions, 4 deletions
diff --git a/docs/source/hooks.rst b/docs/source/hooks.rst
index 7796189..18a530f 100644
--- a/docs/source/hooks.rst
+++ b/docs/source/hooks.rst
@@ -130,9 +130,106 @@ when we run the app and browse the application from our web browser.
about to enter the controller...
DO SOMETHING!
- method: GET
- response: 200 OK
+ method: GET
+ response: 200 OK
+Hooks can be inherited from parent class or mixins. Just make sure to
+subclass from :class:`~pecan.hooks.HookController`.
+
+::
+
+ from pecan import expose
+ from pecan.hooks import PecanHook, HookController
+
+ class ParentHook(PecanHook):
+
+ priority = 1
+
+ def before(self, state):
+ print "\nabout to enter the parent controller..."
+
+ class CommonHook(PecanHook):
+
+ priority = 2
+
+ def before(self, state):
+ print "\njust a common hook..."
+
+ class SubHook(PecanHook):
+
+ def before(self, state):
+ print "\nabout to enter the subcontroller..."
+
+ class SubMixin(object):
+ __hooks__ = [SubHook()]
+
+ # We'll use the same instance for both controllers,
+ # to avoid double calls
+ common = CommonHook()
+
+ class SubController(HookController, SubMixin):
+
+ __hooks__ = [common]
+
+ @expose('json')
+ def index(self):
+ print "\nI AM THE SUB!"
+ return dict()
+
+ class RootController(HookController):
+
+ __hooks__ = [common, ParentHook()]
+
+ @expose('json')
+ def index(self):
+ print "\nI AM THE ROOT!"
+ return dict()
+
+ sub = SubController()
+
+Let's see what happens when we run the app.
+First loading the root controller:
+
+::
+
+ pecan serve config.py
+ serving on 0.0.0.0:8080 view at http://127.0.0.1:8080
+
+ GET / HTTP/1.1" 200
+
+ about to enter the parent controller...
+
+ just a common hook
+
+ I AM THE ROOT!
+
+Then loading the sub controller:
+
+::
+
+ pecan serve config.py
+ serving on 0.0.0.0:8080 view at http://127.0.0.1:8080
+
+ GET /sub HTTP/1.1" 200
+
+ about to enter the parent controller...
+
+ just a common hook
+
+ about to enter the subcontroller...
+
+ I AM THE SUB!
+
+.. note::
+
+ Make sure to set proper priority values for nested hooks in order
+ to get them executed in the desired order.
+
+.. warning::
+
+ Two hooks of the same type will be added/executed twice, if passed as
+ different instances to a parent and a child controller.
+ If passed as one instance variable - will be invoked once for both controllers.
Hooks That Come with Pecan
--------------------------
diff --git a/pecan/hooks.py b/pecan/hooks.py
index 4ceeb42..27dc872 100644
--- a/pecan/hooks.py
+++ b/pecan/hooks.py
@@ -14,6 +14,10 @@ __all__ = [
def walk_controller(root_class, controller, hooks):
if not isinstance(controller, (int, dict)):
+ for hook in getattr(controller, '__hooks__', []):
+ # Append hooks from controller class definition
+ hooks.add(hook)
+
for name, value in getmembers(controller):
if name == 'controller':
continue
@@ -22,7 +26,7 @@ def walk_controller(root_class, controller, hooks):
if iscontroller(value):
for hook in hooks:
- value._pecan.setdefault('hooks', []).append(hook)
+ value._pecan.setdefault('hooks', set()).add(hook)
elif hasattr(value, '__class__'):
if name.startswith('__') and name.endswith('__'):
continue
@@ -37,7 +41,12 @@ class HookControllerMeta(type):
'''
def __init__(cls, name, bases, dict_):
- walk_controller(cls, cls, dict_.get('__hooks__', []))
+ hooks = set(dict_.get('__hooks__', []))
+ for base in bases:
+ # Add hooks from parent class and mixins
+ for hook in getattr(base, '__hooks__', []):
+ hooks.add(hook)
+ walk_controller(cls, cls, hooks)
HookController = HookControllerMeta(
diff --git a/pecan/tests/test_hooks.py b/pecan/tests/test_hooks.py
index 44963bf..8f1ca39 100644
--- a/pecan/tests/test_hooks.py
+++ b/pecan/tests/test_hooks.py
@@ -339,6 +339,78 @@ class TestHooks(PecanTestCase):
assert run_hook[4] == 'after1'
assert run_hook[5] == 'after2'
+ def test_mixin_hooks(self):
+ run_hook = []
+
+ class HelperHook(PecanHook):
+ priority = 2
+
+ def before(self, state):
+ run_hook.append('helper - before hook')
+
+ # we'll use the same hook instance to avoid duplicate calls
+ helper_hook = HelperHook()
+
+ class LastHook(PecanHook):
+ priority = 200
+
+ def before(self, state):
+ run_hook.append('last - before hook')
+
+ class SimpleHook(PecanHook):
+ priority = 1
+
+ def before(self, state):
+ run_hook.append('simple - before hook')
+
+ class HelperMixin(object):
+ __hooks__ = [helper_hook]
+
+ class LastMixin(object):
+ __hooks__ = [LastHook()]
+
+ class SubController(HookController, HelperMixin):
+ __hooks__ = [LastHook()]
+
+ @expose()
+ def index(self):
+ return "This is sub controller!"
+
+ class RootController(HookController, LastMixin):
+ __hooks__ = [SimpleHook(), helper_hook]
+
+ @expose()
+ def index(self):
+ run_hook.append('inside')
+ return 'Hello, World!'
+
+ sub = SubController()
+
+ papp = make_app(RootController())
+ app = TestApp(papp)
+ response = app.get('/')
+ assert response.status_int == 200
+ assert response.body == b_('Hello, World!')
+
+ assert len(run_hook) == 4
+ assert run_hook[0] == 'simple - before hook', run_hook[0]
+ assert run_hook[1] == 'helper - before hook', run_hook[1]
+ assert run_hook[2] == 'last - before hook', run_hook[2]
+ assert run_hook[3] == 'inside', run_hook[3]
+
+ run_hook = []
+ response = app.get('/sub/')
+ assert response.status_int == 200
+ assert response.body == b_('This is sub controller!')
+
+ assert len(run_hook) == 4, run_hook
+ assert run_hook[0] == 'simple - before hook', run_hook[0]
+ assert run_hook[1] == 'helper - before hook', run_hook[1]
+ assert run_hook[2] == 'last - before hook', run_hook[2]
+ # LastHook is invoked once again -
+ # for each different instance of the Hook in the two Controllers
+ assert run_hook[3] == 'last - before hook', run_hook[3]
+
class TestTransactionHook(PecanTestCase):
def test_transaction_hook(self):