Coverage for snekql/sqlite/runtime.py: 98%
81 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"""SQLite adapter for the backend-neutral query runtime."""
3from __future__ import annotations
5from collections.abc import Sequence
6from typing import Any, Literal, cast
8from aiosqlite import Connection, Cursor
10from snekql._pool import (
11 SQLiteConnectionPool,
12 close_sqlite_connection,
13 normalize_sqlite_database,
14 open_sqlite_connection,
15)
16from snekql.errors import DatabaseRuntimeError
17from snekql.model import Table
18from snekql.query import AnySelectQuery, compile_select_sql, compile_write_sql
19from snekql.query import materialize_select_row as materialize_sqlite_select_row
20from snekql.schema import (
21 initialize_sqlite_schema,
22 validate_schema_models,
23 validate_schema_policy,
24)
25from snekql.sqlite.config import Config
26from snekql.storage import SchemaPolicy
27from snekql.structured_logging import ResolvedStructuredLogger
28from snekql.validation import NonNegativeFloat
31class SQLiteCursorAdapter:
32 """Runtime cursor adapter backed by an aiosqlite cursor."""
34 def __init__(self, cursor: Cursor) -> None:
35 self.cursor: Cursor = cursor
37 async def fetchone(self) -> Sequence[object] | None:
38 row = await self.cursor.fetchone()
39 if row is None:
40 return None
41 return cast("Sequence[object]", row)
43 async def fetchall(self) -> Sequence[Sequence[object]]:
44 rows = await self.cursor.fetchall()
45 return [cast("Sequence[object]", row) for row in rows]
47 async def close(self) -> None:
48 await self.cursor.close()
51class SQLiteConnectionAdapter:
52 """Runtime connection adapter backed by an aiosqlite connection."""
54 def __init__(self, connection: Connection) -> None:
55 self.connection: Connection = connection
57 async def begin(self) -> None:
58 await self._execute_control_sql("BEGIN")
60 async def commit(self) -> None:
61 await self._execute_control_sql("COMMIT")
63 async def rollback(self) -> None:
64 await self._execute_control_sql("ROLLBACK")
66 async def execute(
67 self,
68 sql: str,
69 params: tuple[object, ...],
70 ) -> SQLiteCursorAdapter:
71 cursor = await self.connection.execute(sql, params)
72 return SQLiteCursorAdapter(cursor)
74 async def _execute_control_sql(self, sql: str) -> None:
75 cursor = await self.connection.execute(sql, ())
76 try:
77 return
78 finally:
79 await cursor.close()
82class SQLiteRuntime:
83 """SQLite adapter satisfying the backend-neutral runtime seam."""
85 backend_family: Literal["sqlite"] = "sqlite"
87 def __init__(
88 self,
89 *,
90 logger: ResolvedStructuredLogger,
91 acquire_timeout: NonNegativeFloat,
92 connection_pool: SQLiteConnectionPool,
93 ) -> None:
94 self.acquire_timeout: NonNegativeFloat = acquire_timeout
95 self.connection_pool: SQLiteConnectionPool = connection_pool
96 self.logger: ResolvedStructuredLogger = logger
98 async def acquire(
99 self,
100 acquisition_timeout: NonNegativeFloat,
101 ) -> SQLiteConnectionAdapter:
102 connection = await self.connection_pool.acquire(acquisition_timeout)
103 return SQLiteConnectionAdapter(connection)
105 async def release(self, connection: object) -> None:
106 if not isinstance(connection, SQLiteConnectionAdapter):
107 msg = "SQLite runtime cannot release a foreign connection"
108 raise DatabaseRuntimeError(msg)
109 await self.connection_pool.release(connection.connection)
111 async def close(self, close_timeout: NonNegativeFloat) -> None:
112 await self.connection_pool.close(close_timeout)
114 def check_accepting_work(self) -> None:
115 self.connection_pool.check_accepting_work()
117 def compile_select_sql(
118 self,
119 query: AnySelectQuery,
120 ) -> tuple[str, tuple[object, ...]]:
121 return compile_select_sql(query)
123 def compile_write_sql(self, query: object) -> tuple[str, tuple[object, ...]]:
124 return compile_write_sql(query)
126 def materialize_select_row(
127 self,
128 query: AnySelectQuery,
129 row: Sequence[object],
130 ) -> object:
131 return materialize_sqlite_select_row(query, row)
134async def initialize_runtime(
135 config: Config,
136 models: Sequence[type[Table[Any]]],
137 schema_policy: SchemaPolicy,
138 *,
139 logger: ResolvedStructuredLogger,
140) -> SQLiteRuntime:
141 """Initialize SQLite connectivity, schema startup, and pool lifecycle."""
143 validate_schema_policy(schema_policy)
144 validate_schema_models(models)
145 database_path = normalize_sqlite_database(config.database)
146 logger.debug("sqlite connection opening", database_path=database_path)
147 connection = await open_sqlite_connection(database_path)
148 try:
149 await initialize_sqlite_schema(
150 connection,
151 models,
152 schema_policy,
153 logger=logger,
154 )
155 except Exception:
156 await close_sqlite_connection(connection)
157 raise
158 return SQLiteRuntime(
159 logger=logger,
160 acquire_timeout=config.acquire_timeout,
161 connection_pool=SQLiteConnectionPool(
162 logger=logger,
163 database_path=database_path,
164 initial_connection=connection,
165 pool_size=config.pool_size,
166 ),
167 )
170__all__ = [
171 "SQLiteConnectionAdapter",
172 "SQLiteCursorAdapter",
173 "SQLiteRuntime",
174 "initialize_runtime",
175]