Skip to content

Commit cd37f5d

Browse files
Add docstrings and mark internal methods
1 parent 17fa75d commit cd37f5d

File tree

3 files changed

+70
-55
lines changed

3 files changed

+70
-55
lines changed

perscache/cache.py

Lines changed: 65 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
"""An easy to use decorator for persistent memoization.
2+
3+
Like `functools.lrucache`, but results can be saved in any format to any storage.
4+
"""
5+
16
import datetime as dt
27
import functools
38
import hashlib
@@ -11,7 +16,7 @@
1116

1217

1318
def hash_it(*data) -> str:
14-
"""Hashes all the data passed to it as args."""
19+
"""Pickles and hashes all the data passed to it as args."""
1520
result = hashlib.md5()
1621

1722
for datum in data:
@@ -21,44 +26,23 @@ def hash_it(*data) -> str:
2126

2227

2328
def is_async(fn):
29+
"""Checks if the function is async."""
2430
return inspect.iscoroutinefunction(fn) and not inspect.isgeneratorfunction(fn)
2531

2632

2733
class Cache:
28-
def __init__(self, serializer: Serializer = None, storage: Storage = None):
29-
self.serializer = serializer or CloudPickleSerializer()
30-
self.storage = storage or LocalFileStorage()
31-
32-
@staticmethod
33-
def get(
34-
key: str, serializer: Serializer, storage: Storage, deadline: dt.datetime
35-
) -> Any:
36-
data = storage.read(key, deadline)
37-
return serializer.loads(data)
38-
39-
@staticmethod
40-
def set(key: str, value: Any, serializer: Serializer, storage: Storage) -> None:
41-
data = serializer.dumps(value)
42-
storage.write(key, data)
34+
"""A cache that can be used to memoize functions."""
4335

44-
@staticmethod
45-
def get_key(
46-
fn: callable,
47-
args: tuple,
48-
kwargs: dict,
49-
serializer: Serializer,
50-
ignore: Iterable[str],
51-
) -> str:
52-
"""Get a cache key."""
53-
54-
# Remove ignored arguments from the arguments tuple and kwargs dict
55-
if ignore is not None:
56-
kwargs = {k: v for k, v in kwargs.items() if k not in ignore}
36+
def __init__(self, serializer: Serializer = None, storage: Storage = None):
37+
"""Initialize the cache.
5738
58-
return hash_it(inspect.getsource(fn), type(serializer), args, kwargs)
39+
Args:
40+
serializer: The serializer to use. If not specified, CloudPickleSerializer is used.
41+
storage: The storage to use. If not specified, LocalFileStorage is used.
42+
"""
5943

60-
def get_filename(self, fn: callable, key: str, serializer: Serializer) -> str:
61-
return f"{fn.__name__}-{key}.{serializer.extension}"
44+
self.serializer = serializer or CloudPickleSerializer()
45+
self.storage = storage or LocalFileStorage()
6246

