storage: add the CacheStorage interface and a basic test suite
- ID
d51ee4e- date
2026-11-05 06:51:49+00:00- author
Alex Chan <alex@alexwlchan.net>- parent
1ff707a- message
storage: add the CacheStorage interface and a basic test suite This establishes the basic properties of a CacheStorage class and sets up a little test suite we can reuse to test all implementations.- changed files
5 files, 98 additions, 12 deletions
Changed files
pyproject.toml (1363 → 1403)
diff --git a/pyproject.toml b/pyproject.toml
index a182912..33ae166 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -51,6 +51,7 @@ filterwarnings = ["error"]
[tool.ruff.lint]
select = ["D", "E", "F"]
ignore = [
+ "D107", # undocumented-public-init
"D200", # unnecessary-multiline-docstring
"D203", # incorrect-blank-line-before-class
"D205", # missing-blank-line-after-summary
src/funkycache/__init__.py (107 → 219)
diff --git a/src/funkycache/__init__.py b/src/funkycache/__init__.py
index 1268d3d..f904e09 100644
--- a/src/funkycache/__init__.py
+++ b/src/funkycache/__init__.py
@@ -2,4 +2,7 @@
funkycache is a SQLite-backed function cache that tracks file dependencies.
"""
+from .storage import CannotOverwriteValueException
+
__version__ = "0.0.1"
+__all__ = ["CacheStorage", "CannotOverwriteValueException"]
src/funkycache/storage.py (0 → 1060)
diff --git a/src/funkycache/storage.py b/src/funkycache/storage.py
new file mode 100644
index 0000000..4d62d10
--- /dev/null
+++ b/src/funkycache/storage.py
@@ -0,0 +1,41 @@
+"""
+Backing storage for cache, which records the input/output of Python functions.
+"""
+
+from abc import ABC, abstractmethod
+from collections.abc import Mapping
+from typing import Any
+
+
+class CannotOverwriteValueException(ValueError):
+ """
+ Thrown when you try to overwrite an existing cache key with
+ a different value.
+ """
+
+ def __init__(self, key: str) -> None:
+ super().__init__(f"Key already exists: {key!r}")
+
+
+class CacheStorage[V](ABC):
+ """
+ The interface for the backing storage of a cache which records
+ the input/output of a single Python function.
+ """
+
+ @abstractmethod
+ def set(self, key: Mapping[str, Any], value: V) -> None:
+ """
+ Save a key-value pair in the cache.
+
+ Throws an exception if you overwrite an existing key with
+ a different value.
+ """
+
+ @abstractmethod
+ def get(self, key: Mapping[str, Any]) -> V:
+ """
+ Look up the value of a key in the cache.
+
+ Throws a KeyError if you look up a key which isn't in the cache.
+ """
tests/test_funkycache.py (204 → 0)
diff --git a/tests/test_funkycache.py b/tests/test_funkycache.py
deleted file mode 100644
index 10cc76d..0000000
--- a/tests/test_funkycache.py
+++ /dev/null
@@ -1,12 +0,0 @@
-"""
-Tests for funkycache.
-"""
-
-from funkycache import __version__
-
-
-def test_version() -> None:
- """
- Test the version as a way to exercise the test suite.
- """
- assert __version__ == "0.0.1"
tests/test_storage.py (0 → 1405)
diff --git a/tests/test_storage.py b/tests/test_storage.py
new file mode 100644
index 0000000..e1fa30e
--- /dev/null
+++ b/tests/test_storage.py
@@ -0,0 +1,53 @@
+"""
+Tests for backend storage implementations.
+"""
+
+from abc import ABC, abstractmethod
+from collections.abc import Iterator
+
+import pytest
+
+from funkycache.storage import CacheStorage, CannotOverwriteValueException
+
+
+class CacheStorageTestSuite(ABC):
+ """
+ A common test suite for all implementations of CacheStorage.
+
+ This should be subclassed as a `test*` class, where you write fixtures
+ and tests that are specific to a single implementation.
+ """
+
+ @abstractmethod
+ @pytest.fixture
+ def storage(self) -> Iterator[CacheStorage[int]]:
+ """
+ Return an empty cache storage backend that holds integer values.
+ """
+
+ def test_cache(self, storage: CacheStorage[int]) -> None:
+ """
+ Test the basic properties of a cache.
+ """
+ key = {"n": "10"}
+
+ fib10 = 55
+ fib20 = 6765
+
+ # Test empty storage
+ with pytest.raises(KeyError):
+ storage.get(key)
+
+ # Store and retrieve a value
+ storage.set(key, value=fib10)
+ assert storage.get(key) == fib10
+
+ # Overwriting an existing value is an error.
+ with pytest.raises(CannotOverwriteValueException):
+ storage.set(key, value=fib20)
+
+ assert storage.get(key) == fib10
+
+ # Writing a key with the same value as the already-stored
+ # value is allowed.
+ storage.set(key, value=fib10)