"""Litestar channels backend backed by SQLSpec's EventChannel."""
import asyncio
import base64
import hashlib
import re
from typing import TYPE_CHECKING, Any
from litestar.channels.backends.base import ChannelsBackend
from sqlspec.extensions.events._buffer import enqueue_with_capacity, validate_queue_capacity
from sqlspec.utils.logging import get_logger
if TYPE_CHECKING:
from collections.abc import AsyncGenerator, Iterable, Sequence
from sqlspec.extensions.events import AsyncEventChannel
logger = get_logger("sqlspec.extensions.litestar.channels")
_IDENTIFIER_PATTERN = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
class SQLSpecChannelsBackend(ChannelsBackend):
"""A Litestar Channels backend implemented on top of SQLSpec's EventChannel.
This backend allows Litestar's ChannelsPlugin to use a SQLSpec database as the
broker. Under the hood it relies on SQLSpec's events extension, which can be
configured to use a durable table queue or native adapter backends.
"""
def __init__(
self,
event_channel: "AsyncEventChannel",
*,
channel_prefix: str = "litestar",
poll_interval: float = 0.2,
output_queue_capacity: int | None = None,
) -> None:
if not _IDENTIFIER_PATTERN.match(channel_prefix):
msg = f"channel_prefix must be a valid identifier, got: {channel_prefix!r}"
raise ValueError(msg)
if poll_interval <= 0:
msg = "poll_interval must be greater than zero"
raise ValueError(msg)
self._output_queue_capacity = validate_queue_capacity(
output_queue_capacity, name="output_queue_capacity", error_type=ValueError
)
self._event_channel = event_channel
self._channel_prefix = channel_prefix
self._poll_interval = poll_interval
self._output_queue: asyncio.Queue[tuple[str, bytes]] | None = None
self._dropped_message_count = 0
self._shutdown = asyncio.Event()
self._tasks: dict[str, asyncio.Task[None]] = {}
self._to_db_channel: dict[str, str] = {}
self._to_litestar_channel: dict[str, str] = {}