-
Notifications
You must be signed in to change notification settings - Fork 2
[Feature request and solution] Add optional priority to event listeners #8
Description
First of all, thanks for your quality library. I will try to contribute a bit with a slight modification to add a nice and compatible feature (in my case necessary).
Feature request:
Add optional priority to event listeners.
Scenario:
You have an event emitter and you attach several listeners to a specific event, but you want to assure that the listeners execute in a specific order. Currently this is impossible if you attach the listeners in different application levels.
Real-world example:
def logEvent():
log("Event just happened.")
def handleEvent():
...
log("Event was handled.")
emitter.on("event", logEvent) # This statement runs in one application place...
emitter.on("event", handleEvent) # ... and this statement runs in another application place.
If you do not control in which order the listeners are assigned, you could end with something like this, which may not be the desired:
LOG: Event was handled.
LOG: Event just happened.
But if you could optionally assign priorities to listeners, you would always end with the desired execution order.
emitter.on("event", logEvent, EventPriority.FIRST)
emitter.on("event", handleEvent)
LOG: Event just happened.
LOG: Event was handled.
Solution:
events.zip
With the currently implemented listener-wrapper solution, it is very easy to add the ability to assign priorities to listeners and keep things 100% backwards compatible.
__all__ = ['EventPriority', 'EventEmitter', 'on', 'once']
class EventPriority():
# This enumeration is just syntactic sugar for specifying events priority. Any number will do, positive or negative, real or float.
# Higher numbers denote higher priority.
CRITICAL = 10000
FIRST = 1000
VERY_HIGH = 200
HIGH = 100
OVER_NORMAL = 10
NORMAL = 0
BELOW_NORMAL = -10
LOW = -100
VERY_LOW = -200
LAST = -1000
class ListenerWrapper(object):
def __init__(self, listener, is_once=False, priority=None):
self.listener = listener
self.is_once = is_once
self.priority = priority if isinstance(priority, (int, float)) else EventPriority.NORMAL
def __call__(self, *args, **kwargs):
self.listener(*args, **kwargs)
def __eq__(self, other):
if isinstance(other, ListenerWrapper):
return other.listener == self.listener
if callable(other):
return other == self.listener
return False
class EventEmitter(object):
def __init__(self):
self._events = {}
def on(self, event, listener, priority=None):
self._on(event, ListenerWrapper(listener, priority=priority))
def once(self, event, listener, priority=None):
self._on(event, ListenerWrapper(listener, is_once=True, priority=priority))
def _on(self, event, listener_wrapper):
if not event in self._events:
self._events[event] = []
self._events[event].append(listener_wrapper)
def emit(self, event, *args, **kwargs):
if event in self._events:
# Use a copy of listeners sorted by priority.
listeners = sorted(self._events[event], key=lambda listener: listener.priority, reverse=True)
for listener in listeners:
if listener.is_once:
self.remove(event, listener)
listener(*args, **kwargs)
def remove(self, event, listener):
if event in self._events:
events = self._events[event]
if listener in events:
events.remove(listener)
def remove_all(self, event):
if event in self._events:
self._events[event] = []
def count(self, event):
return len(self._events[event]) if event in self._events else 0
def on(emitter, event, priority=None):
def decorator(func):
emitter.on(event, func, priority=priority)
return func
return decorator
def once(emitter, event, priority=None):
def decorator(func):
emitter.once(event, func, priority=priority)
return func
return decorator
Notes:
I hope you find this useful. This is the implementation I am currently using and it works flawlessly.
I also corrected one little unrelated issue. I modified the listener wrapper comparison to accomodate to the fact that a listener can be any callable, not only a function but also an instance method, static method, etc.