Skip to main content

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):
     """