Coverage for snekql/_runtime_selection.py: 91%
55 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-07 21:13 +0300
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-07 21:13 +0300
1"""Backend Runtime Adapter selection for Database initialization."""
3from __future__ import annotations
5from collections.abc import Sequence
6from dataclasses import dataclass
7from pathlib import Path
8from typing import Any, Literal
10from snekql.errors import DatabaseRuntimeError
11from snekql.mariadb.config import Config as MariaDBConfig
12from snekql.model import BackendFamily, Table, require_model_backend
13from snekql.sqlite.config import Config as SQLiteConfig
14from snekql.storage import SchemaPolicy
15from snekql.structured_logging import ResolvedStructuredLogger
16from snekql.validation import NonNegativeFloat, PositiveInt, validate_boundary
18type RuntimeConfig = SQLiteConfig | MariaDBConfig
21@validate_boundary(error_type=DatabaseRuntimeError)
22def _build_legacy_sqlite_config(
23 *,
24 acquire_timeout: NonNegativeFloat,
25 database: Path | Literal[":memory:"],
26 pool_size: PositiveInt,
27) -> SQLiteConfig:
28 """Build an explicit SQLite config for the legacy initializer shape."""
30 return SQLiteConfig(
31 acquire_timeout=acquire_timeout,
32 database=database,
33 pool_size=pool_size,
34 )
37@dataclass(frozen=True)
38class RuntimeSelection:
39 """Selected backend config and runtime initializer for one Database startup."""
41 backend_family: BackendFamily
42 config: RuntimeConfig
44 def validate_model_backends(self, models: Sequence[type[Table[Any]]]) -> None:
45 """Reject models from another backend before runtime startup mutates state."""
47 for model in models:
48 received_backend = require_model_backend(model)
49 if received_backend != self.backend_family:
50 msg = (
51 f"backend mismatch: expected {self.backend_family} model, "
52 f"received {received_backend} model {model.__name__}"
53 )
54 raise DatabaseRuntimeError(msg)
56 async def initialize_runtime(
57 self,
58 models: Sequence[type[Table[Any]]],
59 schema_policy: SchemaPolicy,
60 *,
61 logger: ResolvedStructuredLogger,
62 ) -> object:
63 """Import and initialize the selected Backend Runtime Adapter lazily."""
65 if isinstance(self.config, MariaDBConfig):
66 from snekql.mariadb.runtime import ( # noqa: PLC0415
67 initialize_runtime as initialize_mariadb_runtime,
68 )
70 return await initialize_mariadb_runtime(
71 self.config,
72 models,
73 schema_policy,
74 logger=logger,
75 )
76 try:
77 from snekql.sqlite.runtime import ( # noqa: PLC0415
78 initialize_runtime as initialize_sqlite_runtime,
79 )
80 except ModuleNotFoundError as error:
81 if error.name == "aiosqlite":
82 msg = "SQLite runtime requires the aiosqlite extra; install with snekql[aiosqlite]"
83 raise DatabaseRuntimeError(msg) from error
84 raise
86 return await initialize_sqlite_runtime(
87 self.config,
88 models,
89 schema_policy,
90 logger=logger,
91 )
94def _selection_from_config(config: RuntimeConfig) -> RuntimeSelection:
95 backend_family: BackendFamily = (
96 "mariadb" if isinstance(config, MariaDBConfig) else "sqlite"
97 )
98 return RuntimeSelection(backend_family=backend_family, config=config)
101def resolve_runtime_selection(
102 *,
103 backend: object | None,
104 database: Path | Literal[":memory:"] | None,
105 pool_size: PositiveInt,
106 acquire_timeout: NonNegativeFloat,
107) -> RuntimeSelection:
108 """Resolve public Database.initialize arguments to a Runtime Adapter choice."""
110 if backend is not None:
111 if not isinstance(backend, SQLiteConfig | MariaDBConfig):
112 msg = "unsupported database backend config"
113 raise DatabaseRuntimeError(msg)
114 if database is not None:
115 msg = "backend config cannot be combined with database"
116 raise DatabaseRuntimeError(msg)
117 return _selection_from_config(backend)
118 if database is None:
119 msg = "Database.initialize requires a backend config or database"
120 raise DatabaseRuntimeError(msg)
121 config = _build_legacy_sqlite_config(
122 acquire_timeout=acquire_timeout,
123 database=database,
124 pool_size=pool_size,
125 )
126 return RuntimeSelection(backend_family="sqlite", config=config)