1
2 """
3 Family of classes for maintaining connections with optional subjects.
4 """
5 from itertools import izip, repeat
6 from functools import partial, wraps
7 from Signal import Signal
8 from Util import instance_decorator, monkeypatch, monkeypatch_extend, NamedTuple
9 from Disconnectable import Disconnectable, CompoundDisconnectable
13
23
26 if isinstance(event_name_or_event, str):
27 event = SubjectEvent(name=event_name_or_event)
28 else:
29 event = event_name_or_event
30 raise callable(event.signal) or AssertionError
31 signal_attr = '_' + event.name + '_signal'
32
33 def get_signal(self):
34 try:
35 return getattr(self, signal_attr)
36 except AttributeError:
37 signal = event.signal(sender=self)
38 setattr(self, signal_attr, signal)
39 return signal
40
41 kwargs = dict({'doc': event.doc,
42 'override': event.override})
43
44 @monkeypatch(cls, (event.name + '_has_listener'), **kwargs)
45 def has_method(self, slot):
46 return get_signal(self).is_connected(slot)
47
48 @monkeypatch(cls, ('add_' + event.name + '_listener'), **kwargs)
49 def add_method(self, slot, identify_sender = False, *a, **k):
50 sender = self if identify_sender else None
51 return get_signal(self).connect(slot, sender=sender, *a, **k)
52
53 @monkeypatch(cls, ('remove_' + event.name + '_listener'), **kwargs)
54 def remove_method(self, slot):
55 return get_signal(self).disconnect(slot)
56
57 @monkeypatch(cls, ('notify_' + event.name), **kwargs)
58 def notify_method(self, *a, **k):
59 return get_signal(self)(*a, **k)
60
61 @monkeypatch(cls, ('clear_' + event.name + '_listeners'), **kwargs)
62 def clear_method(self):
63 return get_signal(self).disconnect_all()
64
65 @monkeypatch(cls, (event.name + '_listener_count'), **kwargs)
66 def listener_count_method(self):
67 return get_signal(self).count
68
69 @monkeypatch_extend(cls)
70 def disconnect(self):
71 get_signal(self).disconnect_all()
72
77
89
92 """
93 Base class that installs the SubjectMeta metaclass
94 """
95 __metaclass__ = SubjectMeta
96
99 """
100 Holds references to a number of slots. Disconnecting it clears all
101 of them and unregisters them from the system.
102 """
103
108
113
116 """
117 This class maintains the relationship between a subject and a
118 listener callback. As soon as both are non-null, it connects the
119 listener the given 'event' of the subject and release the connection
120 when any of them change.
121
122 The finalizer of the object also cleans up both parameters and so
123 does the __exit__ override, being able to use it as a context
124 manager with the 'with' clause.
125
126 Note that 'event' is a string with canonical identifier for the
127 listener, i.e., the subject should provide the methods:
128
129 add_[event]_listener
130 remove_[event]_listener
131 [event]_has_listener
132
133 Note that the connection can already be made manually before the
134 subject and listener are fed to the slot.
135 """
136 _extra_kws = {}
137 _extra_args = []
138
139 - def __init__(self, subject = None, listener = None, event = None, extra_kws = None, extra_args = None, *a, **k):
151
159
161 if not callable(getattr(subject, 'add_' + self._event + '_listener', None)):
162 raise SubjectSlotError('Subject %s missing "add" method for event: %s' % (subject, self._event))
163 if not callable(getattr(subject, 'remove_' + self._event + '_listener', None)):
164 raise SubjectSlotError('Subject %s missing "remove" method for event: %s' % (subject, self._event))
165 if not callable(getattr(subject, self._event + '_has_listener', None)):
166 raise SubjectSlotError('Subject %s missing "has" method for event: %s' % (subject, self._event))
167
169 if not self.is_connected and self._subject != None and self._listener != None:
170 add_method = getattr(self._subject, 'add_' + self._event + '_listener')
171 all_args = tuple(self._extra_args) + (self._listener,)
172 add_method(*all_args, **self._extra_kws)
173
175 """
176 Disconnects the listener from the subject keeping their
177 values.
178 """
179 if self.is_connected and self._subject != None and self._listener != None:
180 all_args = tuple(self._extra_args) + (self._listener,)
181 remove_method = getattr(self._subject, 'remove_' + self._event + '_listener')
182 remove_method(*all_args)
183
184 @property
186 all_args = tuple(self._extra_args) + (self._listener,)
187 return bool(self._subject != None and self._listener != None and getattr(self._subject, self._event + '_has_listener')(*all_args))
188
191
199
200 subject = property(_get_subject, _set_subject)
201
203 return self._listener
204
210
211 listener = property(_get_listener, _set_listener)
212
215 """
216 Use this Mixin to turn subject-slot like interfaces into a
217 'callable'. The call operator will be forwarded to the
218 subjectslot.
219 """
220
224
228
231 """
232 A subject slot that connects a given listener to many subjects.
233 """
234 listener = None
235 _extra_kws = None
236 _extra_args = None
237
238 - def __init__(self, listener = None, event = None, extra_kws = None, extra_args = None, *a, **k):
248
253
259
263
266
268 return lambda *a, **k: self.listener and self.listener(*(a + (identifier,)), **k)
269
273
276
277 @instance_decorator
278 def decorator(self, method):
279 raise isinstance(self, SlotManager) or AssertionError
280 slot = wraps(method)(CallableSubjectSlot(event=event, extra_kws=k, extra_args=a, listener=partial(method, self)))
281 self.register_slot(slot)
282 return slot
283
284 return decorator
285
295
296 return decorator
297