diff --git a/README.rst b/README.rst index a770343..9cdf44a 100644 --- a/README.rst +++ b/README.rst @@ -44,6 +44,10 @@ non-`path-like object `_ as the value of ``path``, the cache is ignored. +``memoize_path()`` optionally takes an ``exclude_kwargs`` argument, which must +be a sequence of names of arguments of the decorated function that will be +ignored for caching purposes. + Caches are stored on-disk and thus persist between Python runs. To clear a given ``PersistentCache`` and erase its data store, call the ``clear()`` method. diff --git a/setup.cfg b/setup.cfg index 881a845..5f83e98 100644 --- a/setup.cfg +++ b/setup.cfg @@ -42,7 +42,7 @@ package_dir = python_requires = >=3.7 install_requires = appdirs == 1.* - joblib >= 0.17 + joblib ~= 1.1 [options.extras_require] benchmarks = diff --git a/src/fscacher/cache.py b/src/fscacher/cache.py index 3ec3a5d..3b321a3 100644 --- a/src/fscacher/cache.py +++ b/src/fscacher/cache.py @@ -1,5 +1,5 @@ from collections import deque, namedtuple -from functools import wraps +from functools import partial, wraps from hashlib import md5 from inspect import Parameter, signature import logging @@ -78,12 +78,16 @@ def clear(self): except Exception as exc: lgr.warning(f"Failed to clear out the cache directory: {exc}") - def memoize(self, f, ignore=None): + def memoize(self, f=None, *, exclude_kwargs=None): + if f is None: + return partial(self.memoize, exclude_kwargs=exclude_kwargs) if self._ignore_cache: return f - return self._memory.cache(f, ignore=ignore) + return self._memory.cache(f, ignore=exclude_kwargs) - def memoize_path(self, f): + def memoize_path(self, f=None, *, exclude_kwargs=None): + if f is None: + return partial(self.memoize_path, exclude_kwargs=exclude_kwargs) if self._ignore_cache: return f @@ -112,7 +116,11 @@ def fingerprinted(path, *args, **kwargs): # we need to ignore 'path' since we would like to dereference if symlink # but then expect joblib's caching work on both original and dereferenced # So we will add dereferenced path into fingerprint_kwarg - fingerprinted = self.memoize(fingerprinted, ignore=[path_arg]) + fingerprinted = self.memoize( + fingerprinted, + exclude_kwargs=[path_arg] + + (list(exclude_kwargs) if exclude_kwargs is not None else []), + ) @wraps(f) def fingerprinter(*args, **kwargs): diff --git a/src/fscacher/tests/test_cache.py b/src/fscacher/tests/test_cache.py index 4824fd1..6daba93 100644 --- a/src/fscacher/tests/test_cache.py +++ b/src/fscacher/tests/test_cache.py @@ -504,3 +504,45 @@ def strify(x): assert strify(bar) == str(tmp_path / "bar") assert calls == [path, bar] + + +def test_memoize_path_exclude_kwargs(cache, tmp_path): + calls = [] + + @cache.memoize_path(exclude_kwargs=["extra"]) + def memoread_extra(path, arg, kwarg=None, extra=None): + calls.append((path, arg, kwarg, extra)) + with open(path) as f: + return f.read() + + path = tmp_path / "file.dat" + path.write_text("content") + + time.sleep(cache._min_dtime * 1.1) + + assert memoread_extra(path, 1, extra="foo") == "content" + assert calls == [(path, 1, None, "foo")] + + assert memoread_extra(path, 1, extra="bar") == "content" + assert calls == [(path, 1, None, "foo")] + + assert memoread_extra(path, 1, kwarg="quux", extra="bar") == "content" + assert calls == [(path, 1, None, "foo"), (path, 1, "quux", "bar")] + + path.write_text("different") + + time.sleep(cache._min_dtime * 1.1) + + assert memoread_extra(path, 1, extra="foo") == "different" + assert calls == [ + (path, 1, None, "foo"), + (path, 1, "quux", "bar"), + (path, 1, None, "foo"), + ] + + assert memoread_extra(path, 1, extra="bar") == "different" + assert calls == [ + (path, 1, None, "foo"), + (path, 1, "quux", "bar"), + (path, 1, None, "foo"), + ] diff --git a/tox.ini b/tox.ini index 1e0d0f2..881f847 100644 --- a/tox.ini +++ b/tox.ini @@ -35,6 +35,7 @@ addopts = --cov=fscacher --no-cov-on-fail filterwarnings = error ignore:The distutils package is deprecated:DeprecationWarning:joblib + ignore:`formatargspec` is deprecated:DeprecationWarning:joblib norecursedirs = test/data [coverage:run]