Package _Framework :: Module SubjectSlot
[hide private]
[frames] | no frames]

Source Code for Module _Framework.SubjectSlot

  1  #Embedded file name: /Users/versonator/Hudson/live/Projects/AppLive/Resources/MIDI Remote Scripts/_Framework/SubjectSlot.py 
  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 
10 11 -class SubjectSlotError(Exception):
12 pass
13
14 15 -class SubjectEvent(NamedTuple):
16 """ 17 Description of a subject event 18 """ 19 name = None 20 doc = '' 21 signal = Signal 22 override = False
23
24 25 -def subject_add_event(cls, event_name_or_event):
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
73 74 -def setup_subject(cls, listeners):
75 for lst in listeners: 76 subject_add_event(cls, lst)
77
78 79 -class SubjectMeta(type):
80
81 - def __new__(cls, name, bases, dct):
82 events = dct.get('__subject_events__', []) 83 if events and 'disconnect' not in dct: 84 dct['disconnect'] = lambda self: super(cls, self).disconnect() 85 cls = super(SubjectMeta, cls).__new__(cls, name, bases, dct) 86 raise not events or hasattr(cls, 'disconnect') or AssertionError 87 setup_subject(cls, events) 88 return cls
89
90 91 -class Subject(Disconnectable):
92 """ 93 Base class that installs the SubjectMeta metaclass 94 """ 95 __metaclass__ = SubjectMeta
96
97 98 -class SlotManager(CompoundDisconnectable):
99 """ 100 Holds references to a number of slots. Disconnecting it clears all 101 of them and unregisters them from the system. 102 """ 103
104 - def register_slot(self, *a, **k):
105 slot = a[0] if a and isinstance(a[0], SubjectSlot) else SubjectSlot(*a, **k) 106 self.register_disconnectable(slot) 107 return slot
108
109 - def register_slot_manager(self, *a, **k):
110 manager = a[0] if a and isinstance(a[0], SlotManager) else SlotManager(*a, **k) 111 self.register_disconnectable(manager) 112 return manager
113
114 115 -class SubjectSlot(Disconnectable):
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):
140 super(SubjectSlot, self).__init__(*a, **k) 141 if not event: 142 raise AssertionError 143 self._event = event 144 if extra_kws is not None: 145 self._extra_kws = extra_kws 146 self._extra_args = extra_args is not None and extra_args 147 self._subject = None 148 self._listener = None 149 self.subject = subject 150 self.listener = listener
151
152 - def disconnect(self):
153 """ 154 Disconnects the slot clearing its members. 155 """ 156 self.subject = None 157 self.listener = None 158 super(SubjectSlot, self).disconnect()
159
160 - def _check_subject_interface(self, subject):
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
168 - def connect(self):
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
174 - def soft_disconnect(self):
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
185 - def is_connected(self):
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
189 - def _get_subject(self):
190 return self._subject
191
192 - def _set_subject(self, subject):
193 if subject != self._subject: 194 if subject != None: 195 self._check_subject_interface(subject) 196 self.soft_disconnect() 197 self._subject = subject 198 self.connect()
199 200 subject = property(_get_subject, _set_subject) 201
202 - def _get_listener(self):
203 return self._listener
204
205 - def _set_listener(self, listener):
206 if listener != self._listener: 207 self.soft_disconnect() 208 self._listener = listener 209 self.connect()
210 211 listener = property(_get_listener, _set_listener)
212
213 214 -class CallableSlotMixin(object):
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
221 - def __call__(self, *a, **k):
222 raise self.listener or AssertionError 223 return self.listener(*a, **k)
224
225 226 -class CallableSubjectSlot(SubjectSlot, CallableSlotMixin):
227 pass
228
229 230 -class SubjectSlotGroup(SlotManager):
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):
239 super(SubjectSlotGroup, self).__init__(*a, **k) 240 self.listener = listener 241 self._event = event 242 if listener is not None: 243 self.listener = listener 244 if extra_kws is not None: 245 self._extra_kws = extra_kws 246 if extra_args is not None: 247 self._extra_args = extra_args
248
249 - def replace_subjects(self, subjects, identifiers = repeat(None)):
250 self.disconnect() 251 for subject, identifier in izip(subjects, identifiers): 252 self.add_subject(subject, identifier=identifier)
253
254 - def add_subject(self, subject, identifier = None):
255 if identifier is None: 256 identifier = subject 257 listener = self._listener_for_subject(identifier) 258 self.register_slot(subject, listener, self._event, self._extra_kws, self._extra_args)
259
260 - def remove_subject(self, subject):
261 slot = self.find_disconnectable(lambda x: x.subject == subject) 262 self.disconnect_disconnectable(slot)
263
264 - def has_subject(self, subject):
265 return self.find_disconnectable(lambda x: x.subject == subject) != None
266
267 - def _listener_for_subject(self, identifier):
268 return lambda *a, **k: self.listener and self.listener(*(a + (identifier,)), **k)
269
270 271 -class CallableSubjectSlotGroup(SubjectSlotGroup, CallableSlotMixin):
272 pass
273
274 275 -def subject_slot(event, *a, **k):
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
286 287 -def subject_slot_group(event, *a, **k):
288 289 @instance_decorator 290 def decorator(self, method): 291 raise isinstance(self, SlotManager) or AssertionError 292 slot = wraps(method)(CallableSubjectSlotGroup(event=event, extra_kws=k, extra_args=a, listener=partial(method, self))) 293 self.register_slot_manager(slot) 294 return slot
295 296 return decorator 297