Source code for alchemiq.clickhouse.publisher
"""ClickHousePublisher: outbox relay adapter that batch-inserts into a CH model."""
from __future__ import annotations
from collections.abc import Callable
from typing import Any
from alchemiq.clickhouse.repository import _insert_rows
from alchemiq.outbox.message import OutboxMessage
_FIELDS = ("id", "topic", "payload", "headers", "aggregate_type", "aggregate_id", "event_type")
[docs]
class ClickHousePublisher:
"""Outbox :class:`.Publisher` adapter that batch-inserts messages into a ClickHouse model.
Used as the ``publisher`` argument to :class:`.Relay` when the outbox relay
target is a ClickHouse table rather than a message broker.
E.g.::
from alchemiq.clickhouse.publisher import ClickHousePublisher
from alchemiq.outbox.relay import Relay
publisher = ClickHousePublisher(EventLog)
relay = Relay(publisher)
:param target_model: A :class:`.ClickHouseModel` subclass that receives the
inserted rows.
:param mapper: Optional callable ``(OutboxMessage) -> dict`` to customise how
each message is mapped to a row. When ``None``, the default mapping copies
the standard outbox fields (``id``, ``topic``, ``payload``, ``headers``,
``aggregate_type``, ``aggregate_id``, ``event_type``) that exist on the model.
"""
def __init__(
self,
target_model: type,
*,
mapper: Callable[[OutboxMessage], dict[str, Any]] | None = None,
) -> None:
self._model = target_model
self._mapper = mapper
self._columns = {c.key for c in target_model.__table__.columns} # ty: ignore[unresolved-attribute]
def _to_row(self, message: OutboxMessage) -> dict[str, Any]:
if self._mapper is not None:
return self._mapper(message)
return {f: getattr(message, f) for f in _FIELDS if f in self._columns}
[docs]
async def publish(self, message: OutboxMessage) -> None:
"""Insert a single outbox message into the target CH model."""
await self.publish_batch([message])
[docs]
async def publish_batch(self, messages: list[OutboxMessage]) -> None:
"""Map *messages* to target-model rows and bulk-insert them in one CH call."""
objs = [self._model(**self._to_row(m)) for m in messages]
await _insert_rows(self._model, objs)