Added in-memory database support to storage and switched tests to use it.
CI / Formatting (push) Failing after 32s
CI / Linting (push) Successful in 34s
CI / Tests (Python 3.12) (push) Successful in 39s
CI / Tests (Python 3.13) (push) Successful in 26s
CI / Tests (Python 3.14) (push) Successful in 16s
CI / Type Checking (push) Successful in 21s
CI / Spelling (push) Successful in 18s

This commit is contained in:
2026-04-04 18:48:27 -04:00
parent 15e9cd6131
commit 7957ceabfa
3 changed files with 20 additions and 18 deletions
+14 -5
View File
@@ -21,13 +21,13 @@ import contextlib
import contextvars import contextvars
import logging import logging
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from pathlib import Path
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
import aiosqlite import aiosqlite
if TYPE_CHECKING: if TYPE_CHECKING:
from collections.abc import AsyncIterator from collections.abc import AsyncIterator
from pathlib import Path
class StorageError(Exception): class StorageError(Exception):
@@ -41,6 +41,9 @@ class ModuleStorage:
created lazily on the first storage operation and reused for all created lazily on the first storage operation and reused for all
subsequent calls. WAL mode is enabled for crash resilience. subsequent calls. WAL mode is enabled for crash resilience.
Pass ``None`` as ``storage_dir`` to use an in-memory database
instead of a file, which is useful for testing.
Each storage operation (execute, fetch_one, etc.) auto-commits on Each storage operation (execute, fetch_one, etc.) auto-commits on
success and rolls back on failure. No transaction state is held success and rolls back on failure. No transaction state is held
between calls. between calls.
@@ -50,13 +53,18 @@ class ModuleStorage:
unit. unit.
""" """
def __init__(self, storage_dir: Path, module_name: str): def __init__(self, storage_dir: Path | None, module_name: str):
"""Initialize the storage API. """Initialize the storage API.
:param storage_dir: Directory where module databases are stored. :param storage_dir: Directory where module databases are stored.
Pass ``None`` to use an in-memory database (useful for tests).
:param module_name: Name of the module this storage belongs to. :param module_name: Name of the module this storage belongs to.
""" """
self._db_path = storage_dir / f"{module_name}.db" self._db_path: Path | str = (
storage_dir / f"{module_name}.db"
if storage_dir is not None
else ":memory:"
)
self._module_name = module_name self._module_name = module_name
self._conn: aiosqlite.Connection | None = None self._conn: aiosqlite.Connection | None = None
self._closed = False self._closed = False
@@ -242,7 +250,8 @@ class ModuleStorage:
raise StorageError("Storage is closed") raise StorageError("Storage is closed")
if self._conn is not None: if self._conn is not None:
return self._conn return self._conn
self._db_path.parent.mkdir(parents=True, exist_ok=True) if isinstance(self._db_path, Path):
self._db_path.parent.mkdir(parents=True, exist_ok=True)
conn = await aiosqlite.connect(self._db_path) conn = await aiosqlite.connect(self._db_path)
try: try:
conn.row_factory = aiosqlite.Row conn.row_factory = aiosqlite.Row
@@ -257,7 +266,7 @@ class ModuleStorage:
await asyncio.shield(conn.close()) await asyncio.shield(conn.close())
raise raise
self._conn = conn self._conn = conn
self._logger.info(f"Database opened at: {self._db_path.absolute()}") self._logger.info(f"Database opened at: {self._db_path}")
return conn return conn
@asynccontextmanager @asynccontextmanager
+3 -4
View File
@@ -25,7 +25,6 @@ from owlbot.api.storage import ModuleStorage
if TYPE_CHECKING: if TYPE_CHECKING:
from collections.abc import AsyncIterator from collections.abc import AsyncIterator
from pathlib import Path
def make_module_context( def make_module_context(
@@ -47,7 +46,7 @@ def make_module_context(
@pytest.fixture @pytest.fixture
async def storage(tmp_path: Path) -> AsyncIterator[ModuleStorage]: async def storage() -> AsyncIterator[ModuleStorage]:
"""Yield an open ModuleStorage backed by a temporary directory.""" """Yield an open in-memory ModuleStorage."""
async with ModuleStorage(tmp_path, "test_module") as s: async with ModuleStorage(None, "test_module") as s:
yield s yield s
+3 -9
View File
@@ -24,7 +24,6 @@ import pytest
if TYPE_CHECKING: if TYPE_CHECKING:
from collections.abc import Callable from collections.abc import Callable
from pathlib import Path
from owlbot.api.storage import ModuleStorage, StorageError from owlbot.api.storage import ModuleStorage, StorageError
@@ -54,12 +53,7 @@ class TestLazyConnections:
class TestPragmas: class TestPragmas:
"""WAL mode and foreign keys are enabled on new connections.""" """Foreign keys are enabled on new connections."""
async def test_wal_mode(self, storage: ModuleStorage) -> None:
"""journal_mode is set to WAL."""
value = await storage.fetch_value("PRAGMA journal_mode")
assert value == "wal"
async def test_foreign_keys_enabled(self, storage: ModuleStorage) -> None: async def test_foreign_keys_enabled(self, storage: ModuleStorage) -> None:
"""foreign_keys pragma is enabled.""" """foreign_keys pragma is enabled."""
@@ -369,9 +363,9 @@ class TestClose:
await storage._close() await storage._close()
await storage._close() await storage._close()
async def test_context_manager_closes_on_exit(self, tmp_path: Path) -> None: async def test_context_manager_closes_on_exit(self) -> None:
"""Exiting the async with block closes the connection.""" """Exiting the async with block closes the connection."""
async with ModuleStorage(tmp_path, "ctx_test") as s: async with ModuleStorage(None, "ctx_test") as s:
await s.execute("SELECT 1") await s.execute("SELECT 1")
assert s._conn is not None assert s._conn is not None
assert s._closed assert s._closed