Lauren logoLauren
← Home
Export this page

Events

Typed application events: @on_event handlers, the per-app EventBus and the request-scoped EventPublisher.

Handlers

on_event

Subscribe a provider method to an event class; it is discovered at startup.

event_type=None infers the class from the annotation of the first parameter after self (a union subscribes to each member). The handler also receives instances of subclasses. Other parameters are an EventContext (any name) or dependencies the provider's module can resolve.

mode is "inline" (awaited inside emit(), part of the publisher's unit of work), "concurrent" (a tracked task started after the inline handlers succeed) or "background" (after the HTTP response when published through an EventPublisher in a request, otherwise like "concurrent"). Higher priority runs first; ties keep discovery order. errors defaults to "propagate" for inline handlers (the exception aborts the emission and re-raises from emit()) and "isolate" for deferred ones (logged, signalled as EventHandlerFailed, returned in EmitResult.errors <lauren.events.EmitResult.errors>). enabled=False keeps the handler validated but never calls it.

The decorator only records metadata and returns the method unchanged; stacking it subscribes the method to several events.

EventContext

Delivery details for one handler call, injected into a parameter annotated EventContext.

A handler declares it under any name, after the event parameter:

python
@on_event()
async def record(self, event: OrderPlaced, ctx: EventContext) -> None:
    ctx.log.log(f"order {event.order_id} ({ctx.event_id})")

The request is deliberately not exposed: requests are pooled and reused, and deferred handlers outlive them. Put whatever a handler needs from the request into the event.

Publishing

EventBus

The application's event bus: one per app, injectable everywhere as a singleton.

emit() delivers an event to every @on_event handler subscribed to its class or a base class. Inline handlers run first, by priority, inside the call — sharing one OperationScope per emission for their request-scoped dependencies; then deferred ("concurrent" / "background") handlers start as tracked tasks, each in its own OperationScope. Use EventPublisher instead to run inline handlers in the current request's scope.

recording(), drain() and disabled() are testing aids. A bus built directly (EventBus()) has no handlers, which makes it a convenient stand-in when unit-testing a service that publishes.

EventPublisher

The request-scoped publisher: inline handlers join the current request's DI scope.

Inject it (events: EventPublisher) into a route handler, a WebSocket hook, a request-scoped provider or an @on_event handler — it is provided by the framework. emit() behaves like EventBus.emit, except that inline handlers resolve Scope.REQUEST dependencies from the publisher's scope (the same DbSession as the controller), and "background" handlers in an HTTP request run after the response is sent, on the request's BackgroundTasks queue, each in a fresh OperationScope. In a WebSocket connection there is no response, so background handlers run like concurrent ones.

Resolving it anywhere else (a singleton, a job, app.scope()) raises LifecycleViolationError — inject EventBus there.

EmitResult

What one emit() call did.

handled counts handlers that finished without an error during the call (inline handlers, plus deferred ones when wait=True); scheduled counts deferred handlers queued or started; errors holds an EventHandlerFailed record for every "isolate" handler that failed during the call.

After-commit delivery

TransactionPhase

When a deferred emission (EventPublisher.emit(after=...)) is delivered.

UnitOfWork

A transaction boundary that EventPublisher.emit(after=TransactionPhase.COMMIT) can wait for.

Core defines only the contract; a database integration (for example a request-scoped session) implements it and is registered as a provider — either with provides=(UnitOfWork,) or simply by having both methods, since the protocol also matches structurally. The unit of work awaits each callback once: on_commit callbacks after a successful commit, on_rollback callbacks after a rollback.

Testing

EventRecording

Events and handler outcomes captured by EventBus.recording() <lauren.events.EventBus.recording>.

Everything is recorded in order: events as they are emitted, calls and failures as handlers finish.

Configuration

EventsModule

Optional tuning for the application event bus; events work without it.

Import the module for_root returns into any module of the app.