Decovent - Python event handling using decorators

Documentation

Version: 1.1.1
Last Updated: May 20th, 2010


Description

Decovent is a very small Python library that allows an easy and elegant event rising and handling, using decorators.

It's important to understand that events and handlers are not classes, but decorated methods that may belong to any new-style class. There are no restrictions on the class itself regarding inheritance or the interfaces that are implemented.

top

Download

The latest version of Decovent can be downloaded from http://pypi.python.org/pypi/Decovent.

top

Examples

How to import Decovent

 
from decovent import *    #the only required import 
top

How to create and raise an event

 
class Mouse(object):
    @raise_event()
    def click(self, x, y):
        return (x, y)
        
mouse = Mouse()
mouse.click(10, 20)
top

How to create and register a handler

 
class Mouse(object):
    @set_handler('click')
    def on_click(self, x, y):  
        return (x, y)
        
mouse = Mouse()
mouse.on_click()    #no arguments at registration
top

How to create a class that handles it own events

 
class Mouse(object):
    def __init__(self):
        self.on_click()
 
    @raise_event()
    def click(self, x, y):
        return (x, y)
 
    @set_handler('click')
    def on_click(self, x, y):
        return (x, y)
        
mouse = Mouse()
mouse.click(10, 20)
top

How to create a class that handles the events of another class

 
class Mouse(object):
    @raise_event()
    def click(self, x, y):
        return (x, y)
 
class Screen(object):
    @set_handler('click', Mouse)
    def on_click(self, x, y):
        return (x, y)
        
screen = Screen()
screen.on_click()
         
mouse = Mouse()
mouse.click(10, 20)
top

How to create a @classmethod event or handler

 
class Mouse(object):
    def __init__(self):
        self.on_click()
        
    @classmethod      
    @raise_event()
    def click(self, x, y):
        return (x, y)
 
    @set_handler('click')
    def on_click(self, x, y):
        return (x, y)
        
Mouse.click()
 
class Mouse(object):
    @classmethod 
    @raise_event()
    def click(self, x, y):
        return (x, y)
 
class Screen(object):
    @classmethod 
    @set_handler('click', Mouse)
    def on_click(self, x, y):
        return (x, y)
        
Screen.on_click()
Mouse.click(10, 20)
top

How to raise and handle events with different names than the decorated method name

 
class Mouse(object):
    def __init__(self):
        self.on_move()
 
    @raise_event('move')
    def click(self, x, y):
        return (x, y)
 
    @set_handler('move')
    def on_move(self, x, y):
        return (x, y)
        
mouse = Mouse()
mouse.click(10, 20)
top

How to raise and handle events with non-ASCII names

 
class Mouse(object):
    def __init__(self):
        self.on_move()
 
    @raise_event('щелкать')
    def click(self, x, y):
        return (x, y)
 
    @set_handler('щелкать')
    def on_click(self, x, y):
        return (x, y)
        
mouse = Mouse()
mouse.click(10, 20)
top

How to execute handlers synchronous or asynchronous

top

How to retrieve the execution result of an event and registered handlers

 
class Mouse(object):
    def __init__(self):
        self.on_click()
 
    @raise_event()
    def click(self, x, y):
        return (x, y)
 
    @set_handler('click')
    def on_click(self, x, y):
        return (x, y)
        
mouse = Mouse()
e_result, h_result = mouse.click(10, 20)
 
pprint(e_result)
pprint(h_result)
 
>>(True, (10, 20), <class '__main__.Mouse'>, <function click at 0x00BC5F30>)
>>((True, (10, 20), <class '__main__.Mouse'>, <function on_click at 0x00BC5FB0>),)
 
 
decovent.exc_info = True  
decovent.traceback = True  
 
class Mouse(object):
    def __init__(self):
        self.on_click()
 
    @raise_event()
    def click(self, x, y):
        return (x, y)
 
    @set_handler('click')
    def on_click(self, x, y):
        raise TypeError('Wrong values')
        
mouse = Mouse()
e_result, h_result = mouse.click(10, 20)
 
pprint(e_result)
pprint(h_result)
 
>>(True, (10, 20), <class '__main__.Mouse'>, <function click at 0x00BC5F30>)
>>((False,
>>  (<type 'exceptions.TypeError'>,
>>   TypeError('Wrong values',),
>>   <traceback object at 0x00BB4468>),
>>  <class '__main__.Mouse'>,
>>  <function on_click at 0x00BC5FB0>),)
 
