1# -*- coding: utf-8 -*- 2 3from collections import defaultdict, OrderedDict 4from threading import Lock 5 6__all__ = ["EventEmitter", "PyeeException"] 7 8 9class PyeeException(Exception): 10 """An exception internal to pyee.""" 11 12 pass 13 14 15class EventEmitter: 16 """The base event emitter class. All other event emitters inherit from 17 this class. 18 19 Most events are registered with an emitter via the ``on`` and ``once`` 20 methods, and fired with the ``emit`` method. However, pyee event emitters 21 have two *special* events: 22 23 - ``new_listener``: Fires whenever a new listener is created. Listeners for 24 this event do not fire upon their own creation. 25 26 - ``error``: When emitted raises an Exception by default, behavior can be 27 overriden by attaching callback to the event. 28 29 For example:: 30 31 @ee.on('error') 32 def on_error(message): 33 logging.err(message) 34 35 ee.emit('error', Exception('something blew up')) 36 37 All callbacks are handled in a synchronous, blocking manner. As in node.js, 38 raised exceptions are not automatically handled for you---you must catch 39 your own exceptions, and treat them accordingly. 40 """ 41 42 def __init__(self): 43 self._events = defaultdict(OrderedDict) 44 self._lock = Lock() 45 46 def on(self, event, f=None): 47 """Registers the function ``f`` to the event name ``event``. 48 49 If ``f`` isn't provided, this method returns a function that 50 takes ``f`` as a callback; in other words, you can use this method 51 as a decorator, like so:: 52 53 @ee.on('data') 54 def data_handler(data): 55 print(data) 56 57 In both the decorated and undecorated forms, the event handler is 58 returned. The upshot of this is that you can call decorated handlers 59 directly, as well as use them in remove_listener calls. 60 """ 61 62 def _on(f): 63 self._add_event_handler(event, f, f) 64 return f 65 66 if f is None: 67 return _on 68 else: 69 return _on(f) 70 71 def _add_event_handler(self, event, k, v): 72 # Fire 'new_listener' *before* adding the new listener! 73 self.emit("new_listener", event, k) 74 75 # Add the necessary function 76 # Note that k and v are the same for `on` handlers, but 77 # different for `once` handlers, where v is a wrapped version 78 # of k which removes itself before calling k 79 with self._lock: 80 self._events[event][k] = v 81 82 def _emit_run(self, f, args, kwargs): 83 f(*args, **kwargs) 84 85 def _emit_handle_potential_error(self, event, error): 86 if event == "error": 87 if error: 88 raise error 89 else: 90 raise PyeeException("Uncaught, unspecified 'error' event.") 91 92 def _call_handlers(self, event, args, kwargs): 93 handled = False 94 95 with self._lock: 96 funcs = list(self._events[event].values()) 97 for f in funcs: 98 self._emit_run(f, args, kwargs) 99 handled = True 100 101 return handled 102 103 def emit(self, event, *args, **kwargs): 104 """Emit ``event``, passing ``*args`` and ``**kwargs`` to each attached 105 function. Returns ``True`` if any functions are attached to ``event``; 106 otherwise returns ``False``. 107 108 Example:: 109 110 ee.emit('data', '00101001') 111 112 Assuming ``data`` is an attached function, this will call 113 ``data('00101001')'``. 114 """ 115 handled = self._call_handlers(event, args, kwargs) 116 117 if not handled: 118 self._emit_handle_potential_error(event, args[0] if args else None) 119 120 return handled 121 122 def once(self, event, f=None): 123 """The same as ``ee.on``, except that the listener is automatically 124 removed after being called. 125 """ 126 127 def _wrapper(f): 128 def g(*args, **kwargs): 129 with self._lock: 130 # Check that the event wasn't removed already right 131 # before the lock 132 if event in self._events and f in self._events[event]: 133 self._remove_listener(event, f) 134 else: 135 return None 136 # f may return a coroutine, so we need to return that 137 # result here so that emit can schedule it 138 return f(*args, **kwargs) 139 140 self._add_event_handler(event, f, g) 141 return f 142 143 if f is None: 144 return _wrapper 145 else: 146 return _wrapper(f) 147 148 def _remove_listener(self, event, f): 149 """Naked unprotected removal.""" 150 self._events[event].pop(f) 151 152 def remove_listener(self, event, f): 153 """Removes the function ``f`` from ``event``.""" 154 with self._lock: 155 self._remove_listener(event, f) 156 157 def remove_all_listeners(self, event=None): 158 """Remove all listeners attached to ``event``. 159 If ``event`` is ``None``, remove all listeners on all events. 160 """ 161 with self._lock: 162 if event is not None: 163 self._events[event] = OrderedDict() 164 else: 165 self._events = defaultdict(OrderedDict) 166 167 def listeners(self, event): 168 """Returns a list of all listeners registered to the ``event``.""" 169 return list(self._events[event].keys()) 170