-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsignal.py
More file actions
56 lines (42 loc) · 2.01 KB
/
Copy pathsignal.py
File metadata and controls
56 lines (42 loc) · 2.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
"""Observer as an importable, typed building block.
A subscriber is any callable taking the event. ``Signal`` broadcasts to its
subscribers in subscription order, with the failure policy stated up front:
by default a raising subscriber propagates (fail fast); pass ``on_error`` to
isolate subscribers from each other instead.
"""
from __future__ import annotations
from collections.abc import Callable, Iterator
from typing import Generic, TypeVar
Event = TypeVar("Event")
Subscriber = Callable[[Event], None]
ErrorPolicy = Callable[[Exception, "Subscriber[Event]"], None]
class Signal(Generic[Event]):
"""A broadcast list of callables with an explicit failure policy."""
def __init__(self, on_error: ErrorPolicy[Event] | None = None) -> None:
self._subscribers: list[Subscriber[Event]] = []
self._on_error = on_error
def subscribe(self, subscriber: Subscriber[Event]) -> Subscriber[Event]:
"""Add a subscriber (appending = subscribing); usable as a decorator."""
self._subscribers.append(subscriber)
return subscriber
def unsubscribe(self, subscriber: Subscriber[Event]) -> None:
"""Remove a subscriber; ``ValueError`` if it never subscribed."""
self._subscribers.remove(subscriber)
def emit(self, event: Event) -> None:
"""Notify every subscriber in order.
Iterates over a copy, so subscribers may unsubscribe (even
themselves) mid-broadcast. A subscriber's exception propagates unless
an ``on_error`` policy was given, in which case the policy is called
and the remaining subscribers still run.
"""
for subscriber in list(self._subscribers):
try:
subscriber(event)
except Exception as err:
if self._on_error is None:
raise
self._on_error(err, subscriber)
def __iter__(self) -> Iterator[Subscriber[Event]]:
return iter(self._subscribers)
def __len__(self) -> int:
return len(self._subscribers)