Source code for sqlspec.extensions.litestar.channels

"""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] = {}
async def on_startup(self) -> None: self._shutdown.clear() if self._output_queue is None: self._output_queue = asyncio.Queue()