Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pyee/base.py: 48%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
3from collections import OrderedDict
4from threading import Lock
5from typing import (
6 Any,
7 Callable,
8 Dict,
9 List,
10 Mapping,
11 Optional,
12 overload,
13 Set,
14 Tuple,
15 TypeVar,
16 Union,
17)
19Self = Any
22class PyeeException(Exception):
23 """An exception internal to pyee. Deprecated in favor of PyeeError."""
26class PyeeError(PyeeException):
27 """An error internal to pyee."""
30Handler = TypeVar("Handler", bound=Callable)
33class EventEmitter:
34 """The base event emitter class. All other event emitters inherit from
35 this class.
37 Most events are registered with an emitter via the `on` and `once`
38 methods, and fired with the `emit` method. However, pyee event emitters
39 have two *special* events:
41 - `new_listener`: Fires whenever a new listener is created. Listeners for
42 this event do not fire upon their own creation.
44 - `error`: When emitted raises an Exception by default, behavior can be
45 overridden by attaching callback to the event.
47 For example:
49 ```py
50 @ee.on('error')
51 def on_error(message):
52 logging.err(message)
54 ee.emit('error', Exception('something blew up'))
55 ```
57 All callbacks are handled in a synchronous, blocking manner. As in node.js,
58 raised exceptions are not automatically handled for you---you must catch
59 your own exceptions, and treat them accordingly.
60 """
62 def __init__(self: Self) -> None:
63 self._events: Dict[
64 str,
65 "OrderedDict[Callable, Callable]",
66 ] = dict()
67 self._lock: Lock = Lock()
69 def __getstate__(self: Self) -> Mapping[str, Any]:
70 state = self.__dict__.copy()
71 del state["_lock"]
72 return state
74 def __setstate__(self: Self, state: Mapping[str, Any]) -> None:
75 self.__dict__.update(state)
76 self._lock = Lock()
78 @overload
79 def on(self: Self, event: str) -> Callable[[Handler], Handler]: ...
80 @overload
81 def on(self: Self, event: str, f: Handler) -> Handler: ...
83 def on(
84 self: Self, event: str, f: Optional[Handler] = None
85 ) -> Union[Handler, Callable[[Handler], Handler]]:
86 """Registers the function `f` to the event name `event`, if provided.
88 If `f` isn't provided, this method calls `EventEmitter#listens_to`, and
89 otherwise calls `EventEmitter#add_listener`. In other words, you may either
90 use it as a decorator:
92 ```py
93 @ee.on('data')
94 def data_handler(data):
95 print(data)
96 ```
98 Or directly:
100 ```py
101 ee.on('data', data_handler)
102 ```
104 In both the decorated and undecorated forms, the event handler is
105 returned. The upshot of this is that you can call decorated handlers
106 directly, as well as use them in remove_listener calls.
108 Note that this method's return type is a union type. If you are using
109 mypy or pyright, you will probably want to use either
110 `EventEmitter#listens_to` or `EventEmitter#add_listener`.
111 """
112 if f is None:
113 return self.listens_to(event)
114 else:
115 return self.add_listener(event, f)
117 def listens_to(self: Self, event: str) -> Callable[[Handler], Handler]:
118 """Returns a decorator which will register the decorated function to
119 the event name `event`:
121 ```py
122 @ee.listens_to("event")
123 def data_handler(data):
124 print(data)
125 ```
127 By only supporting the decorator use case, this method has improved
128 type safety over `EventEmitter#on`.
129 """
131 def on(f: Handler) -> Handler:
132 self._add_event_handler(event, f, f)
133 return f
135 return on
137 def add_listener(self: Self, event: str, f: Handler) -> Handler:
138 """Register the function `f` to the event name `event`:
140 ```
141 def data_handler(data):
142 print(data)
144 h = ee.add_listener("event", data_handler)
145 ```
147 By not supporting the decorator use case, this method has improved
148 type safety over `EventEmitter#on`.
149 """
150 self._add_event_handler(event, f, f)
151 return f
153 def _add_event_handler(self: Self, event: str, k: Callable, v: Callable):
154 # Fire 'new_listener' *before* adding the new listener!
155 self.emit("new_listener", event, k)
157 # Add the necessary function
158 # Note that k and v are the same for `on` handlers, but
159 # different for `once` handlers, where v is a wrapped version
160 # of k which removes itself before calling k
161 with self._lock:
162 if event not in self._events:
163 self._events[event] = OrderedDict()
164 self._events[event][k] = v
166 def _emit_run(
167 self: Self,
168 f: Callable,
169 args: Tuple[Any, ...],
170 kwargs: Dict[str, Any],
171 ) -> None:
172 f(*args, **kwargs)
174 def event_names(self: Self) -> Set[str]:
175 """Get a set of events that this emitter is listening to."""
176 return set(self._events.keys())
178 def _emit_handle_potential_error(self: Self, event: str, error: Any) -> None:
179 if event == "error":
180 if isinstance(error, Exception):
181 raise error
182 else:
183 raise PyeeError(f"Uncaught, unspecified 'error' event: {error}")
185 def _call_handlers(
186 self: Self,
187 event: str,
188 args: Tuple[Any, ...],
189 kwargs: Dict[str, Any],
190 ) -> bool:
191 handled = False
193 with self._lock:
194 funcs = list(self._events.get(event, OrderedDict()).values())
195 for f in funcs:
196 self._emit_run(f, args, kwargs)
197 handled = True
199 return handled
201 def emit(
202 self: Self,
203 event: str,
204 *args: Any,
205 **kwargs: Any,
206 ) -> bool:
207 """Emit `event`, passing `*args` and `**kwargs` to each attached
208 function. Returns `True` if any functions are attached to `event`;
209 otherwise returns `False`.
211 Example:
213 ```py
214 ee.emit('data', '00101001')
215 ```
217 Assuming `data` is an attached function, this will call
218 `data('00101001')'`.
219 """
220 handled = self._call_handlers(event, args, kwargs)
222 if not handled:
223 self._emit_handle_potential_error(event, args[0] if args else None)
225 return handled
227 def once(
228 self: Self,
229 event: str,
230 f: Optional[Callable] = None,
231 ) -> Callable:
232 """The same as `ee.on`, except that the listener is automatically
233 removed after being called.
234 """
236 def _wrapper(f: Callable) -> Callable:
237 def g(
238 *args: Any,
239 **kwargs: Any,
240 ) -> Any:
241 with self._lock:
242 # Check that the event wasn't removed already right
243 # before the lock
244 if event in self._events and f in self._events[event]:
245 self._remove_listener(event, f)
246 else:
247 return None
248 # f may return a coroutine, so we need to return that
249 # result here so that emit can schedule it
250 return f(*args, **kwargs)
252 self._add_event_handler(event, f, g)
253 return f
255 if f is None:
256 return _wrapper
257 else:
258 return _wrapper(f)
260 def _remove_listener(self: Self, event: str, f: Callable) -> None:
261 """Naked unprotected removal."""
262 if event in self._events:
263 self._events[event].pop(f)
264 if not self._events[event]:
265 del self._events[event]
267 def remove_listener(self: Self, event: str, f: Callable) -> None:
268 """Removes the function `f` from `event`."""
269 with self._lock:
270 self._remove_listener(event, f)
272 def remove_all_listeners(self: Self, event: Optional[str] = None) -> None:
273 """Remove all listeners attached to `event`.
274 If `event` is `None`, remove all listeners on all events.
275 """
276 with self._lock:
277 if event is not None:
278 self._events[event] = OrderedDict()
279 else:
280 self._events = dict()
282 def listeners(self: Self, event: str) -> List[Callable]:
283 """Returns a list of all listeners registered to the `event`."""
284 return list(self._events.get(event, OrderedDict()).keys())