Skip to content

blackbull.event

blackbull.event

Event-driven dispatcher.

Implements the minimal Pub/Sub dispatcher used by BlackBull.on / BlackBull.intercept. Two delivery modes are supported:

  • Interception (intercept): handlers are awaited in registration order; exceptions propagate to the emitter and abort subsequent interceptors.
  • Observation (on): handlers are scheduled as independent asyncio.Tasks (fire-and-forget); exceptions are caught and logged and never reach the emitter or other observers.

Event dataclass

An immutable message dispatched through EventDispatcher.

Attributes:

Name Type Description
name str

The event name (e.g. "app_startup").

detail dict

Arbitrary per-event data. detail is used (rather than payload) to avoid colliding with HTTP/2 and WebSocket protocol terminology already used in the codebase.

EventDispatcher

Minimal Pub/Sub dispatcher with split interception/observation paths.

Interception handlers (intercept) are awaited in registration order; their exceptions propagate to the emitter. Observation handlers (on) are scheduled via asyncio.create_task (fire-and-forget) and their exceptions are caught and logged — they never reach the emitter.

Observer tasks are tracked so they can be drained at shutdown via :meth:aclose. The drain timeout is configured at construction time (shutdown_timeout); any task still running after the timeout is logged at WARNING and cancelled.

aclose() async

Drain pending observer tasks during shutdown.

Waits up to shutdown_timeout seconds (configured at construction) for all in-flight observer tasks to complete. Any tasks still running after the timeout are logged at WARNING and cancelled.

emit(event) async

Dispatch event to all registered handlers.

Interception handlers are awaited in registration order; their exceptions propagate. Observation handlers are scheduled as independent tasks and their exceptions are isolated. Tasks are tracked so they can be drained at shutdown via :meth:aclose.

has_listeners(event_name)

Return True if any interceptor or observer is registered for event_name.

Hot path: callers use this to skip detail-dict / Event construction when no one will receive the event. defaultdict.get returns None for missing keys without inserting an empty list.

intercept(event_name, handler)

Register an interception handler for event_name.

on(event_name, handler)

Register an observation handler for event_name.