1 #=======================================================================
3 #-----------------------------------------------------------------------
5 __version__
= '''0.3.15'''
6 __sub_version__
= '''20040530032430'''
7 __copyright__
= '''(c) Alex A. Naanou 2003-2004'''
10 #-----------------------------------------------------------------------
13 This module defines the event framework.
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
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
36 #-----------------------------------------------------------------------
38 import pli
.objutils
as objutils
42 #-----------------------------------------------------------------------
43 #----------------------------------------------------------EventError---
44 class EventError(Exception):
46 a general event error.
49 #------------------------------------------------------EventBindError---
50 class EventBindError(EventError
):
56 #-----------------------------------------------------------------------
57 #----------------------------------------------------------------bind---
58 def bind(event
, func
, HOOK_DEBUG
=False):
60 register func as the event callback.
62 HOOK_DEBUG will enable exception propagation from event handlers.
67 # NOTE: this does not pickle correctly...
68 func
.__suppress
_exceptions
__ = False
71 event
.installhook(func
)
73 raise EventBindError
, 'can\'t bind to non-event object %s.' % event
76 #--------------------------------------------------------------unbind---
77 def unbind(event
, func
):
79 unregister func as the event callback.
82 event
.uninstallhook(func
)
84 raise EventBindError
, 'can\'t unbind from non-event object %s.' % event
87 #-------------------------------------------------------------isbound---
88 def isbound(event
, func
):
90 test if func is bound to event.
93 return func
in event
.__eventhooks
__
95 raise EventError
, 'object %s is not an event.' % event
98 #----------------------------------------------------------------fire---
99 def fire(event
, *p
, **n
):
103 shortcut for <event>.fire(...)
108 raise EventError
, 'can\'t fire a non-event object %s.' % event
111 #-------------------------------------------------------------isevent---
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
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.
146 __inherit_source__ : if this is True and the event does not
147 have a source it will be inherited from
150 __strict_source__ : this if set will prevent an event from
151 being created without a source.
153 __max_handler_count__ : this defines the maximum number if
154 handlers per event (None for no limit)
156 __unique_handler_call__ : if this is true a handler may be
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)
164 __suppress_exceptions__ : this is true will supress exceptions
165 generated by event handlers, thus will
166 guarantee that evry handler will get
168 NOTE: this might be rather traitorous
169 as all errors are supressed and
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
180 by default this is obligatory (see the
181 __strict_source__, __inherit_source__
183 if this is not defined, one must fire
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
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
211 NOTE: this will call the unsourse method
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...
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...
241 __inherit_source__
= False
242 # this if set will prevent an event from being created without a
245 __strict_source__
= False
246 # this defines the maximum number if handlers per event (None for
249 __max_handler_count__
= None
250 # if this is true a handler may be installed only once.
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
257 __unique_handler_bind__
= False
258 # if this is set to False the event ececution will break on first
259 # handler exception...
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):
278 ## this, if present, will be used to call each hook (e.g. hook wrapper).
280 ## hook(evt, *p, **n)
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
):
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 ()) \
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
__
330 wrapper(hook
, self
, *pargs
, **nargs
)
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):
338 ## # log the exception.....
346 hook(self
, *pargs
, **nargs
)
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):
354 ## # log the exception.....
357 def installhook(self
, hook_func
):
359 this will install an event hook/handler.
361 if self
.__eventhooks
__ == None:
362 self
.__eventhooks
__ = ()
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
,)
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
__ == ():
389 uninstallhook
= objutils
.classinstancemethod(uninstallhook
)
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'):
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
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
421 __max_handler_count__
= None
422 # this will define a list of methods to be auto-converted to
424 # WARNING: do not change unless you know what you are doing!
425 __interface_methods__
= (
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
__:
445 setattr(cls
, meth
, classmethod(ns
[meth
]))
446 super(_ClassEvent
, cls
).__init
__(name
, bases
, ns
)
447 ## def __call__(cls, *pargs, **nargs):
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
479 __strict_source__
= False
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):
491 ## this will uninstall the event hook...
493 ## def predicate(cls, *pargs, **nargs):
495 ## this should test if an event is to be fired...
496 ## (return True to fire False to ignore)
499 ## def fire(cls, *pargs, **nargs):
503 #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
504 # legacy support (and a convenient shorthand :) )...
509 #=======================================================================
510 # vim:set ts=4 sw=4 nowrap :