Source code for sqlspec.adapters.sqlite.config

"""SQLite database configuration with thread-local connections."""

import logging
import uuid
from contextlib import contextmanager
from typing import TYPE_CHECKING, Any, ClassVar, TypedDict, cast

from typing_extensions import NotRequired

from sqlspec.adapters.sqlite._type_handlers import register_type_handlers
from sqlspec.adapters.sqlite._types import SqliteConnection
from sqlspec.adapters.sqlite.driver import SqliteCursor, SqliteDriver, SqliteExceptionHandler, sqlite_statement_config
from sqlspec.adapters.sqlite.pool import SqliteConnectionPool
from sqlspec.config import ExtensionConfigs, SyncDatabaseConfig
from sqlspec.utils.serializers import from_json, to_json

logger = logging.getLogger(__name__)

if TYPE_CHECKING:
    from collections.abc import Callable, Generator

    from sqlspec.core import StatementConfig
    from sqlspec.observability import ObservabilityConfig


class SqliteConnectionParams(TypedDict):
    """SQLite connection parameters."""

    database: NotRequired[str]
    timeout: NotRequired[float]
    detect_types: NotRequired[int]
    isolation_level: "NotRequired[str | None]"
    check_same_thread: NotRequired[bool]
    factory: "NotRequired[type[SqliteConnection] | None]"
    cached_statements: NotRequired[int]
    uri: NotRequired[bool]


class SqliteDriverFeatures(TypedDict):
    """SQLite driver feature configuration.

    Controls optional type handling and serialization features for SQLite connections.

    enable_custom_adapters: Enable custom type adapters for JSON/UUID/datetime conversion.
        Defaults to True for enhanced Python type support.
        Set to False only if you need pure SQLite behavior without type conversions.
    json_serializer: Custom JSON serializer function.
        Defaults to sqlspec.utils.serializers.to_json.
    json_deserializer: Custom JSON deserializer function.
        Defaults to sqlspec.utils.serializers.from_json.
    """

    enable_custom_adapters: NotRequired[bool]
    json_serializer: "NotRequired[Callable[[Any], str]]"
    json_deserializer: "NotRequired[Callable[[str], Any]]"


__all__ = ("SqliteConfig", "SqliteConnectionParams", "SqliteDriverFeatures")


