Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand All @@ -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
Expand Down Expand Up @@ -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`.
Expand Down
9 changes: 7 additions & 2 deletions perscache/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,17 @@ def get_key(
kwargs: dict,
serializer: Serializer,
ignore: Iterable[str],
ignore_all: bool,
) -> str:
"""Get a cache key."""

# Remove ignored arguments from the arguments tuple and kwargs dict
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:
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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
Expand Down
27 changes: 23 additions & 4 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
)