aaa.events¶
Events¶
Event ¶
Bases: BaseModel
Immutable, CloudEvents-compatible envelope. The event log is the source of truth.
caused ¶
Derive a follow-up event preserving the causal chain and stream. The id is a pure function of (parent, type, data), so re-deriving after a crash is idempotent.
BlobRef ¶
Bases: BaseModel
Claim-check pointer to large payloads held in a BlobStore.
SchemaRegistry ¶
Versioned payload schemas with upcasters that migrate old events on read.
validate ¶
Return the event upcast to the latest version and validated; unknown refs pass.
cloudevents ¶
CloudEvents 1.0 (structured JSON mode) <-> aaa Event. Dependency-free by design: the output is spec-conformant and interoperates with the official SDK / any broker.
Stores¶
EventStore ¶
Bases: Protocol
append
async
¶
Atomically append; expected_seq is the stream's last seq (-1 = empty stream).
Raises ConcurrencyError on mismatch. IDEMPOTENT: an event whose id is already in the
stream is not appended again; the existing stored event is returned with dup=True.
subscribe ¶
Replay from from_seq, then tail live appends.
has
async
¶
O(1) 'is this event id already in the stream?' (ids are unique per stream).
run_index
async
¶
Every run's started event and (if any) completed event, for crash recovery.
scrub
async
¶
Erase every event's payload in stream (see aaa.events.event.scrubbed), keep the
envelope, and drop the stream's snapshots and any bus copies of its events. Idempotent;
returns the number of events in the stream. Only meaningful for finished runs.
delete_stream
async
¶
Remove stream entirely (events, snapshots, bus copies). Returns events removed.
SQLiteEventStore ¶
Durable EventStore on stdlib sqlite3 (no extra dependency).
- One dedicated thread owns the connection, so the event loop never blocks on disk.
- WAL + synchronous=NORMAL: crash-safe (no corruption); a power loss may drop the last few commits, never reorder or tear them. Use synchronous="FULL" for strict durability.
- Append is one IMMEDIATE transaction: the OCC check and the inserts are atomic.
- Append is idempotent on (stream, id), which is what makes crash recovery safe.
subscribewakes instantly for same-process appends and polls for other processes.
PostgresEventStore ¶
trim_outbox
async
¶
Delete bus entries older than the newest keep_last per partition. Consumers that fall
further behind than this lose the gap; size it to your worst-case outage.
RedisEventStore ¶
trim_outbox
async
¶
Trim each partition's bus stream to roughly the newest keep_last entries.
Projections and snapshots¶
Projection ¶
Projection(
initial: Callable[[], S],
fold: Callable[[S, Event], S],
*,
name: str = "",
version: int = 1,
state_type: Any = None,
every: int = 64,
)
Bases: Generic[S]
State = fold over a stream's events. fold(state, event) may mutate and return state
(the state object is private to the load). With a name and state_type, the state is
snapshotted every every events and later loads cost O(tail), not O(history).
Bump version whenever the fold logic or state shape changes: stale snapshots are ignored.
Loaded
dataclass
¶
Bases: Generic[S]
StoredSnapshot ¶
Bases: BaseModel
A projection's state as of seq (inclusive). Pure derived data: always safe to drop.
SnapshotCapable ¶
Bases: Protocol
save_snapshot
async
¶
Must be monotonic: ignore a snapshot whose seq is not newer than the stored one.
Buses¶
EventBus ¶
InProcessBus ¶
InProcessBus(
queue_size: int = 1024,
*,
redeliver_base_s: float = 0.2,
redeliver_max_s: float = 5.0,
)
Bounded-queue bus. Publishing blocks when a subscriber is full (backpressure).
concurrency=1 preserves publish order per subscription. Handler exceptions are
logged and isolated: one bad handler never kills the bus or other subscribers.
Subscriptions sharing a group compete for events (one delivery per group).
DistributedBus ¶
DistributedBus(
backend: BusBackend,
*,
member: str | None = None,
batch: int = 64,
poll_s: float = 0.5,
lease_ttl_s: float = 6.0,
tick_s: float = 1.0,
handover_timeout_s: float = 30.0,
redeliver_base_s: float = 0.2,
redeliver_max_s: float = 5.0,
)
EventBus over a BusBackend. publish is a no-op: the store already enqueued the event
atomically with appending it (see PostgresEventStore / RedisEventStore / MemoryBusBackend).
execution_guard ¶
Cross-process mutual exclusion for side-effecting work (same lease semantics as partitions).
publish
async
¶
No-op by design (the store enqueues atomically with the append).
__aexit__
async
¶
Graceful: stop consuming, let in-flight handlers finish, commit cursors, release leases.
abort
async
¶
Crash: cancel everything, release NOTHING (leases/membership must expire).
drain
async
¶
Wait until this process's consumers have handled everything currently in the log.
ClientBus ¶
For processes that only SUBMIT and OBSERVE runs (an API server, a CLI): it delivers and consumes nothing, so the process never becomes a worker. Pair it with a shared store; the workers' DistributedBus picks the events up from the store's outbox.
BusBackend ¶
MemoryBusBackend ¶
In-process reference backend (shared object = shared 'cluster'). Used by tests and as the executable specification the Postgres/Redis backends must match.
forget ¶
Erase the payload of this stream's queued entries (positions are ids, so entries stay).
partition_of ¶
Stable across processes and Python versions (unlike hash()).
Durable acknowledgement¶
Deferral ¶
Holds the acknowledgement of the event currently being handled.
A handler that hands its real work to a background task calls defer_ack() first and
release()s when that work is finished (in a finally). Until then a durable bus will not
commit its cursor past the event, so if the process dies the event is redelivered. Releasing
twice is harmless. Buses without durable delivery return an inert Deferral.
Execution guards¶
guard ¶
Execution guards: mutual exclusion for side-effecting work across workers.
A guard answers "may I run THIS call right now?". It is a lease with a heartbeat: acquire it,
renew it while working, and treat a failed renewal as "I am no longer the only one running this".
LocalGuard covers a single process; LeaseGuard reuses the bus backend's partition-lease
primitive (Postgres advisory lock / Redis SET NX PX / memory) so it works across processes with
exactly the same failure semantics as partition ownership.
ExecutionGuard ¶
Bases: Protocol
renew
async
¶
True while this holder still owns key; False once ownership was lost.
LocalGuard ¶
Single-process exclusion. (Across a restart nothing is held, which is correct: the previous process is gone.)
LeaseGuard ¶
Cross-process exclusion via the bus backend's lease primitive.