forked from joshuag/speakerbot
-
Notifications
You must be signed in to change notification settings - Fork 0
/
listenable.py
124 lines (75 loc) · 3.74 KB
/
listenable.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
from dynamic_class import Singleton
class NotEventException(Exception):
pass
class GlobalEventDispatcher(object):
"""not quite there yet"""
__metaclass__ = Singleton
def __init__(self):
pass
def event(method):
"""Must be called first in a decorator chain"""
def wrapped(*args, **kwargs):
self = args[0]
print "into the wrapper"
if self.dispatch_events(self._interrogators, method.__name__, *args, **kwargs):
args, kwargs = self.run_manglers(method.__name__, *args, **kwargs)
result = method(*args, **kwargs)
kwargs["event_result"] = result
self.dispatch_events(self._listeners, method.__name__, *args, **kwargs)
return result
wrapped.is_event = True
method.is_event = True
return wrapped
def listenable(klass):
"""
Class decorator to implement a lightweight event-dispatch model.
@listenable on the class
@event on the method you want to monitor
listeners must implement the function signature of the event exactly (or take *args, **kwargs generically),
plus a special argument called "event_result" that contains the return value of the method invocation.
TODO: Make it work with other decorators, inheritance
"""
def _attach(self, event, func, handler_collection_name):
#if not hasattr(getattr(self, event), "is_event"):
# raise NotEventException("This method hasn't been decorated as an event listener")
handler_collection = getattr(self, handler_collection_name)
handlers = handler_collection.get(event, [])
handlers.append(func)
handler_collection[event] = handlers
setattr(self, handler_collection_name, handler_collection)
def attach_interrogator(self, event, interrogator):
_attach(self, event, interrogator, "_interrogators")
def attach_listener(self, event, listener):
_attach(self, event, listener, "_listeners")
def attach_mangler(self, event, listener):
_attach(self, event, listener, "_manglers")
def run_manglers(self, method_name, *args, **kwargs):
for mangler in self._manglers.get(method_name, []):
try:
#pop off the instance information. We just want the function signature
args, kwargs = mangler(*args[1:], **kwargs)
except Exception as e:
print "Argument mangler %s failed with exception %s. It reported the following: %s" % (mangler.__name__, e.__class__.__name__, str(e))
return args, kwargs
def dispatch_events(self, handler_collection, method_name, *args, **kwargs):
please_do_continue = True
for handler in handler_collection.get(method_name, []):
try:
#pop off the instance information. We just want the function signature
please_do_continue = handler(*args[1:], **kwargs)
if please_do_continue == None:
please_do_continue = True
if not please_do_continue:
break
except Exception as e:
print "Event listener %s failed with exception %s. It reported the following: %s" % (handler.__name__, e.__class__.__name__, str(e))
return please_do_continue
setattr(klass, "_listeners", {})
setattr(klass, "_interrogators", {})
setattr(klass, "_manglers", {})
setattr(klass, "attach_listener", attach_listener)
setattr(klass, "attach_interrogator", attach_interrogator)
setattr(klass, "attach_mangler", attach_mangler)
setattr(klass, "dispatch_events", dispatch_events)
setattr(klass, "run_manglers", run_manglers)
return klass