added the locals and globals methods to the pli.misc.namespace.NameSpace class...
[pli.git] / pli / event / event.py
blob913c10e6f8f3934dbcd4030ece8961c62475b1d7
1 #=======================================================================
3 #-----------------------------------------------------------------------
5 __version__ = '''0.3.15'''
6 __sub_version__ = '''20040530032430'''
7 __copyright__ = '''(c) Alex A. Naanou 2003-2004'''
10 #-----------------------------------------------------------------------
12 __doc__ = '''\
13 This module defines the event framework.
15 Basic concepts:
16 Event:
17 An object that represents a hook-point (bind-point), and controls
18 the execution of its hooks/handlers.
19 When an event is *fired* all registered (or *bound*) handlers are
20 called. The handler execution model depends on the event class
21 used. In general the handlers are executed sequentially in no
22 particular order.
23 It is guaranteed that all handlers of an event are called (except
24 when error suppression is disabled (see next..)).
25 Event handler/callback/hook:
26 A routine bound to a particular event (one or more) and called
27 when/if the event is fired.
28 If the handler of an event raises an error, this error is ignored.
29 (unless the __suppress_exceptions__ attribute of either the handler
30 or the event is set).
32 '''
36 #-----------------------------------------------------------------------
38 import pli.objutils as objutils
42 #-----------------------------------------------------------------------
43 #----------------------------------------------------------EventError---
44 class EventError(Exception):
45 '''
46 a general event error.
47 '''
49 #------------------------------------------------------EventBindError---
50 class EventBindError(EventError):
51 '''
52 an event bind error.
53 '''
56 #-----------------------------------------------------------------------
57 #----------------------------------------------------------------bind---
58 def bind(event, func, HOOK_DEBUG=False):
59 '''
60 register func as the event callback.
62 HOOK_DEBUG will enable exception propagation from event handlers.
63 '''
64 if isevent(event):
65 if HOOK_DEBUG:
66 try:
67 # NOTE: this does not pickle correctly...
68 func.__suppress_exceptions__ = False
69 except:
70 pass
71 event.installhook(func)
72 else:
73 raise EventBindError, 'can\'t bind to non-event object %s.' % event
76 #--------------------------------------------------------------unbind---
77 def unbind(event, func):
78 '''
79 unregister func as the event callback.
80 '''
81 if isevent(event):
82 event.uninstallhook(func)
83 else:
84 raise EventBindError, 'can\'t unbind from non-event object %s.' % event
87 #-------------------------------------------------------------isbound---
88 def isbound(event, func):
89 '''
90 test if func is bound to event.
91 '''
92 if isevent(event):
93 return func in event.__eventhooks__
94 else:
95 raise EventError, 'object %s is not an event.' % event
98 #----------------------------------------------------------------fire---
99 def fire(event, *p, **n):
101 fire the event.
103 shortcut for <event>.fire(...)
105 if isevent(event):
106 event.fire(*p, **n)
107 else:
108 raise EventError, 'can\'t fire a non-event object %s.' % event
111 #-------------------------------------------------------------isevent---
112 def isevent(obj):
114 check if obj is an event (e.g. related to AbstractEvent)
116 return isinstance(obj, AbstractEvent) or issubclass(obj, AbstractEvent)
120 #-----------------------------------------------------------------------
121 #-------------------------------------------------------AbstractEvent---
122 class AbstractEvent(object):
124 this should be the root of all events and event-like objects. used
125 mainly for isevent and the like...
129 #--------------------------------------------------------InstanceEvent---
130 class InstanceEvent(AbstractEvent):
132 this class defines basic instance event.
134 This can be considered an event factory (each instance of this class
135 is an event).
137 This is intended for use as a base event class, to create multiple
138 similar events, with the ability to bind to each *micro* event.
140 event configuration attributes:
141 __auto_uninstall__ : if this is set and the unsource method
142 is present the event handler will
143 automaticly be uninstalled when the
144 last hook/callback is removed.
145 default: True
146 __inherit_source__ : if this is True and the event does not
147 have a source it will be inherited from
148 its parent.
149 default: False
150 __strict_source__ : this if set will prevent an event from
151 being created without a source.
152 default: False
153 __max_handler_count__ : this defines the maximum number if
154 handlers per event (None for no limit)
155 default: None
156 __unique_handler_call__ : if this is true a handler may be
157 installed only once.
158 default: True
159 __unique_handler_bind__ : if this is true the ability to bind
160 the same handler to this event will
161 be disabled (on repeated bind
162 EventBindError will be raised)
163 default: False
164 __suppress_exceptions__ : this is true will supress exceptions
165 generated by event handlers, thus will
166 guarantee that evry handler will get
167 called.
168 NOTE: this might be rather traitorous
169 as all errors are supressed and
170 ignored.
171 NOTE: all of the above attributes are optional, and most of them
172 can be changed on-the-fly...
174 event general interface methods:
175 source() : this is the event installation method.
176 this method is called automaticly on
177 first handler bind, and usually is used
178 to install the event callback that will
179 fire the event.
180 by default this is obligatory (see the
181 __strict_source__, __inherit_source__
182 options).
183 if this is not defined, one must fire
184 the event manually.
185 unsourse() : this is an optional uninstall method.
186 this is called to uninstall the event
187 and cleanup (reverse of source).
188 (see the __auto_uninstall__).
189 fire(*p, **n) : this is the event fire method. call this
190 to manually fire the event. usually this
191 is overloaded to define a handler
192 signature, e.g. the arguments this
193 receives are passed as-is to both the
194 predicate method and each handler.
195 NOTE: the arguments are not copied when
196 passed, thus it is possible to run
197 into side-effects (with mutable
198 types).
199 NOTE: the handler will receive the event
200 object as the first argument.
201 predicate(*p, **n) : this method if defined is called by the
202 fire routine to determine if the event
203 handlers are to be called.
204 if this returns True the handlers are
205 called, else the fire method exits silently.
206 NOTE: this must be signature compatible
207 with the fire method.
208 clear() : this method will remove all handlers of the
209 event (to the exception of class-level
210 handlers).
211 NOTE: this will call the unsourse method
212 if it is enabled.
213 __callhook__(hook, evt, *p, **n)
214 : an optional hook wrapper. this is called
215 once per hook, and should call the hook
216 passing it its arguments.
217 NOTE: if this is changed during event
218 execution, the current event run will
219 not be affected (e.g. the new wrapper
220 will only be used on next event fire).
222 NOTE: these event do not behave as do traditional events, e.g. the
223 handler is not passed the event object containing the event data.
224 it is actually passed the event itself and the arguments to fire
225 depending on a particular event.
226 NOTE: here only the public interface is documented, for more LL
227 information see the comments in the source code.
228 NOTE: this was not intended for direct use.
230 # this will hold the hooks/callbacks...
231 # WARNING: do not edit manually...
232 __eventhooks__ = None
233 # if this is set and the unsource method is present the event
234 # handler will automaticly be uninstalled when the last
235 # hook/callback is removed...
236 # default: True
237 __auto_uninstall__ = True
238 # if this is True and the event does not have a source it will be
239 # inherited from its parent...
240 # default: False
241 __inherit_source__ = False
242 # this if set will prevent an event from being created without a
243 # source...
244 # default: False
245 __strict_source__ = False
246 # this defines the maximum number if handlers per event (None for
247 # no limit)
248 # default: None
249 __max_handler_count__ = None
250 # if this is true a handler may be installed only once.
251 # default: True
252 __unique_handler_call__ = True
253 # if this is true the ability to bind the same handler to this
254 # event will be disabled (on repeated bind EventBindError will be
255 # raised)
256 # default: False
257 __unique_handler_bind__ = False
258 # if this is set to False the event ececution will break on first
259 # handler exception...
260 # default: False
261 __suppress_exceptions__ = False
263 def __init__(self, *p, **n):
265 init the event object.
267 if self.__class__ is InstanceEvent:
268 raise TypeError, 'can\'t use the InstanceEvent class directly.'
269 # check event source....
270 ## if (not hasattr(self, '__inherit_source__') or not self.__inherit_source__) and \
271 ## hasattr(self, '__strict_source__') and self.__strict_source__ and not hasattr(self, 'source'):
272 if not hasattr(self, 'source') \
273 and hasattr(self, '__strict_source__') and self.__strict_source__:
274 raise TypeError, 'an event must have a source.'
275 super(InstanceEvent, self).__init__(*p, **n)
276 ## def __callhook__(self, hook, evt, *p, **n):
277 ## '''
278 ## this, if present, will be used to call each hook (e.g. hook wrapper).
279 ## '''
280 ## hook(evt, *p, **n)
281 def source(self):
283 this method will register the callback that will fire the event.
284 NOTE: this is a class method.
286 if hasattr(self, '__strict_source__') and self.__strict_source__:
287 raise NotImplementedError, 'an event must have a source.'
288 source = objutils.classinstancemethod(source)
289 def fire(self, *pargs, **nargs):
291 this will check the predicate if present and fire the event.
293 NOTE: if the predicate will return False the event will not be fired.
294 NOTE: the predicate will receive the same args as fire.
296 # do not fire the event if the predicate returns false...
297 if hasattr(self, 'predicate'):
298 if not self.predicate(*pargs, **nargs):
299 return
300 return self._exec_event_callback(*pargs, **nargs)
301 def _exec_event_callback(self, *pargs, **nargs):
303 this is the LL event handler executer.
305 NOTE: this was not intended for direct use.
307 # do not fire the event if the predicate returns false...
308 evthooks = self.__eventhooks__
309 self_evthooks = self.__class__.__eventhooks__
310 # handle hooks if at least one of the classes or metaclasses
311 # hook sets is not none...
312 if evthooks != None or self_evthooks != None:
313 if not hasattr(self, '__unique_handler_call__') or self.__unique_handler_call__:
314 ##!!! use the set type...
315 # remove all repeating elements from the hook list...
316 hooks = dict([(item, None) for item in \
317 (self_evthooks != None and self_evthooks or ()) \
318 + (evthooks != None and evthooks or ()) \
319 ]).keys()
320 else:
321 hooks = (self_evthooks != None and self_evthooks or ()) + (evthooks != None and evthooks or ())
322 # check if we have a hook wrapper....
323 if hasattr(self, '__callhook__') and callable(self.__callhook__):
324 # call wrapped hooks...
325 ## raise NotImplementedError, 'assync event mode is not yet enabled.'
326 wrapper = self.__callhook__
327 for hook in hooks:
328 try:
329 # wrap the hook...
330 wrapper(hook, self, *pargs, **nargs)
331 except:
332 # raise the exception if either the hook or the
333 # event are in debug mode...
334 if (not hasattr(hook, '__suppress_exceptions__') or hook.__suppress_exceptions__ == False) or \
335 (not hasattr(self, '__suppress_exceptions__') or self.__suppress_exceptions__ == False):
336 raise
337 ## elif False:
338 ## # log the exception.....
339 ## ##!!!
340 ## pass
341 else:
342 # call bare hooks...
343 for hook in hooks:
344 try:
345 # call the hook...
346 hook(self, *pargs, **nargs)
347 except:
348 # raise the exception if either the hook or the
349 # event are in debug mode...
350 if (not hasattr(hook, '__suppress_exceptions__') or hook.__suppress_exceptions__ == False) or \
351 (not hasattr(self, '__suppress_exceptions__') or self.__suppress_exceptions__ == False):
352 raise
353 ## elif False:
354 ## # log the exception.....
355 ## ##!!!
356 ## pass
357 def installhook(self, hook_func):
359 this will install an event hook/handler.
361 if self.__eventhooks__ == None:
362 self.__eventhooks__ = ()
363 self.source()
364 # handler count...
365 if hasattr(self, '__max_handler_count__') and self.__max_handler_count__ != None \
366 and len(self.__eventhooks__) >= self.__max_handler_count__:
367 raise EventBindError, 'maximum handler count reached for %s.' % self
368 # handler uniqueness...
369 if hasattr(self, '__unique_handler_bind__') and self.__unique_handler_bind__:
370 evthooks = self.__eventhooks__
371 self_evthooks = self.__class__.__eventhooks__
372 if hook_func in (self_evthooks != None and self_evthooks or ()) \
373 + (evthooks != None and evthooks or ()):
374 raise EventBindError, 'the handler %s is already bound to event %s' % (hook_func, self)
375 self.__eventhooks__ += (hook_func,)
376 else:
377 self.__eventhooks__ += (hook_func,)
378 installhook = objutils.classinstancemethod(installhook)
379 def uninstallhook(self, hook_func):
381 this will uninstall an event hook/handler.
383 if hook_func in self.__eventhooks__:
384 tmp = list(self.__eventhooks__)
385 tmp.remove(hook_func)
386 self.__eventhooks__ = tuple(tmp)
387 if self.__eventhooks__ == ():
388 self.clear()
389 uninstallhook = objutils.classinstancemethod(uninstallhook)
390 def clear(self):
392 this will drop all handlers for this event.
394 self.__eventhooks__ = None
395 # uninstall handler...
396 if hasattr(self, '__auto_uninstall__') and self.__auto_uninstall__ and \
397 hasattr(self, 'unsource'):
398 self.unsource()
401 #--------------------------------------------------------------_ClassEvent---
402 # NOTE: this might change in the future...
403 # NOTE: it might be good to pass an event instance to the handler,
404 # though this will be memory hungry (copy per handler...)
405 class _ClassEvent(InstanceEvent, type):
408 NOTE: the time the event is fired depends on the actual event class.
409 NOTE: the args the callback will receive depend on the actual event it
410 is bound to.
412 # this will hold the hooks/callbacks...
413 # WARNING: do not edit manually...
414 __eventhooks__ = None
415 # if this is set and the unsource method is present the event
416 # handler will automaticly be uninstalled when the last
417 # hook/callback is removed...
418 __auto_uninstall__ = True
419 # this defines the maximum number if handlers per event (None for
420 # no limit)
421 __max_handler_count__ = None
422 # this will define a list of methods to be auto-converted to
423 # classmethods....
424 # WARNING: do not change unless you know what you are doing!
425 __interface_methods__ = (
426 'fire',
427 'source',
428 'unsource',
429 'predicate',
430 '__callhook__',
433 ## __suppress_exceptions__ = True
435 def __init__(cls, name, bases, ns):
438 # check event source.... ##!!! revise this check !!!#
439 if (not hasattr(cls, '__inherit_source__') or not cls.__inherit_source__) and \
440 hasattr(cls, '__strict_source__') and cls.__strict_source__ and 'source' not in ns:
441 raise TypeError, 'an event must have a source.'
442 # classmethodify methods...
443 for meth in cls.__interface_methods__:
444 if meth in ns:
445 setattr(cls, meth, classmethod(ns[meth]))
446 super(_ClassEvent, cls).__init__(name, bases, ns)
447 ## def __call__(cls, *pargs, **nargs):
448 ## '''
449 ## '''
450 ## super(_ClassEvent, cls).__call__()
453 #----------------------------------------------------------ClassEvent---
454 class ClassEvent(AbstractEvent):
456 base abstract class event.
458 In essence, this is the same as the InstanceEvent, thus all
459 documentation written for the above is applicable here, with the
460 exception that each ClassEvent subclass is an event by itself
461 (the event factory here is a metaclass).
463 this is to be used for global, system level events, where there
464 is no need to create multiple copies of the event.
467 NOTE: this was not intended for direct use.
469 __metaclass__ = _ClassEvent
470 # this will hold the hooks/callbacks...
471 # WARNING: do not edit manually...
472 __eventhooks__ = None
473 # if this is True and the event does not have a source it will be
474 # inherited from its parent...
475 # NOTE: an event must have a source.
476 __inherit_source__ = False
477 # this if set will prevent an event from being created without a
478 # source...
479 __strict_source__ = False
481 def source(self):
483 this method will register the callback that will fire the event.
484 NOTE: this is a class method.
486 if hasattr(self, '__strict_source__') and self.__strict_source__:
487 raise NotImplementedError, 'an event must have a source.'
488 # these are here for documentation...
489 ## def unsource(cls):
490 ## '''
491 ## this will uninstall the event hook...
492 ## '''
493 ## def predicate(cls, *pargs, **nargs):
494 ## '''
495 ## this should test if an event is to be fired...
496 ## (return True to fire False to ignore)
497 ## '''
498 ## return True
499 ## def fire(cls, *pargs, **nargs):
500 ## '''
501 ## '''
503 #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
504 # legacy support (and a convenient shorthand :) )...
505 Event = ClassEvent
509 #=======================================================================
510 # vim:set ts=4 sw=4 nowrap :