Przeglądaj źródła

Added event decorator to further simplify event system

Nuwan Goonasekera 7 lat temu
rodzic
commit
0a48ea533f
2 zmienionych plików z 102 dodań i 7 usunięć
  1. 36 7
      cloudbridge/cloud/base/middleware.py
  2. 66 0
      test/test_middleware_system.py

+ 36 - 7
cloudbridge/cloud/base/middleware.py

@@ -1,3 +1,4 @@
+import functools
 import inspect
 import logging
 import sys
@@ -9,6 +10,7 @@ from ..base.events import InterceptingEventHandler
 from ..base.events import ObservingEventHandler
 from ..interfaces.events import EventHandler
 from ..interfaces.exceptions import CloudBridgeBaseException
+from ..interfaces.exceptions import HandlerException
 from ..interfaces.middleware import Middleware
 from ..interfaces.middleware import MiddlewareManager
 
@@ -21,7 +23,7 @@ def intercept(event_pattern, priority):
         # The callback cannot be set to f as it is not bound yet and will be
         # set during auto discovery
         f.__event_handler = InterceptingEventHandler(
-            event_pattern, priority, None)
+            event_pattern, priority, f)
         return f
     return deco
 
@@ -32,7 +34,7 @@ def observe(event_pattern, priority):
         # The callback cannot be set to f as it is not bound yet and will be
         # set during auto discovery
         f.__event_handler = ObservingEventHandler(
-            event_pattern, priority, None)
+            event_pattern, priority, f)
         return f
     return deco
 
@@ -40,14 +42,40 @@ def observe(event_pattern, priority):
 def implement(event_pattern, priority):
     def deco(f):
         # Mark function as having an event_handler so we can discover it
-        # The callback cannot be set to f as it is not bound yet and will be
-        # set during auto discovery
+        # The callback will be unbound since we do not have access to `self`
+        # yet, and must be bound before invocation. This binding is done
+        # during middleware auto discovery
         f.__event_handler = ImplementingEventHandler(
-            event_pattern, priority, None)
+            event_pattern, priority, f)
         return f
     return deco
 
 
+def dispatch_event(event):
+    """
+    The event decorator combines the functionality of the implement decorator
+    and a manual event dispatch into a single decorator.
+    """
+    def deco(f):
+        @functools.wraps(f)
+        def wrapper(self, *args, **kwargs):
+            events = getattr(self, 'events')
+            if events:
+                # Don't call the wrapped method, just dispatch the event,
+                # and the event handler will get invoked
+                return events.dispatch(self, event, *args, **kwargs)
+            else:
+                raise HandlerException(
+                    "Cannot dispatch event: {0}. The object {1} should have"
+                    " an events property".format(event, self))
+        # Mark function as having an event_handler so we can discover it
+        # The callback f is unbound and will be bound during middleware
+        # auto discovery
+        wrapper.__event_handler = ImplementingEventHandler(event, 2500, f)
+        return wrapper
+    return deco
+
+
 class SimpleMiddlewareManager(MiddlewareManager):
 
     def __init__(self, event_manager):
@@ -117,8 +145,9 @@ class BaseMiddleware(Middleware):
         for _, func in getmembers_static(class_or_obj, inspect.ismethod):
             handler = getattr(func, "__event_handler", None)
             if handler and isinstance(handler, EventHandler):
-                # Set the properly bound method as the callback
-                handler.callback = func
+                # Bind the currently unbound method
+                # and set the bound method as the callback
+                handler.callback = handler.callback.__get__(class_or_obj)
                 discovered_handlers.append(handler)
         return discovered_handlers
 

+ 66 - 0
test/test_middleware_system.py

@@ -5,6 +5,7 @@ from cloudbridge.cloud.base.middleware import BaseMiddleware
 from cloudbridge.cloud.base.middleware import EventDebugLoggingMiddleware
 from cloudbridge.cloud.base.middleware import ExceptionWrappingMiddleware
 from cloudbridge.cloud.base.middleware import SimpleMiddlewareManager
+from cloudbridge.cloud.base.middleware import dispatch_event
 from cloudbridge.cloud.base.middleware import implement
 from cloudbridge.cloud.base.middleware import intercept
 from cloudbridge.cloud.base.middleware import observe
@@ -207,6 +208,71 @@ class MiddlewareSystemTestCase(unittest.TestCase):
             [handler.callback for handler in
              dispatcher.get_handlers_for_event(EVENT_NAME)])
 
+    def test_event_decorator(self):
+        EVENT_NAME = "some.event.occurred"
+
+        class SomeDummyClass(object):
+
+            def __init__(self):
+                self.invocation_order = ""
+                self.events = SimpleEventDispatcher()
+
+            @intercept(event_pattern="some.event.*", priority=900)
+            def my_callback_intcpt(self, event_args, *args, **kwargs):
+                self.invocation_order += "intcpt_"
+                assert 'first_pos_arg' in args
+                assert kwargs.get('a_keyword_arg') == "something"
+                next_handler = event_args.get('next_handler')
+                return next_handler.invoke(event_args, *args, **kwargs)
+
+            @observe(event_pattern="some.event.*", priority=3000)
+            def my_callback_obs(self, event_args, *args, **kwargs):
+                self.invocation_order += "obs"
+                assert 'first_pos_arg' in args
+                assert event_args['result'] == "hello"
+                assert kwargs.get('a_keyword_arg') == "something"
+
+            @dispatch_event(EVENT_NAME)
+            def my_callback_impl(self, *args, **kwargs):
+                self.invocation_order += "impl_"
+                assert 'first_pos_arg' in args
+                assert kwargs.get('a_keyword_arg') == "something"
+                return "hello"
+
+        obj = SomeDummyClass()
+        manager = SimpleMiddlewareManager(obj.events)
+        middleware = manager.add(obj)
+
+        # calling my_implementation should trigger all events
+        result = obj.my_callback_impl(
+            'first_pos_arg', a_keyword_arg='something')
+
+        self.assertEqual(result, "hello")
+        self.assertEqual(obj.invocation_order, "intcpt_impl_obs")
+        callbacks = [handler.callback for handler
+                     in middleware.events.get_handlers_for_event(EVENT_NAME)]
+
+        self.assertNotIn(
+            obj.my_callback_impl, callbacks,
+            "The event impl callback should not be directly contained"
+            " in callbacks to avoid a circular dispatch")
+
+        self.assertEqual(
+            len(set(callbacks).difference(
+                set([obj.my_callback_intcpt,
+                     obj.my_callback_obs]))),
+            1,
+            "The event impl callback should be included in the list of"
+            "  callbacks indirectly")
+
+        manager.remove(middleware)
+
+        # calling my_implementation again should trigger a None response
+        result = obj.my_callback_impl(
+            'first_pos_arg', a_keyword_arg='something')
+
+        self.assertEqual(result, None)
+
 
 class ExceptionWrappingMiddlewareTestCase(unittest.TestCase):