6347
def cache(
6448
self,
@@ -86,37 +70,68 @@ def cache(
8670
Defaults to None.
8771
"""
8872

89-
def decorator(fn):
73+
def _decorator(fn):
9074
ser = serializer or self.serializer
9175
stor = storage or self.storage
9276

9377
@functools.wraps(fn)
94-
def non_async_wrapper(*args, **kwargs):
95-
key = self.get_key(fn, args, kwargs, ser, ignore)
96-
key = self.get_filename(fn, key, ser)
78+
def _non_async_wrapper(*args, **kwargs):
79+
key = self._get_key(fn, args, kwargs, ser, ignore)
80+
key = self._get_filename(fn, key, ser)
9781
try:
9882
deadline = dt.datetime.now(dt.timezone.utc) - ttl if ttl else None
99-
return self.get(key, ser, stor, deadline)
83+
return self._get(key, ser, stor, deadline)
10084
except (FileNotFoundError, CacheExpired):
10185
value = fn(*args, **kwargs)
102-
self.set(key, value, ser, stor)
86+
self._set(key, value, ser, stor)
10387
return value
10488

10589
@functools.wraps(fn)
106-
async def async_wrapper(*args, **kwargs):
107-
key = self.get_key(fn, args, kwargs, ser, ignore)
108-
key = self.get_filename(fn, key, ser)
90+
async def _async_wrapper(*args, **kwargs):
91+
key = self._get_key(fn, args, kwargs, ser, ignore)
92+
key = self._get_filename(fn, key, ser)
10993
try:
11094
deadline = dt.datetime.now(dt.timezone.utc) - ttl if ttl else None
111-
return self.get(key, ser, stor, deadline)
95+
return self._get(key, ser, stor, deadline)
11296
except (FileNotFoundError, CacheExpired):
11397
value = await fn(*args, **kwargs)
114-
self.set(key, value, ser, stor)
98+
self._set(key, value, ser, stor)
11599
return value
116100

117-
return async_wrapper if is_async(fn) else non_async_wrapper
101+
return _async_wrapper if is_async(fn) else _non_async_wrapper
102+
103+
return _decorator
104+
105+
@staticmethod
106+
def _get(
107+
key: str, serializer: Serializer, storage: Storage, deadline: dt.datetime
108+
) -> Any:
109+
data = storage.read(key, deadline)
110+
return serializer.loads(data)
111+
112+
@staticmethod
113+
def _set(key: str, value: Any, serializer: Serializer, storage: Storage) -> None:
114+
data = serializer.dumps(value)
115+
storage.write(key, data)
116+
117+
@staticmethod
118+
def _get_key(
119+
fn: callable,
120+
args: tuple,
121+
kwargs: dict,
122+
serializer: Serializer,
123+
ignore: Iterable[str],
124+
) -> str:
125+
"""Get a cache key."""
118126

119-
return decorator
127+
# Remove ignored arguments from the arguments tuple and kwargs dict
128+
if ignore is not None:
129+
kwargs = {k: v for k, v in kwargs.items() if k not in ignore}
130+
131+
return hash_it(inspect.getsource(fn), type(serializer), args, kwargs)
132+
133+
def _get_filename(self, fn: callable, key: str, serializer: Serializer) -> str:
134+
return f"{fn.__name__}-{key}.{serializer.extension}"
120135

121136

122137
class NoCache:
@@ -138,15 +153,15 @@ def cache(*decorator_args, **decorator_kwargs):
138153
return its result without any caching.
139154
"""
140155

141-
def decorator(fn):
156+
def _decorator(fn):
142157
@functools.wraps(fn)
143-
def non_async_wrapper(*args, **kwargs):
158+
def _non_async_wrapper(*args, **kwargs):
144159
return fn(*args, **kwargs)
145160

146161
@functools.wraps(fn)
147-
async def async_wrapper(*args, **kwargs):
162+
async def _async_wrapper(*args, **kwargs):
148163
return await fn(*args, **kwargs)
149164

150-
return async_wrapper if is_async(fn) else non_async_wrapper
165+
return _async_wrapper if is_async(fn) else _non_async_wrapper
151166

152-
return decorator
167+
return _decorator

tests/test_main.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,14 @@ def test_body_change(cache: Cache):
7272
def get_data(key):
7373
return key
7474

75-
hash1 = cache.get_key(get_data, None, None, None, None)
75+
hash1 = cache._get_key(get_data, None, None, None, None)
7676

7777
@cache.cache()
7878
def get_data(key):
7979
print("This function has been changed...")
8080
return key
8181

82-
hash2 = cache.get_key(get_data, None, None, None, None)
82+
hash2 = cache._get_key(get_data, None, None, None, None)
8383

8484
assert hash1 != hash2
8585

@@ -185,6 +185,6 @@ def test_hash():
185185
cache = Cache(serializer=CloudPickleSerializer())
186186
for data1, data2 in data():
187187
assert data1 != data2
188-
assert cache.get_key(
188+
assert cache._get_key(
189189
lambda: None, (data1,), None, CloudPickleSerializer(), None
190-
) != cache.get_key(lambda: None, (data2,), None, CloudPickleSerializer(), None)
190+
) != cache._get_key(lambda: None, (data2,), None, CloudPickleSerializer(), None)

tests/test_storage.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,6 @@ def test_no_path(tmp_path):
3535
assert not path.exists()
3636

3737
cache = Cache(storage=LocalFileStorage(path))
38-
cache.set("a", 1, cache.serializer, cache.storage)
38+
cache._set("a", 1, cache.serializer, cache.storage)
3939

4040
assert path.exists()

0 commit comments

Comments
 (0)