top

How to unregister a handler after its first execution

 
class Mouse(object):
    def __init__(self):
        self.on_click()
 
    @raise_event()
    def click(self, x, y):
        return (x, y)
 
    @set_handler('click', unregister=True)
    def on_click(self, x, y):
        return (x, y)
top

How to unregister handlers at various levels

 
class Mouse(object):
    def __init__(self):
        self.on_click()
 
    @raise_event()
    def click(self, x, y):
        return (x, y)
 
    @set_handler('click', unregister=True)
    def on_click(self, x, y):
        return (x, y)
 
decovent.reset(Mouse, 'click')  #remove handlers for Mouse().click() 
decovent.reset(Mouse)           #remove handlers for Mouse()
decovent.reset()                #remove handlers for any class        
top

How to integrate events and handlers with custom decorators

As opposed to most decorators, that will check some conditions before the decorated method is executed, Decovent executes the decorated event and handler methods and returns the results. The execution occurs only when the event is being raised. If you need to decorate a method and in the same time raise it as an event or register it as a handler, a few points have to be considered: top

How to memoize events and handlers

http://en.wikipedia.org/wiki/Memoization

Memoization can be activated at local or global level. When the global level is active, will overwrite the local level. To activate memoization at local level, set parameter memoize_ of set_handler or raise_event to True. To activate it at global level, set decovent.memoize = True. Only successful executions are memoized

Please make sure you actually need memoization (especially at global level), otherwise might be just overhead.
 
decovent.memoize = True    #global
 
class Mouse(object):
    @raise_event()
    def click(self, x, y):
        return (x, y)
or
 
class Mouse(object):
    @raise_event(memoize_=True)  #local
    def click(self, x, y):
        return (x, y)
top

How to synchronize events and handlers

http://en.wikipedia.org/wiki/Synchronization_(computer_science)#Process_synchronization

To synchronize execution of an event and registered handlers on the same lock, an external threading.RLock() has to be provided for lock argument of raise_event.

Please make sure you understand the difference between running handlers synchronous/asynchronous and synchronization, otherwise you may cause yourself performance issues.
 
lock = threading.RLock()
 
class Mouse(object):
    @raise_event(lock=lock)
    def click(self, x, y):
        return (x, y)
top

How to control the execution time of an event or handler

The execution time allocated for an event or handler can be controlled by setting the timeout argument of raise_event or set_handler, to the desired limit (positive integer or float). If the limit is reached before the execution ends, a RuntimeError is raised and stored in the execution result of the corresponding event or handler.

Please note that a new thread is created for each method that has a restricted execution interval.
 
class Mouse(object):
    def __init__(self):
        self.on_click()
        self.long_on_click()
        self.err_on_click()
        
    @raise_event(timeout=1)
    def click(self, x, y):        
        return (x, y)
        
    @set_handler('click', timeout=1)
    def on_click(self, x, y):        
        return (x, y) 
 
    @set_handler('click', timeout=1)
    def long_on_click(self, x, y):
        sleep(2)        
        return (x, y) 
 
    @set_handler('click', timeout=1)
    def err_on_click(self, x, y):
        raise TypeError('Wrong values')         
 
mouse = Mouse()
pprint(mouse.click(10, 20))
 
>>((True, (10, 20), <class '__main__.Mouse'>, <function click at 0x00BCA030>),
>> ((False,
>>   (<type 'exceptions.TypeError'>,
>>    TypeError('Wrong values',),
>>    <traceback object at 0x00BCB1E8>),
>>   <class '__main__.Mouse'>,
>>   <function err_on_click at 0x00BCA1B0>),
>>  (True,
>>   (10, 20),
>>   <class '__main__.Mouse'>,
>>   <function on_click at 0x00BCA0B0>),
>>  (False,
>>   (<type 'exceptions.RuntimeError'>,
>>    RuntimeError('[Thread-6] Execution was forcefully terminated',),
>>    <traceback object at 0x00BCB288>),
>>   <class '__main__.Mouse'>,
>>   <function long_on_click at 0x00BCA130>)))
top

How to control the number of active parallel executions

To control the number of methods that can be executed in parallel at one time, you may use method decovent.active(value). By default, maximum 3 methods are allowed to run in parallel.
 
decovent.active(5)	# max. 5 methods are executed in parallel  
top

Internals

Classes

top

Attributes

top

Execution model

top

Execution result

top

Helpers

top

Storage model

top