Source code for kerno.event

"""A tiny event hub.

``kerno.events`` will be an instance of EventHub, however you can also
use the EventHub class in other circumstances if you want to.

First a library defines an event class to contain event data::

    class EventUserLoggedIn:
        def __init__(self, peto):
            self.peto = peto
            # ...and other data that user code might find important.

Elsewhere, user code will import the event class and write a handler::

    from example_library import EventUserLoggedIn

    def when_user_logs_in(event: EventUserLoggedIn):
        print(event.peto)

Finally, in setup code::

    kerno.events.subscribe(EventUserLoggedIn, when_user_logs_in)

Sometimes it is necessary to avoid leaks by unsubscribing::

    kerno.events.unsubscribe(EventUserLoggedIn, when_user_logs_in)

One of the advantages of having the event hub as a separate object is
that, instead of unsubscribing many handlers, sometimes you can just
throw away the hub, replacing its instance.

The library fires the event by doing::

    kerno.events.broadcast(EventUserLoggedIn(peto=peto))
"""

from typing import Callable, Dict, List


[docs]class EventHub: """A hub for events to be subscribed, fired and removed.""" def __init__(self): # noqa self._events: Dict[type, List[Callable]] = {}
[docs] def subscribe(self, event_cls: type, function: Callable) -> Callable: """Subscribe a handler ``function`` to the ``event_cls``.""" assert isinstance(event_cls, type) assert callable(function) handlers: List[Callable] = self._events.setdefault(event_cls, []) if function in handlers: raise RuntimeError( f"This function is already subscribed to {event_cls}." ) handlers.append(function) return function
[docs] def unsubscribe(self, event_cls: type, function: Callable) -> bool: """Remove a function. Return True if it really was subscribed.""" handlers: List[Callable] = self._events.setdefault(event_cls, []) ret = function in handlers if ret: handlers.remove(function) return ret
[docs] def broadcast(self, event): """Trigger/fire ``event`` -- execute its subscribers. The type of ``event`` must be an exact match: inheritance is not supported. """ handlers: List[Callable] = self._events.setdefault(type(event), []) for fn in handlers: fn(event)