Events#
Pub/sub event channel system with database-backed queue support. Provides both sync and async channels with listener management and native backend integration for databases that support LISTEN/NOTIFY.
Transport selection#
Choose a transport by delivery semantics:
notify— transient native notification with no replay or retry.notify_queue— durable competing-consumer queue with a native wakeup hint.poll_queue— durable competing-consumer queue discovered by polling.aq— Oracle Advanced Queuing, with explicit provisioning and privileges.txeventq— Oracle Transactional Event Queues, with explicit provisioning and privileges.
The durable queue is the source of truth for notify_queue; native
notifications only prompt consumers to check it. Durable event queues are not
browser fan-out transports.
Set extension_config["events"]["backend"] to select the transport. The
adapter driver_features["events_backend"] value is used only when the
extension setting is absent. Retired transport names fail with an explicit
canonical replacement instead of silently changing delivery semantics.
Adapter family |
Available transports |
Default |
|---|---|---|
PostgreSQL ( |
|
|
Oracle |
|
|
Other database adapters |
|
|
Configure the transport and durable reconciliation cadence independently:
from sqlspec.adapters.asyncpg import AsyncpgConfig
config = AsyncpgConfig(
connection_config={"dsn": "postgresql://...", "max_size": 5},
extension_config={
"events": {
"backend": "notify_queue",
"event_poll_interval": 1.0,
}
},
)
event_poll_interval controls how often durable transports reconcile the
queue when no native wakeup arrives. The older poll_interval setting is a
compatibility input; event_poll_interval takes precedence when both are
provided.
polling is not a SQLSpec backend name. Litestar Queues uses it for the
fallback worker mode where no push wakeup transport is available and the
worker waits for its configured polling interval.
Native LISTEN/NOTIFY model#
Native PG event backends (asyncpg, psycopg async/sync, psqlpy)
hold a single persistent LISTEN connection per backend instance. Each
backend owns its own listener hub that:
Acquires the dedicated LISTEN connection lazily on first subscribe.
Emits
LISTEN <channel>exactly once per channel andUNLISTENon unsubscribe / shutdown.Dispatches incoming notifications into per-channel
asyncio.Queueinstances (orqueue.Queuefor the sync psycopg variant).Serializes subscribe / unsubscribe under a lock so concurrent callers cannot race on driver-level statements that share the connection.
The listener lease is held for the backend lifetime. Publishers use separate,
short-lived pooled sessions, so a shared PostgreSQL pool must configure at
least two connections: max_size >= 2 for asyncpg/psycopg and
max_db_pool_size >= 2 for psqlpy. Native backend construction rejects a
configured pool of size one instead of allowing publication to deadlock behind
the listener.
The Oracle native backends (aq and
txeventq) use an analogous pattern: a per-channel
queue-handle cache backed by a single dedicated session per backend instance.
dequeue honors min(poll_interval, aq_wait_seconds) as its wait bound so
the caller's polling cadence is respected.
ack / nack semantics are unchanged. notify remains
fire-and-forget; notify_queue acknowledges through the durable table queue.
Batch publication and recovery#
Both AsyncEventChannel and
SyncEventChannel provide
publish_many(events). Each item is a
(channel, payload, metadata) tuple, and the returned event IDs preserve
input order. Batch-capable implementations commit each grouped call atomically.
Backends without publish_many, including the current Oracle native
transports, use an ordered single-event fallback; that fallback is not atomic
across the batch.
poll_queue bulk-inserts the independent event rows with one publisher
session and transaction. PostgreSQL notify publishes the normal per-event
notification envelopes in one publisher transaction, so each notification
keeps its existing payload and size limit.
For PostgreSQL notify_queue, SQLSpec bulk-inserts all durable rows and then
emits one compact marker per channel in the same transaction. A marker contains
only marker_id and batch_size; it is a wakeup hint, not a batch event
envelope or source of truth. A consumer uses that marker to drain the queued
rows without waiting for another notification. Duplicate markers are ignored,
and a missing marker is recovered by durable reconciliation on
event_poll_interval.
Oracle native event backends#
Oracle provides two native messaging backends in addition to the default
poll_queue:
aq— classic Oracle Advanced Queuing (AQ).txeventq— Oracle Transactional Event Queues (TxEventQ).
Both share the same client path and JSON payloads; they differ only in how the
underlying queue is provisioned. Select one via events.backend:
from sqlspec.adapters.oracledb import OracleAsyncConfig
config = OracleAsyncConfig(
connection_config={"dsn": "..."},
extension_config={"events": {"backend": "txeventq"}},
)
The default remains poll_queue, which works on every Oracle edition
without extra privileges; both native backends are opt-in.
Requirements#
Thin mode — both backends run in python-oracledb's default Thin mode; no Instant Client / Thick mode is required.
JSON payloads require Oracle Database 21c or newer (23ai satisfies this).
Privileges — the connecting user needs
DBMS_AQADMaccess. Grantaq_administrator_role, aq_user_roleandEXECUTE ON dbms_aq.
Provisioning#
The backend attaches to an existing queue; it does not create one. Provision the
queue with DBMS_AQADM first:
aq—create_queue_table(queue_payload_type => 'JSON')+create_queue+start_queue.txeventq—create_transactional_event_queue(queue_payload_type => 'JSON', multiple_consumers => FALSE)+start_queue.
By default all channels route through a single physical queue
(SQLSPEC_EVENTS_QUEUE) with the channel carried in the event envelope. To
isolate channels onto per-channel physical queues, template the queue name with
{channel} via the aq_queue setting (for example
"aq_queue": "SQLSPEC_EVT_{channel}") and provision one queue per channel.
Channels#
- class sqlspec.extensions.events.AsyncEventChannel[source]#
Bases:
objectEvent channel for asynchronous database configurations.
- async publish_many(events)[source]#
Publish independent events in one grouped operation when supported.
Backend-native implementations are atomic per grouped call. A backend without
publish_manyuses an ordered single-event fallback, which is not atomic across the full batch.
- iter_events(channel, *, event_poll_interval=None, poll_interval=None)[source]#
Yield events as they become available.
- Return type:
- class sqlspec.extensions.events.SyncEventChannel[source]#
Bases:
objectEvent channel for synchronous database configurations.
- publish_many(events)[source]#
Publish independent events in one grouped operation when supported.
Backend-native implementations are atomic per grouped call. A backend without
publish_manyuses an ordered single-event fallback, which is not atomic across the full batch.
- iter_events(channel, *, event_poll_interval=None, poll_interval=None)[source]#
Yield events as they become available.
- Return type:
Listeners#
Event Queue#
The durable table queue is available for SQL Server through arrow_odbc when
configured with Microsoft ODBC Driver 18. It uses SQL Server DATETIME2(6)
timestamps and NVARCHAR payload columns.
Durable queue migrations reconcile missing tables and additive columns from the
adapter store's canonical DDL. Set events.manage_schema=False when an
external migration system owns the queue schema. Set
events.create_schema=False to avoid creating an absent queue table. Column
renames, drops, and type changes still require an explicit migration.
Queue table storage options#
Put durable queue tuning under extension_config["events"]. SQLSpec
validates the mapping against the selected adapter; an unknown key or an option
that the backend cannot honor raises ImproperConfigurationError.
PostgreSQL (
asyncpg,psycopg, andpsqlpy) acceptsfillfactor,autovacuum_vacuum_scale_factor, andautovacuum_analyze_scale_factor. These queue-table settings are opt-in.BigQuery accepts
partitioning,partition_expiration_days, andrequire_partition_filterforavailable_atpartitioning. Existing channel and status clustering is preserved.SQLite and AioSQLite accept
pragma_profileandpragma_overrides. PRAGMAs run once during schema preparation rather than on every queue operation.Oracle Database accepts
compression,partitioning,in_memory, andtable_options. See Extension Table Storage Options for how SQLSpec handles optional database capabilities.
CockroachDB deliberately does not expose its session-table row TTL for durable queues. Queue acknowledgement and retention have different semantics, so session-only TTL keys are rejected rather than translated to destructive queue DDL. Other adapters use their existing queue-table defaults and reject these backend-specific storage keys.
For example, configure a PostgreSQL queue for a write-heavy workload:
config = AsyncpgConfig(
connection_config={"dsn": "postgresql://localhost/app"},
extension_config={
"events": {
"backend": "notify_queue",
"fillfactor": 70,
"autovacuum_vacuum_scale_factor": 0.05,
"autovacuum_analyze_scale_factor": 0.02,
}
},
)
- final class sqlspec.extensions.events.AsyncTableEventQueue[source]#
Bases:
_BaseTableEventQueueAsync table queue implementation.
- final class sqlspec.extensions.events.SyncTableEventQueue[source]#
Bases:
_BaseTableEventQueueSync table queue implementation.
Store#
- class sqlspec.extensions.events.BaseEventQueueStore[source]#
-
Base class for adapter-specific event queue DDL generators.
This class provides a hook-based pattern for DDL generation. Adapters only need to override _column_types() and optionally any hook methods for dialect-specific variations:
_string_type(length): String type syntax (default: VARCHAR(N))
_integer_type(): Integer type syntax (default: INTEGER)
_timestamp_default(): Timestamp default expression (default: CURRENT_TIMESTAMP)
_primary_key_syntax(): Inline PRIMARY KEY clause (default: empty, PK on column)
_table_clause(): Additional table options (default: empty)
For complex dialects (Oracle PL/SQL, BigQuery CLUSTER BY), adapters may override _table_ddl() directly.
- property settings: dict[str, TypeAliasForwardRef('typing.Any')]#
Return extension settings for adapters to inspect.
- prepare_schema_sync(driver)[source]#
Prepare adapter-specific schema decisions with a synchronous driver.
- Return type:
- async prepare_schema_async(driver)[source]#
Prepare adapter-specific schema decisions with an asynchronous driver.
- Return type:
- reconcile_schema_sync(driver)[source]#
Apply additive queue-table changes with a synchronous driver.
- Return type:
Models#
Protocols#
- class sqlspec.extensions.events.AsyncEventBackendProtocol[source]#
Bases:
ProtocolProtocol for async event backends.
All async event backends (native or queue-based) must implement these methods.
- async publish_many(events)[source]#
Publish independent events as one grouped backend operation.
Implementations with native batching must preserve input order in the returned event IDs. Backends without native batching are invoked through the event channel's single-event fallback.
- __init__(*args, **kwargs)#
- class sqlspec.extensions.events.SyncEventBackendProtocol[source]#
Bases:
ProtocolProtocol for sync event backends.
All sync event backends (native or queue-based) must implement these methods.
- publish_many(events)[source]#
Publish independent events as one grouped backend operation.
Implementations with native batching must preserve input order in the returned event IDs. Backends without native batching are invoked through the event channel's single-event fallback.
- __init__(*args, **kwargs)#
Payload Helpers#
- sqlspec.extensions.events.encode_notify_payload(event_id, payload, metadata)[source]#
Encode event data as JSON for NOTIFY payload.
- Raises:
EventChannelError -- If the encoded payload exceeds PostgreSQL's 8KB limit.
- Return type:
Utility Functions#
- sqlspec.extensions.events.load_native_backend(config, backend_name, extension_settings, adapter_name=None)[source]#
Load adapter-specific native backend if available.
- sqlspec.extensions.events.resolve_poll_interval(poll_interval, default)[source]#
Resolve poll interval with validation.
- Return type:
- sqlspec.extensions.events.resolve_event_poll_interval(event_poll_interval, poll_interval, default)[source]#
Resolve the event reconciliation interval with compatibility precedence.
- Return type: