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
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:
+14
-5
@@ -21,13 +21,13 @@ import contextlib
|
||||
import contextvars
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import aiosqlite
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import AsyncIterator
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class StorageError(Exception):
|
||||
@@ -41,6 +41,9 @@ class ModuleStorage:
|
||||
created lazily on the first storage operation and reused for all
|
||||
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
|
||||
success and rolls back on failure. No transaction state is held
|
||||
between calls.
|
||||
@@ -50,13 +53,18 @@ class ModuleStorage:
|
||||
unit.
|
||||
"""
|
||||
|
||||
def __init__(self, storage_dir: Path, module_name: str):
|
||||
def __init__(self, storage_dir: Path | None, module_name: str):
|
||||
"""Initialize the storage API.
|
||||
|
||||
: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.
|
||||
"""
|
||||
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._conn: aiosqlite.Connection | None = None
|
||||
self._closed = False
|
||||
@@ -242,7 +250,8 @@ class ModuleStorage:
|
||||
raise StorageError("Storage is closed")
|
||||
if self._conn is not None:
|
||||
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)
|
||||
try:
|
||||
conn.row_factory = aiosqlite.Row
|
||||
@@ -257,7 +266,7 @@ class ModuleStorage:
|
||||
await asyncio.shield(conn.close())
|
||||
raise
|
||||
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
|
||||
|
||||
@asynccontextmanager
|
||||
|
||||
+3
-4
@@ -25,7 +25,6 @@ from owlbot.api.storage import ModuleStorage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import AsyncIterator
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def make_module_context(
|
||||
@@ -47,7 +46,7 @@ def make_module_context(
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def storage(tmp_path: Path) -> AsyncIterator[ModuleStorage]:
|
||||
"""Yield an open ModuleStorage backed by a temporary directory."""
|
||||
async with ModuleStorage(tmp_path, "test_module") as s:
|
||||
async def storage() -> AsyncIterator[ModuleStorage]:
|
||||
"""Yield an open in-memory ModuleStorage."""
|
||||
async with ModuleStorage(None, "test_module") as s:
|
||||
yield s
|
||||
|
||||
@@ -24,7 +24,6 @@ import pytest
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
from owlbot.api.storage import ModuleStorage, StorageError
|
||||
|
||||
@@ -54,12 +53,7 @@ class TestLazyConnections:
|
||||
|
||||
|
||||
class TestPragmas:
|
||||
"""WAL mode and 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"
|
||||
"""Foreign keys are enabled on new connections."""
|
||||
|
||||
async def test_foreign_keys_enabled(self, storage: ModuleStorage) -> None:
|
||||
"""foreign_keys pragma is enabled."""
|
||||
@@ -369,9 +363,9 @@ class TestClose:
|
||||
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."""
|
||||
async with ModuleStorage(tmp_path, "ctx_test") as s:
|
||||
async with ModuleStorage(None, "ctx_test") as s:
|
||||
await s.execute("SELECT 1")
|
||||
assert s._conn is not None
|
||||
assert s._closed
|
||||
|
||||
Reference in New Issue
Block a user