Skip to content

Commit 67bcf72

Browse files
skip cashing for exceptions (#233)
* adding test * _print * cleaning * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * with completed * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 731b66e commit 67bcf72

7 files changed

Lines changed: 85 additions & 76 deletions

File tree

src/cachier/config.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ class CacheEntry:
4646
value: Any
4747
time: datetime
4848
stale: bool
49-
being_calculated: bool
50-
condition: Optional[threading.Condition] = None
49+
_processing: bool
50+
_condition: Optional[threading.Condition] = None
51+
_completed: bool = False
5152

5253

5354
def _update_with_defaults(

src/cachier/core.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from collections import OrderedDict
1515
from concurrent.futures import ThreadPoolExecutor
1616
from functools import wraps
17-
from typing import Optional, Union
17+
from typing import Any, Optional, Union
1818
from warnings import warn
1919

2020
from .config import (
@@ -55,13 +55,11 @@ def _function_thread(core, key, func, args, kwds):
5555
print(f"Function call failed with the following exception:\n{exc}")
5656

5757

58-
def _calc_entry(core, key, func, args, kwds):
58+
def _calc_entry(core, key, func, args, kwds) -> Optional[Any]:
59+
core.mark_entry_being_calculated(key)
5960
try:
60-
core.mark_entry_being_calculated(key)
61-
# _get_executor().submit(core.mark_entry_being_calculated, key)
6261
func_res = func(*args, **kwds)
6362
core.set_entry(key, func_res)
64-
# _get_executor().submit(core.set_entry, key, func_res)
6563
return func_res
6664
finally:
6765
core.mark_entry_not_calculated(key)
@@ -242,9 +240,8 @@ def func_wrapper(*args, **kwds):
242240
func, _is_method=core.func_is_method, args=args, kwds=kwds
243241
)
244242

245-
_print = lambda x: None # noqa: E731
246-
if verbose:
247-
_print = print
243+
_print = print if verbose else lambda x: None
244+
248245
if ignore_cache or not _global_params.caching_enabled:
249246
return (
250247
func(args[0], **kwargs)
@@ -254,7 +251,9 @@ def func_wrapper(*args, **kwds):
254251
key, entry = core.get_entry((), kwargs)
255252
if overwrite_cache:
256253
return _calc_entry(core, key, func, args, kwds)
257-
if entry is None:
254+
if entry is None or (
255+
not entry._completed and not entry._processing
256+
):
258257
_print("No entry found. No current calc. Calling like a boss.")
259258
return _calc_entry(core, key, func, args, kwds)
260259
_print("Entry found.")
@@ -265,7 +264,7 @@ def func_wrapper(*args, **kwds):
265264
_print("And it is fresh!")
266265
return entry.value
267266
_print("But it is stale... :(")
268-
if entry.being_calculated:
267+
if entry._processing:
269268
if _next_time:
270269
_print("Returning stale.")
271270
return entry.value # return stale val
@@ -276,8 +275,8 @@ def func_wrapper(*args, **kwds):
276275
return _calc_entry(core, key, func, args, kwds)
277276
if _next_time:
278277
_print("Async calc and return stale")
278+
core.mark_entry_being_calculated(key)
279279
try:
280-
core.mark_entry_being_calculated(key)
281280
_get_executor().submit(
282281
_function_thread, core, key, func, args, kwds
283282
)
@@ -286,7 +285,7 @@ def func_wrapper(*args, **kwds):
286285
return entry.value
287286
_print("Calling decorated function and waiting")
288287
return _calc_entry(core, key, func, args, kwds)
289-
if entry.being_calculated:
288+
if entry._processing:
290289
_print("No value but being calculated. Waiting.")
291290
try:
292291
return core.wait_on_entry_calc(key)

src/cachier/cores/memory.py

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26,61 +26,65 @@ def get_entry_by_key(
2626
return key, self.cache.get(self._hash_func_key(key), None)
2727

2828
def set_entry(self, key: str, func_res: Any) -> None:
29+
hash_key = self._hash_func_key(key)
2930
with self.lock:
3031
try:
3132
# we need to retain the existing condition so that
3233
# mark_entry_not_calculated can notify all possibly-waiting
3334
# threads about it
34-
cond = self.cache[self._hash_func_key(key)].condition
35+
cond = self.cache[hash_key]._condition
3536
except KeyError: # pragma: no cover
3637
cond = None
37-
self.cache[self._hash_func_key(key)] = CacheEntry(
38+
self.cache[hash_key] = CacheEntry(
3839
value=func_res,
3940
time=datetime.now(),
4041
stale=False,
41-
being_calculated=False,
42-
condition=cond,
42+
_processing=False,
43+
_condition=cond,
44+
_completed=True,
4345
)
4446

4547
def mark_entry_being_calculated(self, key: str) -> None:
4648
with self.lock:
4749
condition = threading.Condition()
50+
hash_key = self._hash_func_key(key)
51+
if hash_key in self.cache:
52+
self.cache[hash_key]._processing = True
53+
self.cache[hash_key]._condition = condition
4854
# condition.acquire()
49-
try:
50-
self.cache[self._hash_func_key(key)].being_calculated = True
51-
self.cache[self._hash_func_key(key)].condition = condition
52-
except KeyError:
53-
self.cache[self._hash_func_key(key)] = CacheEntry(
55+
else:
56+
self.cache[hash_key] = CacheEntry(
5457
value=None,
5558
time=datetime.now(),
5659
stale=False,
57-
being_calculated=True,
58-
condition=condition,
60+
_processing=True,
61+
_condition=condition,
5962
)
6063

6164
def mark_entry_not_calculated(self, key: str) -> None:
65+
hash_key = self._hash_func_key(key)
6266
with self.lock:
63-
try:
64-
entry = self.cache[self._hash_func_key(key)]
65-
except KeyError: # pragma: no cover
67+
if hash_key not in self.cache:
6668
return # that's ok, we don't need an entry in that case
67-
entry.being_calculated = False
68-
cond = entry.condition
69+
entry = self.cache[hash_key]
70+
entry._processing = False
71+
cond = entry._condition
6972
if cond:
7073
cond.acquire()
7174
cond.notify_all()
7275
cond.release()
73-
entry.condition = None
76+
entry._condition = None
7477

7578
def wait_on_entry_calc(self, key: str) -> Any:
79+
hash_key = self._hash_func_key(key)
7680
with self.lock: # pragma: no cover
77-
entry = self.cache[self._hash_func_key(key)]
78-
if not entry.being_calculated:
81+
entry = self.cache[hash_key]
82+
if not entry._processing:
7983
return entry.value
80-
entry.condition.acquire()
81-
entry.condition.wait()
82-
entry.condition.release()
83-
return self.cache[self._hash_func_key(key)].value
84+
entry._condition.acquire()
85+
entry._condition.wait()
86+
entry._condition.release()
87+
return self.cache[hash_key].value
8488

8589
def clear_cache(self) -> None:
8690
with self.lock:
@@ -89,5 +93,5 @@ def clear_cache(self) -> None:
8993
def clear_being_calculated(self) -> None:
9094
with self.lock:
9195
for entry in self.cache.values():
92-
entry.being_calculated = False
93-
entry.condition = None
96+
entry._processing = False
97+
entry._condition = None

src/cachier/cores/mongo.py

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -73,20 +73,14 @@ def get_entry_by_key(self, key: str) -> Tuple[str, Optional[CacheEntry]]:
7373
)
7474
if not res:
7575
return key, None
76-
try:
77-
entry = CacheEntry(
78-
value=pickle.loads(res["value"]), # noqa: S301
79-
time=res.get("time", None),
80-
stale=res.get("stale", False),
81-
being_calculated=res.get("being_calculated", False),
82-
)
83-
except KeyError:
84-
entry = CacheEntry(
85-
value=None,
86-
time=res.get("time", None),
87-
stale=res.get("stale", False),
88-
being_calculated=res.get("being_calculated", False),
89-
)
76+
val = pickle.loads(res["value"]) if "value" in res else None # noqa: S301
77+
entry = CacheEntry(
78+
value=val,
79+
time=res.get("time", None),
80+
stale=res.get("stale", False),
81+
_processing=res.get("processing", False),
82+
_completed=res.get("completed", False),
83+
)
9084
return key, entry
9185

9286
def set_entry(self, key: str, func_res: Any) -> None:
@@ -100,7 +94,8 @@ def set_entry(self, key: str, func_res: Any) -> None:
10094
"value": Binary(thebytes),
10195
"time": datetime.now(),
10296
"stale": False,
103-
"being_calculated": False,
97+
"processing": False,
98+
"completed": True,
10499
}
105100
},
106101
upsert=True,
@@ -109,7 +104,7 @@ def set_entry(self, key: str, func_res: Any) -> None:
109104
def mark_entry_being_calculated(self, key: str) -> None:
110105
self.mongo_collection.update_one(
111106
filter={"func": self._func_str, "key": key},
112-
update={"$set": {"being_calculated": True}},
107+
update={"$set": {"processing": True}},
113108
upsert=True,
114109
)
115110

@@ -120,7 +115,7 @@ def mark_entry_not_calculated(self, key: str) -> None:
120115
"func": self._func_str,
121116
"key": key,
122117
},
123-
update={"$set": {"being_calculated": False}},
118+
update={"$set": {"processing": False}},
124119
upsert=False, # should not insert in this case
125120
)
126121

@@ -132,7 +127,7 @@ def wait_on_entry_calc(self, key: str) -> Any:
132127
key, entry = self.get_entry_by_key(key)
133128
if entry is None:
134129
raise RecalculationNeeded()
135-
if not entry.being_calculated:
130+
if not entry._processing:
136131
return entry.value
137132
self.check_calc_timeout(time_spent)
138133

@@ -143,7 +138,7 @@ def clear_being_calculated(self) -> None:
143138
self.mongo_collection.update_many(
144139
filter={
145140
"func": self._func_str,
146-
"being_calculated": True,
141+
"processing": True,
147142
},
148-
update={"$set": {"being_calculated": False}},
143+
update={"$set": {"processing": False}},
149144
)

src/cachier/cores/pickle.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,14 @@ def inject_observer(self, observer) -> None:
4646
self.observer = observer
4747

4848
def _check_calculation(self) -> None:
49-
# print('checking calc')
5049
entry = self.core.get_entry_by_key(self.key, True)[1]
51-
# print(self.key)
52-
# print(entry)
5350
try:
54-
if not entry.being_calculated:
51+
if not entry._processing:
5552
# print('stopping observer!')
5653
self.value = entry.value
5754
self.observer.stop()
5855
# else:
59-
# print('NOT stopping observer... :(')
56+
# print('NOT stopping observer... :(')
6057
except TypeError:
6158
self.value = None
6259
self.observer.stop()
@@ -169,7 +166,8 @@ def set_entry(self, key: str, func_res: Any) -> None:
169166
value=func_res,
170167
time=datetime.now(),
171168
stale=False,
172-
being_calculated=False,
169+
_processing=False,
170+
_completed=True,
173171
)
174172
if self.separate_files:
175173
self._save_cache(key_data, key)
@@ -186,14 +184,14 @@ def mark_entry_being_calculated_separate_files(self, key: str) -> None:
186184
value=None,
187185
time=datetime.now(),
188186
stale=False,
189-
being_calculated=True,
187+
_processing=True,
190188
),
191189
key=key,
192190
)
193191

