| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284 |
- # -*- coding: utf-8 -*-
- from collections import OrderedDict
- from threading import Lock
- from typing import (
- Any,
- Callable,
- Dict,
- List,
- Mapping,
- Optional,
- overload,
- Set,
- Tuple,
- TypeVar,
- Union,
- )
- Self = Any
- class PyeeException(Exception):
- """An exception internal to pyee. Deprecated in favor of PyeeError."""
- class PyeeError(PyeeException):
- """An error internal to pyee."""
- Handler = TypeVar("Handler", bound=Callable)
- class EventEmitter:
- """The base event emitter class. All other event emitters inherit from
- this class.
- Most events are registered with an emitter via the `on` and `once`
- methods, and fired with the `emit` method. However, pyee event emitters
- have two *special* events:
- - `new_listener`: Fires whenever a new listener is created. Listeners for
- this event do not fire upon their own creation.
- - `error`: When emitted raises an Exception by default, behavior can be
- overridden by attaching callback to the event.
- For example:
- ```py
- @ee.on('error')
- def on_error(message):
- logging.err(message)
- ee.emit('error', Exception('something blew up'))
- ```
- All callbacks are handled in a synchronous, blocking manner. As in node.js,
- raised exceptions are not automatically handled for you---you must catch
- your own exceptions, and treat them accordingly.
- """
- def __init__(self: Self) -> None:
- self._events: Dict[
- str,
- "OrderedDict[Callable, Callable]",
- ] = dict()
- self._lock: Lock = Lock()
- def __getstate__(self: Self) -> Mapping[str, Any]:
- state = self.__dict__.copy()
- del state["_lock"]
- return state
- def __setstate__(self: Self, state: Mapping[str, Any]) -> None:
- self.__dict__.update(state)
- self._lock = Lock()
- @overload
- def on(self: Self, event: str) -> Callable[[Handler], Handler]: ...
- @overload
- def on(self: Self, event: str, f: Handler) -> Handler: ...
- def on(
- self: Self, event: str, f: Optional[Handler] = None
- ) -> Union[Handler, Callable[[Handler], Handler]]:
- """Registers the function `f` to the event name `event`, if provided.
- If `f` isn't provided, this method calls `EventEmitter#listens_to`, and
- otherwise calls `EventEmitter#add_listener`. In other words, you may either
- use it as a decorator:
- ```py
- @ee.on('data')
- def data_handler(data):
- print(data)
- ```
- Or directly:
- ```py
- ee.on('data', data_handler)
- ```
- In both the decorated and undecorated forms, the event handler is
- returned. The upshot of this is that you can call decorated handlers
- directly, as well as use them in remove_listener calls.
- Note that this method's return type is a union type. If you are using
- mypy or pyright, you will probably want to use either
- `EventEmitter#listens_to` or `EventEmitter#add_listener`.
- """
- if f is None:
- return self.listens_to(event)
- else:
- return self.add_listener(event, f)
- def listens_to(self: Self, event: str) -> Callable[[Handler], Handler]:
- """Returns a decorator which will register the decorated function to
- the event name `event`:
- ```py
- @ee.listens_to("event")
- def data_handler(data):
- print(data)
- ```
- By only supporting the decorator use case, this method has improved
- type safety over `EventEmitter#on`.
- """
- def on(f: Handler) -> Handler:
- self._add_event_handler(event, f, f)
- return f
- return on
- def add_listener(self: Self, event: str, f: Handler) -> Handler:
- """Register the function `f` to the event name `event`:
- ```
- def data_handler(data):
- print(data)
- h = ee.add_listener("event", data_handler)
- ```
- By not supporting the decorator use case, this method has improved
- type safety over `EventEmitter#on`.
- """
- self._add_event_handler(event, f, f)
- return f
- def _add_event_handler(self: Self, event: str, k: Callable, v: Callable):
- # Fire 'new_listener' *before* adding the new listener!
- self.emit("new_listener", event, k)
- # Add the necessary function
- # Note that k and v are the same for `on` handlers, but
- # different for `once` handlers, where v is a wrapped version
- # of k which removes itself before calling k
- with self._lock:
- if event not in self._events:
- self._events[event] = OrderedDict()
- self._events[event][k] = v
- def _emit_run(
- self: Self,
- f: Callable,
- args: Tuple[Any, ...],
- kwargs: Dict[str, Any],
- ) -> None:
- f(*args, **kwargs)
- def event_names(self: Self) -> Set[str]:
- """Get a set of events that this emitter is listening to."""
- return set(self._events.keys())
- def _emit_handle_potential_error(self: Self, event: str, error: Any) -> None:
- if event == "error":
- if isinstance(error, Exception):
- raise error
- else:
- raise PyeeError(f"Uncaught, unspecified 'error' event: {error}")
- def _call_handlers(
- self: Self,
- event: str,
- args: Tuple[Any, ...],
- kwargs: Dict[str, Any],
- ) -> bool:
- handled = False
- with self._lock:
- funcs = list(self._events.get(event, OrderedDict()).values())
- for f in funcs:
- self._emit_run(f, args, kwargs)
- handled = True
- return handled
- def emit(
- self: Self,
- event: str,
- *args: Any,
- **kwargs: Any,
- ) -> bool:
- """Emit `event`, passing `*args` and `**kwargs` to each attached
- function. Returns `True` if any functions are attached to `event`;
- otherwise returns `False`.
- Example:
- ```py
- ee.emit('data', '00101001')
- ```
- Assuming `data` is an attached function, this will call
- `data('00101001')'`.
- """
- handled = self._call_handlers(event, args, kwargs)
- if not handled:
- self._emit_handle_potential_error(event, args[0] if args else None)
- return handled
- def once(
- self: Self,
- event: str,
- f: Optional[Callable] = None,
- ) -> Callable:
- """The same as `ee.on`, except that the listener is automatically
- removed after being called.
- """
- def _wrapper(f: Callable) -> Callable:
- def g(
- *args: Any,
- **kwargs: Any,
- ) -> Any:
- with self._lock:
- # Check that the event wasn't removed already right
- # before the lock
- if event in self._events and f in self._events[event]:
- self._remove_listener(event, f)
- else:
- return None
- # f may return a coroutine, so we need to return that
- # result here so that emit can schedule it
- return f(*args, **kwargs)
- self._add_event_handler(event, f, g)
- return f
- if f is None:
- return _wrapper
- else:
- return _wrapper(f)
- def _remove_listener(self: Self, event: str, f: Callable) -> None:
- """Naked unprotected removal."""
- if event in self._events:
- self._events[event].pop(f)
- if not self._events[event]:
- del self._events[event]
- def remove_listener(self: Self, event: str, f: Callable) -> None:
- """Removes the function `f` from `event`."""
- with self._lock:
- self._remove_listener(event, f)
- def remove_all_listeners(self: Self, event: Optional[str] = None) -> None:
- """Remove all listeners attached to `event`.
- If `event` is `None`, remove all listeners on all events.
- """
- with self._lock:
- if event is not None:
- self._events[event] = OrderedDict()
- else:
- self._events = dict()
- def listeners(self: Self, event: str) -> List[Callable]:
- """Returns a list of all listeners registered to the `event`."""
- return list(self._events.get(event, OrderedDict()).keys())
|