middleware.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import functools
  2. import inspect
  3. import logging
  4. import sys
  5. import six
  6. from ..base.events import ImplementingEventHandler
  7. from ..base.events import InterceptingEventHandler
  8. from ..base.events import ObservingEventHandler
  9. from ..base.events import PlaceHoldingEventHandler
  10. from ..interfaces.exceptions import CloudBridgeBaseException
  11. from ..interfaces.exceptions import HandlerException
  12. from ..interfaces.middleware import Middleware
  13. from ..interfaces.middleware import MiddlewareManager
  14. log = logging.getLogger(__name__)
  15. def intercept(event_pattern, priority):
  16. def deco(f):
  17. # Mark function as having an event_handler so we can discover it
  18. # The callback cannot be set to f as it is not bound yet and will be
  19. # set during auto discovery
  20. f.__event_handler = PlaceHoldingEventHandler(
  21. event_pattern, priority, f, InterceptingEventHandler)
  22. return f
  23. return deco
  24. def observe(event_pattern, priority):
  25. def deco(f):
  26. # Mark function as having an event_handler so we can discover it
  27. # The callback cannot be set to f as it is not bound yet and will be
  28. # set during auto discovery
  29. f.__event_handler = PlaceHoldingEventHandler(
  30. event_pattern, priority, f, ObservingEventHandler)
  31. return f
  32. return deco
  33. def implement(event_pattern, priority):
  34. def deco(f):
  35. # Mark function as having an event_handler so we can discover it
  36. # The callback will be unbound since we do not have access to `self`
  37. # yet, and must be bound before invocation. This binding is done
  38. # during middleware auto discovery
  39. f.__event_handler = PlaceHoldingEventHandler(
  40. event_pattern, priority, f, ImplementingEventHandler)
  41. return f
  42. return deco
  43. def dispatch(event, priority):
  44. """
  45. The event decorator combines the functionality of the implement decorator
  46. and a manual event dispatch into a single decorator.
  47. """
  48. def deco(f):
  49. @functools.wraps(f)
  50. def wrapper(self, *args, **kwargs):
  51. events = getattr(self, 'events', None)
  52. if events:
  53. # Don't call the wrapped method, just dispatch the event,
  54. # and the event handler will get invoked
  55. return events.dispatch(self, event, *args, **kwargs)
  56. else:
  57. raise HandlerException(
  58. "Cannot dispatch event: {0}. The object {1} should have"
  59. " an events property".format(event, self))
  60. # Mark function as having an event_handler so we can discover it
  61. # The callback f is unbound and will be bound during middleware
  62. # auto discovery
  63. wrapper.__event_handler = PlaceHoldingEventHandler(
  64. event, priority, f, ImplementingEventHandler)
  65. return wrapper
  66. return deco
  67. class SimpleMiddlewareManager(MiddlewareManager):
  68. def __init__(self, event_manager):
  69. self.events = event_manager
  70. self.middleware_list = []
  71. def add(self, middleware):
  72. if isinstance(middleware, Middleware):
  73. m = middleware
  74. else:
  75. m = AutoDiscoveredMiddleware(middleware)
  76. m.install(self.events)
  77. self.middleware_list.append(m)
  78. return m
  79. def remove(self, middleware):
  80. middleware.uninstall()
  81. self.middleware_list.remove(middleware)
  82. class BaseMiddleware(Middleware):
  83. def __init__(self):
  84. self.event_handlers = []
  85. self.events = None
  86. def install(self, event_manager):
  87. self.events = event_manager
  88. discovered_handlers = self.discover_handlers(self)
  89. self.add_handlers(discovered_handlers)
  90. def add_handlers(self, handlers):
  91. if not hasattr(self, "event_handlers"):
  92. # In case the user forgot to call super class init
  93. self.event_handlers = []
  94. for handler in handlers:
  95. self.events.subscribe(handler)
  96. self.event_handlers.extend(handlers)
  97. def uninstall(self):
  98. for handler in self.event_handlers:
  99. handler.unsubscribe()
  100. self.event_handlers = []
  101. self.events = None
  102. @staticmethod
  103. def discover_handlers(class_or_obj):
  104. # https://bugs.python.org/issue30533
  105. # simulating a getmembers_static to be easily replaced with the
  106. # function if they add it to inspect module
  107. def getmembers_static(obj, predicate=None):
  108. results = []
  109. for key in dir(obj):
  110. if not inspect.isdatadescriptor(getattr(obj.__class__,
  111. key,
  112. None)):
  113. try:
  114. value = getattr(obj, key)
  115. except AttributeError:
  116. continue
  117. if not predicate or predicate(value):
  118. results.append((key, value))
  119. return results
  120. discovered_handlers = []
  121. for _, func in getmembers_static(class_or_obj, inspect.ismethod):
  122. handler = getattr(func, "__event_handler", None)
  123. if handler and isinstance(handler, PlaceHoldingEventHandler):
  124. # create a new handler that mimics the original one,
  125. # essentially deep-copying the handler, so that the bound
  126. # method is never stored in the function itself, preventing
  127. # further bonding
  128. new_handler = handler.handler_class(handler.event_pattern,
  129. handler.priority,
  130. handler.callback)
  131. # Bind the currently unbound method
  132. # and set the bound method as the callback
  133. new_handler.callback = (new_handler.callback
  134. .__get__(class_or_obj))
  135. discovered_handlers.append(new_handler)
  136. return discovered_handlers
  137. class AutoDiscoveredMiddleware(BaseMiddleware):
  138. def __init__(self, class_or_obj):
  139. super(AutoDiscoveredMiddleware, self).__init__()
  140. self.obj_to_discover = class_or_obj
  141. def install(self, event_manager):
  142. super(AutoDiscoveredMiddleware, self).install(event_manager)
  143. discovered_handlers = self.discover_handlers(self.obj_to_discover)
  144. self.add_handlers(discovered_handlers)
  145. class EventDebugLoggingMiddleware(BaseMiddleware):
  146. """
  147. Logs all event parameters. This middleware should not be enabled other
  148. than for debugging, as it could log sensitive parameters such as
  149. access keys.
  150. """
  151. @observe(event_pattern="*", priority=100)
  152. def pre_log_event(self, event_args, *args, **kwargs):
  153. log.debug("Event: {0}, args: {1} kwargs: {2}".format(
  154. event_args.get("event"), args, kwargs))
  155. @observe(event_pattern="*", priority=4900)
  156. def post_log_event(self, event_args, *args, **kwargs):
  157. log.debug("Event: {0}, result: {1}".format(
  158. event_args.get("event"), event_args.get("result")))
  159. class ExceptionWrappingMiddleware(BaseMiddleware):
  160. """
  161. Wraps all unhandled exceptions in cloudbridge exceptions.
  162. """
  163. @intercept(event_pattern="*", priority=1050)
  164. def wrap_exception(self, event_args, *args, **kwargs):
  165. next_handler = event_args.pop("next_handler")
  166. if not next_handler:
  167. return
  168. try:
  169. return next_handler.invoke(event_args, *args, **kwargs)
  170. except Exception as e:
  171. if isinstance(e, CloudBridgeBaseException):
  172. raise
  173. else:
  174. ex_type, ex_value, traceback = sys.exc_info()
  175. cb_ex = CloudBridgeBaseException(
  176. "CloudBridgeBaseException: {0} from exception type: {1}"
  177. .format(ex_value, ex_type))
  178. if sys.version_info >= (3, 0):
  179. six.raise_from(cb_ex, e)
  180. else:
  181. six.reraise(CloudBridgeBaseException, cb_ex, traceback)