Configuration¶
omni-box is configured per component — there is no single global settings object. The main knobs:
OmniBoxDomainService¶
Constructor flags (all optional, defaults sourced from omni_box.core.constants):
max_attempts— default retry budget for new events.scheduled_at_skew_seconds/scheduled_at_max_future_seconds— validation window forscheduled_at.payload_max_bytes— refuse oversized payloads.headers_max_count,header_key_max_length,header_value_max_length— header sanity limits.last_error_max_length,truncation_suffix— howmark_event_failedtruncates long error strings.
OutboxPublisher¶
OutboxPublisher(
repo,
broker,
metrics=None,
publish_timeout=DEFAULT_PUBLISH_TIMEOUT_SECONDS,
concurrency_limit=None,
retry_backoff_base_seconds=DEFAULT_BACKOFF_BASE_SECONDS, # 10.0
retry_backoff_cap_seconds=DEFAULT_BACKOFF_CAP_SECONDS, # 3600.0
)
publish_timeout— hard timeout for a singlebroker.publishcall. It is treated as the broker's problem, not the row's: it costs no attempt and ends the cycle's publishing. Keep an eye on how it compares with the adapter's own patience —AIOKafkaProducer(request_timeout_ms=...)defaults to 40 s against this timeout's 30 s, so with the defaults the step gives up before aiokafka has said anything, and the log shows a publish timeout where a broker error would be more informative. Setrequest_timeout_msbelowpublish_timeout, or raisepublish_timeoutabove it.concurrency_limit— wrapspublish_batchin anasyncio.Semaphore.retry_backoff_base_seconds/retry_backoff_cap_seconds— when a counted failure is retried: afterbase * 2 ** attempts_madeseconds, capped, with equal jitter.0retries on the next cycle. The threecreate_*_processorfactories take the same two.
InboxConsumerRunner¶
Key parameters:
ack_strategy—AT_MOST_ONCE,AT_LEAST_ONCE,EXACTLY_ONCE_INBOX.commit_offset_policy—ON_PERSIST(default) orON_SUCCESS. Only used byAT_LEAST_ONCE.exactly_once_commit_on_failed— whenTrue, also commit on handler failure underEXACTLY_ONCE_INBOX(typically combined with DLQ).process_timeout— per-message handler timeout.concurrency_limit— outer semaphore aroundprocess_one.
EventProcessorBuilder¶
with_lease_ttl(seconds)— lock TTL forwarded toDistributedLockingFetchStrategy.with_job_name(name)— used in log records and metrics labels.with_metrics(metrics)— wiresProcessingMetrics.with_fetch_strategy/with_commit_strategy— override defaults (auto-picked from repository capabilities).
Factories¶
create_outbox_processor, create_inbox_processor, create_dispatching_processor all accept the same observability / safety options:
metricsdlq_storageenable_otelenable_circuit_breaker,circuit_breaker_failure_threshold,circuit_breaker_recovery_timeoutadditional_steps_before,additional_steps_after— splice extra steps into the pipeline.
Optional pydantic-settings integration¶
With the settings extra installed, omni_box.contrib.settings exposes ready-to-use BaseSettings classes for outbox/inbox runners. Inspect that module for the exact field set — these are convenience helpers, not a required entry point.
DI integration (Dishka)¶
With the dishka extra installed, omni_box.contrib.dishka provides ready-made Provider classes. A minimal example lives in docs/examples/dishka_integration.py.
EventDispatcherProvider provides the EventRouter and the DI-aware router around it. PrometheusInboxMetricsProvider(prefix=None) and PrometheusOutboxMetricsProvider(prefix=None) provide the Prometheus collectors under the keys the rest of the kit takes as metrics=: InboxMetrics | None and OutboxMetrics | None. Each reads the section's own settings — BaseInboxSettings / BaseOutboxSettings, which your application registers under exactly those keys — and provides the collector when observability.enable_metrics is on (the default), None otherwise; the factories, OutboxPublisher and InboxConsumerRunner swap in the no-op on None. The collectors come from get_inbox_metrics / get_outbox_metrics, so a container rebuilt per test does not re-register the series. They need the settings extra, and the metrics extra once metrics are enabled — the collector raises ImportError naming it otherwise.
from dishka import Provider, Scope, make_async_container, provide
from omni_box import EventPublisher, OutboxEventRepository, OutboxMetrics, OutboxPublisher
from omni_box.contrib.dishka import EventDispatcherProvider, PrometheusOutboxMetricsProvider
from omni_box.contrib.settings import BaseOutboxSettings
class AppProvider(Provider):
scope = Scope.APP
@provide
def outbox_settings(self) -> BaseOutboxSettings:
return BaseOutboxSettings()
@provide
def publisher(self, repo: OutboxEventRepository, broker: EventPublisher, metrics: OutboxMetrics | None) -> OutboxPublisher:
return OutboxPublisher(repo, broker, metrics=metrics)
container = make_async_container(
EventDispatcherProvider(),
PrometheusOutboxMetricsProvider(prefix="billing"),
AppProvider(),
)
A service with only an inbox registers PrometheusInboxMetricsProvider and BaseInboxSettings alone; the two providers are independent.