manager: disambiguate instances of storage
- ID
9393430- date
2026-11-06 08:37:49+00:00- author
Alex Chan <alex@alexwlchan.net>- parent
e097186- message
manager: disambiguate instances of storage- changed files
2 files, 53 additions, 1 deletion
Changed files
src/funkycache/manager.py (3379 → 4214)
diff --git a/src/funkycache/manager.py b/src/funkycache/manager.py
index e1740f2..70e375b 100644
--- a/src/funkycache/manager.py
+++ b/src/funkycache/manager.py
@@ -47,6 +47,7 @@ class FunkyCache:
_storage: StorageType
_root: Path
+ _known_caches: dict[str, CacheStorage]
@overload
def __init__(self, *, storage: Literal["memory"] = "memory"): ...
@@ -67,6 +68,7 @@ class FunkyCache:
self._storage = storage
self._root = Path(root)
+ self._known_caches = {}
def register[**P, R](self, func: F) -> F:
"""
@@ -74,7 +76,11 @@ class FunkyCache:
"""
sig = inspect.signature(func)
+ func_name = self._canonical_spec(func)
+ if func_name in self._known_caches:
+ raise TypeError(f"Duplicate cache for {func_name}")
storage = self._create_storage(func, data_type=sig.return_annotation)
+ self._known_caches[func_name] = storage
@wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
@@ -103,6 +109,20 @@ class FunkyCache:
return wrapper
+ @staticmethod
+ def _canonical_spec(func: F) -> str:
+ """
+ Return a canonical identifier for this function.
+
+ This should uniquely identify a function within a codebase,
+ and functions with the same name but in different files/classes
+ should get a different identifier.
+ """
+ module = getattr(func, "__module__", "__main__")
+ qualname = getattr(func, "__qualname__", func.__name__)
+
+ return f"{module}::{qualname}"
+
def _create_storage(self, func: F, data_type: R) -> CacheStorage[R]:
"""
Create a new instance of CacheStorage.
@@ -112,8 +132,9 @@ class FunkyCache:
return MemoryCacheStorage[R]()
case "sqlite":
self._root.mkdir(exist_ok=True, parents=True)
+ identifier = self._canonical_spec(func).replace(":", "-")
storage = SQLiteCacheStorage[R](
- self._root / f"{func.__name__}.db", data_type=data_type
+ self._root / f"{identifier}.db", data_type=data_type
)
atexit.register(lambda: storage.close())
return storage
tests/test_manager.py (3572 → 4510)
diff --git a/tests/test_manager.py b/tests/test_manager.py
index 9022e54..54eda11 100644
--- a/tests/test_manager.py
+++ b/tests/test_manager.py
@@ -68,6 +68,37 @@ class FunkyCacheTestSuite(ABC):
assert double(2) == 4
assert triple(2) == 6
+ def test_double_registering_is_error(self, cache: FunkyCache) -> None:
+ """
+ Registering a function with the same canonical identifier
+ is an error.
+ """
+
+ @cache.register
+ def double(n: int) -> int: # pragma: no cover
+ return n * 2
+
+ with pytest.raises(TypeError, match="Duplicate cache"):
+
+ @cache.register
+ def double(n: int) -> int: # pragma: no cover
+ return n * 2
+
+ def test_register_same_name_different_class(self, cache: FunkyCache) -> None:
+ """
+ Registering two functions with the same name is okay if one
+ of them is wrapped in a class.
+ """
+
+ @cache.register
+ def double(n: int) -> int: # pragma: no cover
+ return n * 2
+
+ class WrapperClass:
+ @cache.register
+ def double(n: int) -> int: # pragma: no cover
+ return n * 2
+
class TestMemoryFunkyCache(FunkyCacheTestSuite):
"""