base.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. # -*- coding: utf-8 -*-
  2. from collections import OrderedDict
  3. from threading import Lock
  4. from typing import (
  5. Any,
  6. Callable,
  7. Dict,
  8. List,
  9. Mapping,
  10. Optional,
  11. overload,
  12. Set,
  13. Tuple,
  14. TypeVar,
  15. Union,
  16. )
  17. Self = Any
  18. class PyeeException(Exception):
  19. """An exception internal to pyee. Deprecated in favor of PyeeError."""
  20. class PyeeError(PyeeException):
  21. """An error internal to pyee."""
  22. Handler = TypeVar("Handler", bound=Callable)
  23. class EventEmitter:
  24. """The base event emitter class. All other event emitters inherit from
  25. this class.
  26. Most events are registered with an emitter via the `on` and `once`
  27. methods, and fired with the `emit` method. However, pyee event emitters
  28. have two *special* events:
  29. - `new_listener`: Fires whenever a new listener is created. Listeners for
  30. this event do not fire upon their own creation.
  31. - `error`: When emitted raises an Exception by default, behavior can be
  32. overridden by attaching callback to the event.
  33. For example:
  34. ```py
  35. @ee.on('error')
  36. def on_error(message):
  37. logging.err(message)
  38. ee.emit('error', Exception('something blew up'))
  39. ```
  40. All callbacks are handled in a synchronous, blocking manner. As in node.js,
  41. raised exceptions are not automatically handled for you---you must catch
  42. your own exceptions, and treat them accordingly.
  43. """
  44. def __init__(self: Self) -> None:
  45. self._events: Dict[
  46. str,
  47. "OrderedDict[Callable, Callable]",
  48. ] = dict()
  49. self._lock: Lock = Lock()
  50. def __getstate__(self: Self) -> Mapping[str, Any]:
  51. state = self.__dict__.copy()
  52. del state["_lock"]
  53. return state
  54. def __setstate__(self: Self, state: Mapping[str, Any]) -> None:
  55. self.__dict__.update(state)
  56. self._lock = Lock()
  57. @overload
  58. def on(self: Self, event: str) -> Callable[[Handler], Handler]: ...
  59. @overload
  60. def on(self: Self, event: str, f: Handler) -> Handler: ...
  61. def on(
  62. self: Self, event: str, f: Optional[Handler] = None
  63. ) -> Union[Handler, Callable[[Handler], Handler]]:
  64. """Registers the function `f` to the event name `event`, if provided.
  65. If `f` isn't provided, this method calls `EventEmitter#listens_to`, and
  66. otherwise calls `EventEmitter#add_listener`. In other words, you may either
  67. use it as a decorator:
  68. ```py
  69. @ee.on('data')
  70. def data_handler(data):
  71. print(data)
  72. ```
  73. Or directly:
  74. ```py
  75. ee.on('data', data_handler)
  76. ```
  77. In both the decorated and undecorated forms, the event handler is
  78. returned. The upshot of this is that you can call decorated handlers
  79. directly, as well as use them in remove_listener calls.
  80. Note that this method's return type is a union type. If you are using
  81. mypy or pyright, you will probably want to use either
  82. `EventEmitter#listens_to` or `EventEmitter#add_listener`.
  83. """
  84. if f is None:
  85. return self.listens_to(event)
  86. else:
  87. return self.add_listener(event, f)
  88. def listens_to(self: Self, event: str) -> Callable[[Handler], Handler]:
  89. """Returns a decorator which will register the decorated function to
  90. the event name `event`:
  91. ```py
  92. @ee.listens_to("event")
  93. def data_handler(data):
  94. print(data)
  95. ```
  96. By only supporting the decorator use case, this method has improved
  97. type safety over `EventEmitter#on`.
  98. """
  99. def on(f: Handler) -> Handler:
  100. self._add_event_handler(event, f, f)
  101. return f
  102. return on
  103. def add_listener(self: Self, event: str, f: Handler) -> Handler:
  104. """Register the function `f` to the event name `event`:
  105. ```
  106. def data_handler(data):
  107. print(data)
  108. h = ee.add_listener("event", data_handler)
  109. ```
  110. By not supporting the decorator use case, this method has improved
  111. type safety over `EventEmitter#on`.
  112. """
  113. self._add_event_handler(event, f, f)
  114. return f
  115. def _add_event_handler(self: Self, event: str, k: Callable, v: Callable):
  116. # Fire 'new_listener' *before* adding the new listener!
  117. self.emit("new_listener", event, k)
  118. # Add the necessary function
  119. # Note that k and v are the same for `on` handlers, but
  120. # different for `once` handlers, where v is a wrapped version
  121. # of k which removes itself before calling k
  122. with self._lock:
  123. if event not in self._events:
  124. self._events[event] = OrderedDict()
  125. self._events[event][k] = v
  126. def _emit_run(
  127. self: Self,
  128. f: Callable,
  129. args: Tuple[Any, ...],
  130. kwargs: Dict[str, Any],
  131. ) -> None:
  132. f(*args, **kwargs)
  133. def event_names(self: Self) -> Set[str]:
  134. """Get a set of events that this emitter is listening to."""
  135. return set(self._events.keys())
  136. def _emit_handle_potential_error(self: Self, event: str, error: Any) -> None:
  137. if event == "error":
  138. if isinstance(error, Exception):
  139. raise error
  140. else:
  141. raise PyeeError(f"Uncaught, unspecified 'error' event: {error}")
  142. def _call_handlers(
  143. self: Self,
  144. event: str,
  145. args: Tuple[Any, ...],
  146. kwargs: Dict[str, Any],
  147. ) -> bool:
  148. handled = False
  149. with self._lock:
  150. funcs = list(self._events.get(event, OrderedDict()).values())
  151. for f in funcs:
  152. self._emit_run(f, args, kwargs)
  153. handled = True
  154. return handled
  155. def emit(
  156. self: Self,
  157. event: str,
  158. *args: Any,
  159. **kwargs: Any,
  160. ) -> bool:
  161. """Emit `event`, passing `*args` and `**kwargs` to each attached
  162. function. Returns `True` if any functions are attached to `event`;
  163. otherwise returns `False`.
  164. Example:
  165. ```py
  166. ee.emit('data', '00101001')
  167. ```
  168. Assuming `data` is an attached function, this will call
  169. `data('00101001')'`.
  170. """
  171. handled = self._call_handlers(event, args, kwargs)
  172. if not handled:
  173. self._emit_handle_potential_error(event, args[0] if args else None)
  174. return handled
  175. def once(
  176. self: Self,
  177. event: str,
  178. f: Optional[Callable] = None,
  179. ) -> Callable:
  180. """The same as `ee.on`, except that the listener is automatically
  181. removed after being called.
  182. """
  183. def _wrapper(f: Callable) -> Callable:
  184. def g(
  185. *args: Any,
  186. **kwargs: Any,
  187. ) -> Any:
  188. with self._lock:
  189. # Check that the event wasn't removed already right
  190. # before the lock
  191. if event in self._events and f in self._events[event]:
  192. self._remove_listener(event, f)
  193. else:
  194. return None
  195. # f may return a coroutine, so we need to return that
  196. # result here so that emit can schedule it
  197. return f(*args, **kwargs)
  198. self._add_event_handler(event, f, g)
  199. return f
  200. if f is None:
  201. return _wrapper
  202. else:
  203. return _wrapper(f)
  204. def _remove_listener(self: Self, event: str, f: Callable) -> None:
  205. """Naked unprotected removal."""
  206. if event in self._events:
  207. self._events[event].pop(f)
  208. if not self._events[event]:
  209. del self._events[event]
  210. def remove_listener(self: Self, event: str, f: Callable) -> None:
  211. """Removes the function `f` from `event`."""
  212. with self._lock:
  213. self._remove_listener(event, f)
  214. def remove_all_listeners(self: Self, event: Optional[str] = None) -> None:
  215. """Remove all listeners attached to `event`.
  216. If `event` is `None`, remove all listeners on all events.
  217. """
  218. with self._lock:
  219. if event is not None:
  220. self._events[event] = OrderedDict()
  221. else:
  222. self._events = dict()
  223. def listeners(self: Self, event: str) -> List[Callable]:
  224. """Returns a list of all listeners registered to the `event`."""
  225. return list(self._events.get(event, OrderedDict()).keys())