194192
def mark_entry_not_calculated_separate_files(self, key: str) -> None:
195193
_, entry = self.get_entry_by_key(key)
196-
entry.being_calculated = False
194+
entry._processing = False
197195
self._save_cache(entry, key=key)
198196

199197
def mark_entry_being_calculated(self, key: str) -> None:
@@ -203,14 +201,14 @@ def mark_entry_being_calculated(self, key: str) -> None:
203201

204202
with self.lock:
205203
cache = self._get_cache()
206-
try:
207-
cache[key].being_calculated = True
208-
except KeyError:
204+
if key in cache:
205+
cache[key]._processing = True
206+
else:
209207
cache[key] = CacheEntry(
210208
value=None,
211209
time=datetime.now(),
212210
stale=False,
213-
being_calculated=True,
211+
_processing=True,
214212
)
215213
self._save_cache(cache)
216214

@@ -221,7 +219,7 @@ def mark_entry_not_calculated(self, key: str) -> None:
221219
cache = self._get_cache()
222220
# that's ok, we don't need an entry in that case
223221
if isinstance(cache, dict) and key in cache:
224-
cache[key].being_calculated = False
222+
cache[key]._processing = False
225223
self._save_cache(cache)
226224

227225
def wait_on_entry_calc(self, key: str) -> Any:
@@ -233,7 +231,7 @@ def wait_on_entry_calc(self, key: str) -> Any:
233231
self._reload_cache()
234232
entry = self._get_cache()[key]
235233
filename = self.cache_fname
236-
if not entry.being_calculated:
234+
if not entry._processing:
237235
return entry.value
238236
event_handler = _PickleCore.CacheChangeHandler(
239237
filename=filename, core=self, key=key
@@ -263,5 +261,5 @@ def clear_being_calculated(self) -> None:
263261
with self.lock:
264262
cache = self._get_cache()
265263
for key in cache:
266-
cache[key].being_calculated = False
264+
cache[key]._processing = False
267265
self._save_cache(cache)

tests/test_general.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,3 +492,15 @@ def fn_minus(a, b=2):
492492

493493
assert count_p == 1
494494
assert count_m == 1
495+
496+
497+
@pytest.mark.parametrize("backend", ["memory", "pickle"])
498+
def test_raise_exception(tmpdir, backend: str):
499+
@cachier.cachier(cache_dir=tmpdir, backend=backend, allow_none=True)
500+
def tmp_test(_):
501+
raise RuntimeError("always raise")
502+
503+
with pytest.raises(RuntimeError):
504+
tmp_test(123)
505+
with pytest.raises(RuntimeError):
506+
tmp_test(123)

tests/test_mongo_core.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ def _stalled_func():
275275
def test_stalled_mong_db_core(monkeypatch):
276276
def mock_get_entry(self, args, kwargs):
277277
return "key", CacheEntry(
278-
being_calculated=True, value=None, time=None, stale=None
278+
_processing=True, value=None, time=None, stale=None
279279
)
280280

281281
def mock_get_entry_by_key(self, key):
@@ -300,7 +300,7 @@ def mock_get_entry_2(self, args, kwargs):
300300
return "key", CacheEntry(
301301
value=1,
302302
time=datetime.datetime.now() - datetime.timedelta(seconds=10),
303-
being_calculated=True,
303+
_processing=True,
304304
stale=None,
305305
)
306306

0 commit comments

Comments
 (0)