Skip to content

blackbull.event

blackbull.event

Event dispatch with three lifetime contracts.

Interceptors run in order and propagate failures. Blocking observers finish before emit returns but isolate failures. Detached observers do not extend the event lifetime; use blocking observers for cleanup that must finish first.

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.

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 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

Wait up to shutdown_timeout for quiescence; warn and cancel remaining observers.

drain(timeout=5.0) async

Wait for observer quiescence, including tasks emitted by observers.

Return False on timeout, True on quiescence; never cancel pending work.

emit(event, *, timeout=None) async

Dispatch event to all registered handlers.

Delivery order:

  1. Interceptors — awaited in registration order; their exceptions propagate (and abort the remaining interceptors).
  2. Blocking observers — awaited in registration order; their exceptions are caught and logged.
  3. Detached observers — scheduled as independent tasks (isolated), tracked so they can be drained at shutdown via aclose.

timeout limits the wait for each interceptor and blocking observer. On expiry, cancellation is requested, a warning identifies the handler, and dispatch continues without waiting for it to stop. None disables this limit.

intercept(event_name, handler)

Register an interception handler for event_name.

on(event_name, handler, blocking=False)

Register an observation handler for event_name.

With blocking=False (the default) the handler is scheduled as an independent task when the event fires — it never delays the emitter. With blocking=True the handler is awaited in registration order, subject to emit()'s timeout and cancellation; its exceptions are logged.