[docs] class SqliteConfig(SyncDatabaseConfig[SqliteConnection, SqliteConnectionPool, SqliteDriver]): """SQLite configuration with thread-local connections.""" driver_type: "ClassVar[type[SqliteDriver]]" = SqliteDriver connection_type: "ClassVar[type[SqliteConnection]]" = SqliteConnection supports_transactional_ddl: "ClassVar[bool]" = True supports_native_arrow_export: "ClassVar[bool]" = True supports_native_arrow_import: "ClassVar[bool]" = True supports_native_parquet_export: "ClassVar[bool]" = True supports_native_parquet_import: "ClassVar[bool]" = True
[docs] def __init__( self, *, pool_config: "SqliteConnectionParams | dict[str, Any] | None" = None, pool_instance: "SqliteConnectionPool | None" = None, migration_config: "dict[str, Any] | None" = None, statement_config: "StatementConfig | None" = None, driver_features: "SqliteDriverFeatures | dict[str, Any] | None" = None, bind_key: "str | None" = None, extension_config: "ExtensionConfigs | None" = None, observability_config: "ObservabilityConfig | None" = None, ) -> None: """Initialize SQLite configuration. Args: pool_config: Configuration parameters including connection settings pool_instance: Pre-created pool instance migration_config: Migration configuration statement_config: Default SQL statement configuration driver_features: Optional driver feature configuration bind_key: Optional bind key for the configuration extension_config: Extension-specific configuration (e.g., Litestar plugin settings) observability_config: Adapter-level observability overrides for lifecycle hooks and observers """ if pool_config is None: pool_config = {} if "database" not in pool_config or pool_config["database"] == ":memory:": pool_config["database"] = f"file:memory_{uuid.uuid4().hex}?mode=memory&cache=private" pool_config["uri"] = True elif "database" in pool_config: database_path = str(pool_config["database"]) if database_path.startswith("file:") and not pool_config.get("uri"): logger.debug( "Database URI detected (%s) but uri=True not set. " "Auto-enabling URI mode to prevent physical file creation.", database_path, ) pool_config["uri"] = True processed_driver_features: dict[str, Any] = dict(driver_features) if driver_features else {} processed_driver_features.setdefault("enable_custom_adapters", True) json_serializer = processed_driver_features.setdefault("json_serializer", to_json) json_deserializer = processed_driver_features.setdefault("json_deserializer", from_json) base_statement_config = statement_config or sqlite_statement_config if json_serializer is not None: parameter_config = base_statement_config.parameter_config.with_json_serializers( json_serializer, deserializer=json_deserializer ) base_statement_config = base_statement_config.replace(parameter_config=parameter_config) super().__init__( bind_key=bind_key, pool_instance=pool_instance, pool_config=cast("dict[str, Any]", pool_config), migration_config=migration_config, statement_config=base_statement_config, driver_features=processed_driver_features, extension_config=extension_config, observability_config=observability_config, )
def _get_connection_config_dict(self) -> "dict[str, Any]": """Get connection configuration as plain dict for pool creation.""" excluded_keys = {"pool_min_size", "pool_max_size", "pool_timeout", "pool_recycle_seconds", "extra"} return {k: v for k, v in self.pool_config.items() if v is not None and k not in excluded_keys} def _create_pool(self) -> SqliteConnectionPool: """Create connection pool from configuration.""" config_dict = self._get_connection_config_dict() pool = SqliteConnectionPool(connection_parameters=config_dict, **self.pool_config) if self.driver_features.get("enable_custom_adapters", False): self._register_type_adapters() return pool def _register_type_adapters(self) -> None: """Register custom type adapters and converters for SQLite. Called once during pool creation if enable_custom_adapters is True. Registers JSON serialization handlers if configured. """ if self.driver_features.get("enable_custom_adapters", False): register_type_handlers( json_serializer=self.driver_features.get("json_serializer"), json_deserializer=self.driver_features.get("json_deserializer"), ) def _close_pool(self) -> None: """Close the connection pool.""" if self.pool_instance: self.pool_instance.close()
[docs] def create_connection(self) -> SqliteConnection: """Get a SQLite connection from the pool. Returns: SqliteConnection: A connection from the pool """ pool = self.provide_pool() return pool.acquire()
[docs] @contextmanager def provide_connection(self, *args: "Any", **kwargs: "Any") -> "Generator[SqliteConnection, None, None]": """Provide a SQLite connection context manager. Yields: SqliteConnection: A thread-local connection """ pool = self.provide_pool() with pool.get_connection() as connection: yield connection
[docs] @contextmanager def provide_session( self, *args: "Any", statement_config: "StatementConfig | None" = None, **kwargs: "Any" ) -> "Generator[SqliteDriver, None, None]": """Provide a SQLite driver session. Yields: SqliteDriver: A driver instance with thread-local connection """ with self.provide_connection(*args, **kwargs) as connection: driver = self.driver_type( connection=connection, statement_config=statement_config or self.statement_config, driver_features=self.driver_features, ) yield self._prepare_driver(driver)
[docs] def get_signature_namespace(self) -> "dict[str, Any]": """Get the signature namespace for SQLite types. Returns: Dictionary mapping type names to types. """ namespace = super().get_signature_namespace() namespace.update({ "SqliteConnection": SqliteConnection, "SqliteConnectionParams": SqliteConnectionParams, "SqliteConnectionPool": SqliteConnectionPool, "SqliteCursor": SqliteCursor, "SqliteDriver": SqliteDriver, "SqliteDriverFeatures": SqliteDriverFeatures, "SqliteExceptionHandler": SqliteExceptionHandler, }) return namespace