diff --git a/README.md b/README.md index fd239a7..f65d8b6 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ An easy to use decorator for persistent memoization: like `functools.lrucache`, - Async functions supported. - Automatic cache invalidation when the decorated function arguments or code have been changed. - Time-to-live (TTL) support - automatically invalidate cache entries after a certain time. -- You can ignore changes in certain arguments of the decorated function. +- You can ignore changes in certain arguments of the decorated function or all of them. - Various serialization formats: JSON, YAML, pickle, Parquet, CSV etc. - Various storage backends: - local disk (_implemented_) or @@ -108,7 +108,10 @@ def get_data(): ... ``` ### Ignoring certain arguments -By specifying the arguments that should be ignored, you can still use the cache even in the values of these arguments have changed. **NOTE** that the decorated function should be called with ignored arguments specified as keyword arguments. +By specifying the arguments that should be ignored, you can still use the cache even in the values of these arguments have changed. + +**NOTE** that the decorated function should be called with ignored arguments specified as keyword arguments. + ```python @cache.cache(ignore=["ignore_this"]) def get_data(key, ignore_this): @@ -123,7 +126,7 @@ print(get_data("abc", ignore_this="ignore_1")) # the function has been called print(get_data("abc", ignore_this="ignore_2")) # abc ``` - +You can use `ignore_all` to ignore all arguments. ### Changing the default serialization format and storage backend ```python # set up serialization format and storage backend @@ -223,6 +226,8 @@ The cache will be invalidated if the function code, its argument values or the c ##### Arguments - `ignore (Iterable[str])`: keyword arguments of the decorated function that will not be used in making the cache key. In other words, changes in these arguments will not invalidate the cache. Defaults to `None`. +- `ignore_all (bool)`: if `True`, all arguments will be ignored. Defaults to `False`. + - `serializer (perscache.serializers.Serializer)`: Overrides the default `Cache()` serializer. Defaults to `None`. - `storage (perscache.storage.Storage)`: Overrides the default `Cache()` storage. Defaults to `None`. diff --git a/perscache/cache.py b/perscache/cache.py index 0aea40b..4c0d571 100644 --- a/perscache/cache.py +++ b/perscache/cache.py @@ -48,6 +48,7 @@ def get_key( kwargs: dict, serializer: Serializer, ignore: Iterable[str], + ignore_all: bool, ) -> str: """Get a cache key.""" @@ -55,6 +56,9 @@ def get_key( if ignore is not None: kwargs = {k: v for k, v in kwargs.items() if k not in ignore} + if ignore_all: + args, kwargs = None, None + return hash_it(inspect.getsource(fn), type(serializer), args, kwargs) def get_filename(self, fn: callable, key: str, serializer: Serializer) -> str: @@ -63,6 +67,7 @@ def get_filename(self, fn: callable, key: str, serializer: Serializer) -> str: def cache( self, ignore: Iterable[str] = None, + ignore_all: bool = False, serializer: Serializer = None, storage: Storage = None, ttl: dt.timedelta = None, @@ -77,7 +82,7 @@ def decorator(fn): @functools.wraps(fn) def non_async_wrapper(*args, **kwargs): - key = self.get_key(fn, args, kwargs, ser, ignore) + key = self.get_key(fn, args, kwargs, ser, ignore, ignore_all) key = self.get_filename(fn, key, ser) try: deadline = dt.datetime.now(dt.timezone.utc) - ttl if ttl else None @@ -89,7 +94,7 @@ def non_async_wrapper(*args, **kwargs): @functools.wraps(fn) async def async_wrapper(*args, **kwargs): - key = self.get_key(fn, args, kwargs, ser, ignore) + key = self.get_key(fn, args, kwargs, ser, ignore, ignore_all) key = self.get_filename(fn, key, ser) try: deadline = dt.datetime.now(dt.timezone.utc) - ttl if ttl else None diff --git a/tests/test_main.py b/tests/test_main.py index a323b23..6fd4665 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -72,14 +72,14 @@ def test_body_change(cache: Cache): def get_data(key): return key - hash1 = cache.get_key(get_data, None, None, None, None) + hash1 = cache.get_key(get_data, None, None, None, None, None) @cache.cache() def get_data(key): print("This function has been changed...") return key - hash2 = cache.get_key(get_data, None, None, None, None) + hash2 = cache.get_key(get_data, None, None, None, None, None) assert hash1 != hash2 @@ -127,6 +127,23 @@ def get_data(key, ignore_this): assert counter == 1 +def test_ignore_all(cache): + + counter = 0 + + @cache.cache(ignore_all=True) + def get_data(key1, key2): + nonlocal counter + counter += 1 + return key1, key2 + + get_data("abc", "def") + assert counter == 1 + + get_data("def", "ghi") + assert counter == 1 + + def test_no_cache(): counter = 0 @@ -186,5 +203,7 @@ def test_hash(): for data1, data2 in data(): assert data1 != data2 assert cache.get_key( - lambda: None, (data1,), None, CloudPickleSerializer(), None - ) != cache.get_key(lambda: None, (data2,), None, CloudPickleSerializer(), None) + lambda: None, (data1,), None, CloudPickleSerializer(), None, None + ) != cache.get_key( + lambda: None, (data2,), None, CloudPickleSerializer(), None, None + )