from __future__ import annotations import sqlite3 from collections.abc import Iterator from contextlib import contextmanager from dataclasses import dataclass from pathlib import Path @dataclass(frozen=True, slots=True) class Database: path: Path timeout_seconds: float = 20.0 def connect(self) -> sqlite3.Connection: self.path.parent.mkdir(parents=True, exist_ok=True) connection = sqlite3.connect(self.path, timeout=self.timeout_seconds) connection.row_factory = sqlite3.Row connection.execute("PRAGMA foreign_keys = ON") connection.execute("PRAGMA journal_mode = WAL") connection.execute(f"PRAGMA busy_timeout = {int(self.timeout_seconds * 1000)}") return connection @contextmanager def read(self) -> Iterator[sqlite3.Connection]: connection = self.connect() try: yield connection finally: connection.close() @contextmanager def transaction(self) -> Iterator[sqlite3.Connection]: connection = self.connect() try: connection.execute("BEGIN IMMEDIATE") yield connection connection.commit() except Exception: connection.rollback() raise finally: connection.close()