Source code for alchemiq.outbox.store
"""Low-level helpers for writing OutboxEvent rows within an active session."""
from __future__ import annotations
from typing import Any
from pydantic import BaseModel
from alchemiq.outbox.models import OutboxEvent
from alchemiq.runtime.session import session_scope
def _write_event(
session: Any,
*,
topic: str,
payload: dict[str, Any],
aggregate_type: str | None = None,
aggregate_id: str | None = None,
event_type: str | None = None,
headers: dict[str, Any] | None = None,
) -> None:
session.add(
OutboxEvent(
topic=topic,
payload=payload,
aggregate_type=aggregate_type,
aggregate_id=aggregate_id,
event_type=event_type,
headers=headers,
)
)
[docs]
async def publish(
topic: str,
payload: dict[str, Any] | BaseModel,
*,
key: str | None = None,
headers: dict[str, Any] | None = None,
) -> None:
"""Write an outbox event row in its own autocommit transaction.
Use this for manual event emission (e.g. from a background task or CLI) when no
model signal is firing. To tie the event to a business transaction, open a
:class:`.UnitOfWork` around the call.
E.g.::
from alchemiq import publish
await publish("user.signed_up", {"id": 1, "email": "ada@x.io"})
# with a Pydantic payload:
await publish("billing.upgraded", BillingUpgraded(plan="pro"), key="user-42")
:param topic: broker routing key (e.g. ``"user.signed_up"``).
:param payload: event data - a plain ``dict`` or a Pydantic ``BaseModel``.
:param key: stored as ``aggregate_id``; typically used as the broker partition key.
:param headers: optional broker-level headers dict.
.. seealso:: :class:`.Relay` - background worker that picks up and delivers these rows.
"""
data = payload.model_dump() if isinstance(payload, BaseModel) else dict(payload)
async with session_scope(write=True) as session:
_write_event(session, topic=topic, payload=data, aggregate_id=key, headers=headers)