Skip to content

Commit 2a4d1c9

Browse files
fixing wrong results for runtime func (#196)
* debug wrong results for runtime func * more tests * tests * _ * fixing & tests * memory * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * rev --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 4ee3891 commit 2a4d1c9

3 files changed

Lines changed: 119 additions & 79 deletions

File tree

cachier/cores/memory.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from datetime import datetime
55

66
from .._types import HashFunc
7-
from .base import _BaseCore
7+
from .base import _BaseCore, _get_func_str
88

99

1010
class _MemoryCore(_BaseCore):
@@ -14,20 +14,23 @@ def __init__(self, hash_func: HashFunc, wait_for_calc_timeout: int):
1414
super().__init__(hash_func, wait_for_calc_timeout)
1515
self.cache = {}
1616

17+
def _hash_func_key(self, key):
18+
return f"{_get_func_str(self.func)}:{key}"
19+
1720
def get_entry_by_key(self, key, reload=False):
1821
with self.lock:
19-
return key, self.cache.get(key, None)
22+
return key, self.cache.get(self._hash_func_key(key), None)
2023

2124
def set_entry(self, key, func_res):
2225
with self.lock:
2326
try:
2427
# we need to retain the existing condition so that
2528
# mark_entry_not_calculated can notify all possibly-waiting
2629
# threads about it
27-
cond = self.cache[key]["condition"]
30+
cond = self.cache[self._hash_func_key(key)]["condition"]
2831
except KeyError: # pragma: no cover
2932
cond = None
30-
self.cache[key] = {
33+
self.cache[self._hash_func_key(key)] = {
3134
"value": func_res,
3235
"time": datetime.now(),
3336
"stale": False,
@@ -40,10 +43,10 @@ def mark_entry_being_calculated(self, key):
4043
condition = threading.Condition()
4144
# condition.acquire()
4245
try:
43-
self.cache[key]["being_calculated"] = True
44-
self.cache[key]["condition"] = condition
46+
self.cache[self._hash_func_key(key)]["being_calculated"] = True
47+
self.cache[self._hash_func_key(key)]["condition"] = condition
4548
except KeyError:
46-
self.cache[key] = {
49+
self.cache[self._hash_func_key(key)] = {
4750
"value": None,
4851
"time": datetime.now(),
4952
"stale": False,
@@ -54,7 +57,7 @@ def mark_entry_being_calculated(self, key):
5457
def mark_entry_not_calculated(self, key):
5558
with self.lock:
5659
try:
57-
entry = self.cache[key]
60+
entry = self.cache[self._hash_func_key(key)]
5861
except KeyError: # pragma: no cover
5962
return # that's ok, we don't need an entry in that case
6063
entry["being_calculated"] = False
@@ -67,13 +70,13 @@ def mark_entry_not_calculated(self, key):
6770

6871
def wait_on_entry_calc(self, key):
6972
with self.lock: # pragma: no cover
70-
entry = self.cache[key]
73+
entry = self.cache[self._hash_func_key(key)]
7174
if not entry["being_calculated"]:
7275
return entry["value"]
7376
entry["condition"].acquire()
7477
entry["condition"].wait()
7578
entry["condition"].release()
76-
return self.cache[key]["value"]
79+
return self.cache[self._hash_func_key(key)]["value"]
7780

7881
def clear_cache(self):
7982
with self.lock:

cachier/cores/pickle.py

Lines changed: 35 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -86,38 +86,27 @@ def __init__(
8686
self.separate_files = _update_with_defaults(
8787
separate_files, "separate_files"
8888
)
89-
self._cache_fname = None
90-
self._cache_fpath = None
9189

9290
@property
9391
def cache_fname(self) -> str:
94-
if self._cache_fname is None:
95-
fname = f".{self.func.__module__}.{self.func.__qualname__}"
96-
self._cache_fname = fname.replace("<", "_").replace(">", "_")
97-
return self._cache_fname
92+
fname = f".{self.func.__module__}.{self.func.__qualname__}"
93+
return fname.replace("<", "_").replace(">", "_")
9894

9995
@property
10096
def cache_fpath(self) -> str:
101-
if self._cache_fpath is None:
102-
os.makedirs(self.cache_dir, exist_ok=True)
103-
self._cache_fpath = os.path.abspath(
104-
os.path.join(
105-
os.path.realpath(self.cache_dir), self.cache_fname
106-
)
107-
)
108-
return self._cache_fpath
97+
os.makedirs(self.cache_dir, exist_ok=True)
98+
return os.path.abspath(
99+
os.path.join(os.path.realpath(self.cache_dir), self.cache_fname)
100+
)
109101

110102
def _reload_cache(self):
111103
with self.lock:
112104
try:
113105
with portalocker.Lock(
114106
self.cache_fpath, mode="rb"
115107
) as cache_file:
116-
try:
117-
self.cache = pickle.load(cache_file) # noqa: S301
118-
except EOFError:
119-
self.cache = {}
120-
except FileNotFoundError:
108+
self.cache = pickle.load(cache_file) # noqa: S301
109+
except (FileNotFoundError, EOFError):
121110
self.cache = {}
122111

123112
def _get_cache(self):
@@ -180,11 +169,12 @@ def set_entry(self, key, func_res):
180169
}
181170
if self.separate_files:
182171
self._save_cache(key_data, key)
183-
else:
184-
with self.lock:
185-
cache = self._get_cache()
186-
cache[key] = key_data
187-
self._save_cache(cache)
172+
return # pragma: no cover
173+
174+
with self.lock:
175+
cache = self._get_cache()
176+
cache[key] = key_data
177+
self._save_cache(cache)
188178

189179
def mark_entry_being_calculated_separate_files(self, key):
190180
self._save_cache(
@@ -205,19 +195,20 @@ def mark_entry_not_calculated_separate_files(self, key):
205195
def mark_entry_being_calculated(self, key):
206196
if self.separate_files:
207197
self.mark_entry_being_calculated_separate_files(key)
208-
else:
209-
with self.lock:
210-
cache = self._get_cache()
211-
try:
212-
cache[key]["being_calculated"] = True
213-
except KeyError:
214-
cache[key] = {
215-
"value": None,
216-
"time": datetime.now(),
217-
"stale": False,
218-
"being_calculated": True,
219-
}
220-
self._save_cache(cache)
198+
return # pragma: no cover
199+
200+
with self.lock:
201+
cache = self._get_cache()
202+
try:
203+
cache[key]["being_calculated"] = True
204+
except KeyError:
205+
cache[key] = {
206+
"value": None,
207+
"time": datetime.now(),
208+
"stale": False,
209+
"being_calculated": True,
210+
}
211+
self._save_cache(cache)
221212

222213
def mark_entry_not_calculated(self, key):
223214
if self.separate_files:
@@ -263,9 +254,10 @@ def clear_cache(self):
263254
def clear_being_calculated(self):
264255
if self.separate_files:
265256
self._clear_being_calculated_all_cache_files()
266-
else:
267-
with self.lock:
268-
cache = self._get_cache()
269-
for key in cache:
270-
cache[key]["being_calculated"] = False
271-
self._save_cache(cache)
257+
return # pragma: no cover
258+
259+
with self.lock:
260+
cache = self._get_cache()
261+
for key in cache:
262+
cache[key]["being_calculated"] = False
263+
self._save_cache(cache)

tests/test_general.py

Lines changed: 71 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -344,44 +344,89 @@ def dummy_func(a, b=2):
344344
assert count == 1
345345

346346

347-
def test_runtime_handling(tmpdir):
348-
count = 0
347+
@pytest.mark.parametrize("backend", ["memory", "pickle"])
348+
def test_diff_functions_same_args(tmpdir, backend: str):
349+
count_p = count_m = 0
350+
351+
@cachier.cachier(cache_dir=tmpdir, backend=backend)
352+
def fn_plus(a, b=2):
353+
nonlocal count_p
354+
count_p += 1
355+
return a + b
349356

350-
def dummy_func(a, b):
351-
nonlocal count
352-
count += 1
357+
@cachier.cachier(cache_dir=tmpdir, backend=backend)
358+
def fn_minus(a, b=2):
359+
nonlocal count_m
360+
count_m += 1
361+
return a - b
362+
363+
assert count_p == count_m == 0
364+
365+
for fn, expected in [(fn_plus, 3), (fn_minus, -1)]:
366+
assert fn(1) == expected
367+
assert fn(a=1, b=2) == expected
368+
assert count_p == 1
369+
assert count_m == 1
370+
371+
372+
@pytest.mark.parametrize("backend", ["memory", "pickle"])
373+
def test_runtime_handling(tmpdir, backend):
374+
count_p = count_m = 0
375+
376+
def fn_plus(a, b=2):
377+
nonlocal count_p
378+
count_p += 1
353379
return a + b
354380

355-
cachier_ = cachier.cachier(cache_dir=tmpdir)
356-
assert count == 0
357-
cachier_(dummy_func)(a=1, b=2)
358-
cachier_(dummy_func)(a=1, b=2)
359-
assert count == 1
381+
def fn_minus(a, b=2):
382+
nonlocal count_m
383+
count_m += 1
384+
return a - b
385+
386+
cachier_ = cachier.cachier(cache_dir=tmpdir, backend=backend)
387+
assert count_p == count_m == 0
388+
389+
for fn, expected in [(fn_plus, 3), (fn_minus, -1)]:
390+
assert cachier_(fn)(1, 2) == expected
391+
assert cachier_(fn)(a=1, b=2) == expected
392+
assert count_p == 1
393+
assert count_m == 1
394+
395+
for fn, expected in [(fn_plus, 5), (fn_minus, 1)]:
396+
assert cachier_(fn)(3, 2) == expected
397+
assert cachier_(fn)(a=3, b=2) == expected
398+
assert count_p == 2
399+
assert count_m == 2
360400

361401

362402
def test_partial_handling(tmpdir):
363-
count = 0
403+
count_p = count_m = 0
364404

365-
def dummy_func(a, b=2):
366-
nonlocal count
367-
count += 1
405+
def fn_plus(a, b=2):
406+
nonlocal count_p
407+
count_p += 1
368408
return a + b
369409

370-
cachier_ = cachier.cachier(cache_dir=tmpdir)
371-
assert count == 0
410+
def fn_minus(a, b=2):
411+
nonlocal count_m
412+
count_m += 1
413+
return a - b
372414

373-
dummy_ = functools.partial(dummy_func, 1)
374-
cachier_(dummy_)()
415+
cachier_ = cachier.cachier(cache_dir=tmpdir)
416+
assert count_p == count_m == 0
375417

376-
dummy_ = functools.partial(dummy_func, a=1)
377-
cachier_(dummy_)()
418+
for fn, expected in [(fn_plus, 3), (fn_minus, -1)]:
419+
dummy_ = functools.partial(fn, 1)
420+
assert cachier_(dummy_)() == expected
378421

379-
dummy_ = functools.partial(dummy_func, b=2)
380-
cachier_(dummy_)(1)
422+
dummy_ = functools.partial(fn, a=1)
423+
assert cachier_(dummy_)() == expected
381424

382-
assert count == 1
425+
dummy_ = functools.partial(fn, b=2)
426+
assert cachier_(dummy_)(1) == expected
383427

384-
cachier_(dummy_func)(1, 2)
385-
cachier_(dummy_func)(a=1, b=2)
428+
assert cachier_(fn)(1, 2) == expected
429+
assert cachier_(fn)(a=1, b=2) == expected
386430

387-
assert count == 1
431+
assert count_p == 1
432+
assert count_m == 1

0 commit comments

Comments
 (0)