Skip to main content

manager: handle many forms of path in notify_path_change

ID
3e11553
date
2026-11-06 23:02:36+00:00
author
Alex Chan <alexc@tailscale.com>
parent
b919468
message
manager: handle many forms of path in notify_path_change

Allow passing paths in a singleton, list, tuple, or set, and allow each
path to be a str or a Path. This makes the types less "pure" but is more
convenient for callers.
changed files
2 files, 27 additions, 6 deletions

Changed files

src/funkycache/manager.py (11579 → 11691)

diff --git a/src/funkycache/manager.py b/src/funkycache/manager.py
index d7c7cfc..aedbe63 100644
--- a/src/funkycache/manager.py
+++ b/src/funkycache/manager.py
@@ -5,7 +5,7 @@ Ensure each function has an independent cache.
 """
 
 import atexit
-from collections.abc import Callable
+from collections.abc import Callable, Sequence
 from dataclasses import dataclass
 from functools import wraps
 import inspect
@@ -232,12 +232,15 @@ class FunkyCache:
             case _:
                 pass
 
-    def notify_path_change(self, changed_paths: Path | list[Path]) -> None:
+    def notify_path_change(
+        self, changed_paths: Path | str | Sequence[Path | str]
+    ) -> None:
         """
         Invalidate cache that depend on this path and/or paths.
         """
-        if not isinstance(changed_paths, list):
+        if isinstance(changed_paths, (Path, str)):
             changed_paths = [changed_paths]
+        changed_paths: list[Path] = [Path(p) for p in changed_paths]
 
         for name, known_cache in self._known_caches.items():
             # If this cache isn't affected by any of the paths that

tests/test_manager.py (19451 → 20246)

diff --git a/tests/test_manager.py b/tests/test_manager.py
index 662674f..6b3b801 100644
--- a/tests/test_manager.py
+++ b/tests/test_manager.py
@@ -3,7 +3,7 @@ Tests for the FunkyCache cache manager.
 """
 
 from abc import ABC, abstractmethod
-from collections.abc import Iterator, Mapping
+from collections.abc import Callable, Iterator, Mapping, Sequence
 from collections import Counter
 import inspect
 from pathlib import Path
@@ -116,8 +116,26 @@ class FunkyCacheTestSuite(ABC):
             def double(n: int) -> int:  # pragma: no cover
                 return n * 2
 
+    # path_modifier captures all the semantically equivalent ways
+    # to pass a single path to notify_path_change.
+    @pytest.mark.parametrize(
+        "path_modifier",
+        [
+            pytest.param(lambda p: p, id="single-path"),
+            pytest.param(lambda p: str(p), id="single-str"),
+            pytest.param(lambda p: [p], id="list-path"),
+            pytest.param(lambda p: [str(p)], id="list-str"),
+            pytest.param(lambda p: (p,), id="tuple-path"),
+            pytest.param(lambda p: (str(p),), id="tuple-str"),
+            pytest.param(lambda p: {p}, id="s-path"),
+            pytest.param(lambda p: {str(p)}, id="s-str"),
+        ],
+    )
     def test_cache_invalidated_on_path_change(
-        self, cache: FunkyCache, tmp_path: Path
+        self,
+        cache: FunkyCache,
+        tmp_path: Path,
+        path_modifier: Callable[[Path], Path | str | Sequence[Path | str]],
     ) -> None:
         """
         If a function depends on a path and the path changes, the cache
@@ -144,7 +162,7 @@ class FunkyCacheTestSuite(ABC):
 
         # Change the underlying file
         p.write_text("bonjour monde")
-        cache.notify_path_change([p])
+        cache.notify_path_change(path_modifier(p))
 
         # The third call is a cache miss; goes to the real function
         assert initial("apple") == "A"