Skip to content

Commit 731b66e

Browse files
Bordashaypal5pre-commit-ci[bot]
authored
convert dict entry to dataclass & typing (#239)
* convert `dict` entry to dataclass * linting * Dict * lint * if key not in cache * fixing * mock * mock * mock * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Remove unused TypedDict import from config.py --------- Co-authored-by: Shay Palachy-Affek <shaypal5@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 9dd89b0 commit 731b66e

9 files changed

Lines changed: 162 additions & 140 deletions

File tree

.github/workflows/ci-test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
5151
- name: Unit tests (local)
5252
if: matrix.backend == 'local'
53-
run: pytest -m "not mongo"
53+
run: pytest -m "not mongo" --cov=cachier --cov-report=term --cov-report=xml:cov.xml
5454

5555
- name: Setup docker (missing on MacOS)
5656
if: runner.os == 'macOS' && matrix.backend == 'db'
@@ -77,7 +77,7 @@ jobs:
7777
docker ps -a
7878
- name: Unit tests (DB)
7979
if: matrix.backend == 'db'
80-
run: pytest -m "mongo"
80+
run: pytest -m "mongo" --cov=cachier --cov-report=term --cov-report=xml:cov.xml
8181
- name: Speed eval
8282
run: python tests/speed_eval.py
8383

pyproject.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,6 @@ norecursedirs = [
145145
]
146146
addopts = [
147147
"--color=yes",
148-
"--cov=cachier",
149-
"--cov-report=term",
150-
"--cov-report=xml:cov.xml",
151148
"-r a",
152149
"-v",
153150
"-s",

src/cachier/config.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
import hashlib
33
import os
44
import pickle
5+
import threading
56
from collections.abc import Mapping
67
from dataclasses import dataclass, replace
7-
from typing import Optional, Union
8+
from typing import Any, Optional, Union
89

910
from ._types import Backend, HashFunc, Mongetter
1011

@@ -38,6 +39,17 @@ class Params:
3839
_global_params = Params()
3940

4041

42+
@dataclass
43+
class CacheEntry:
44+
"""Data class for cache entries."""
45+
46+
value: Any
47+
time: datetime
48+
stale: bool
49+
being_calculated: bool
50+
condition: Optional[threading.Condition] = None
51+
52+
4153
def _update_with_defaults(
4254
param, name: str, func_kwargs: Optional[dict] = None
4355
):

src/cachier/core.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -258,17 +258,17 @@ def func_wrapper(*args, **kwds):
258258
_print("No entry found. No current calc. Calling like a boss.")
259259
return _calc_entry(core, key, func, args, kwds)
260260
_print("Entry found.")
261-
if _allow_none or entry.get("value", None) is not None:
261+
if _allow_none or entry.value is not None:
262262
_print("Cached result found.")
263263
now = datetime.datetime.now()
264-
if now - entry["time"] <= _stale_after:
264+
if now - entry.time <= _stale_after:
265265
_print("And it is fresh!")
266-
return entry["value"]
266+
return entry.value
267267
_print("But it is stale... :(")
268-
if entry["being_calculated"]:
268+
if entry.being_calculated:
269269
if _next_time:
270270
_print("Returning stale.")
271-
return entry["value"] # return stale val
271+
return entry.value # return stale val
272272
_print("Already calc. Waiting on change.")
273273
try:
274274
return core.wait_on_entry_calc(key)
@@ -283,10 +283,10 @@ def func_wrapper(*args, **kwds):
283283
)
284284
finally:
285285
core.mark_entry_not_calculated(key)
286-
return entry["value"]
286+
return entry.value
287287
_print("Calling decorated function and waiting")
288288
return _calc_entry(core, key, func, args, kwds)
289-
if entry["being_calculated"]:
289+
if entry.being_calculated:
290290
_print("No value but being calculated. Waiting.")
291291
try:
292292
return core.wait_on_entry_calc(key)

src/cachier/cores/base.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
import abc # for the _BaseCore abstract base class
1010
import inspect
1111
import threading
12-
from typing import Callable
12+
from typing import Callable, Optional, Tuple
1313

1414
from .._types import HashFunc
15-
from ..config import _update_with_defaults
15+
from ..config import CacheEntry, _update_with_defaults
1616

1717

1818
class RecalculationNeeded(Exception):
@@ -51,7 +51,7 @@ def get_key(self, args, kwds):
5151
"""Return a unique key based on the arguments provided."""
5252
return self.hash_func(args, kwds)
5353

54-
def get_entry(self, args, kwds):
54+
def get_entry(self, args, kwds) -> Tuple[str, Optional[CacheEntry]]:
5555
"""Get entry based on given arguments.
5656
5757
Return the result mapped to the given arguments in this core's cache,
@@ -76,7 +76,7 @@ def check_calc_timeout(self, time_spent):
7676
raise RecalculationNeeded()
7777

7878
@abc.abstractmethod
79-
def get_entry_by_key(self, key):
79+
def get_entry_by_key(self, key: str) -> Tuple[str, Optional[CacheEntry]]:
8080
"""Get entry based on given key.
8181
8282
Return the result mapped to the given key in this core's cache, if such
@@ -85,25 +85,25 @@ def get_entry_by_key(self, key):
8585
"""
8686

8787
@abc.abstractmethod
88-
def set_entry(self, key, func_res):
88+
def set_entry(self, key: str, func_res):
8989
"""Map the given result to the given key in this core's cache."""
9090

9191
@abc.abstractmethod
92-
def mark_entry_being_calculated(self, key):
92+
def mark_entry_being_calculated(self, key: str) -> None:
9393
"""Mark the entry mapped by the given key as being calculated."""
9494

9595
@abc.abstractmethod
96-
def mark_entry_not_calculated(self, key):
96+
def mark_entry_not_calculated(self, key: str) -> None:
9797
"""Mark the entry mapped by the given key as not being calculated."""
9898

9999
@abc.abstractmethod
100-
def wait_on_entry_calc(self, key):
100+
def wait_on_entry_calc(self, key: str) -> None:
101101
"""Wait on the entry with keys being calculated and returns result."""
102102

103103
@abc.abstractmethod
104-
def clear_cache(self):
104+
def clear_cache(self) -> None:
105105
"""Clear the cache of this core."""
106106

107107
@abc.abstractmethod
108-
def clear_being_calculated(self):
108+
def clear_being_calculated(self) -> None:
109109
"""Mark all entries in this cache as not being calculated."""

src/cachier/cores/memory.py

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import threading
44
from datetime import datetime
5+
from typing import Any, Optional, Tuple
56

67
from .._types import HashFunc
8+
from ..config import CacheEntry
79
from .base import _BaseCore, _get_func_str
810

911

@@ -14,76 +16,78 @@ def __init__(self, hash_func: HashFunc, wait_for_calc_timeout: int):
1416
super().__init__(hash_func, wait_for_calc_timeout)
1517
self.cache = {}
1618

17-
def _hash_func_key(self, key):
19+
def _hash_func_key(self, key: str) -> str:
1820
return f"{_get_func_str(self.func)}:{key}"
1921

20-
def get_entry_by_key(self, key, reload=False):
22+
def get_entry_by_key(
23+
self, key: str, reload=False
24+
) -> Tuple[str, Optional[CacheEntry]]:
2125
with self.lock:
2226
return key, self.cache.get(self._hash_func_key(key), None)
2327

24-
def set_entry(self, key, func_res):
28+
def set_entry(self, key: str, func_res: Any) -> None:
2529
with self.lock:
2630
try:
2731
# we need to retain the existing condition so that
2832
# mark_entry_not_calculated can notify all possibly-waiting
2933
# threads about it
30-
cond = self.cache[self._hash_func_key(key)]["condition"]
34+
cond = self.cache[self._hash_func_key(key)].condition
3135
except KeyError: # pragma: no cover
3236
cond = None
33-
self.cache[self._hash_func_key(key)] = {
34-
"value": func_res,
35-
"time": datetime.now(),
36-
"stale": False,
37-
"being_calculated": False,
38-
"condition": cond,
39-
}
37+
self.cache[self._hash_func_key(key)] = CacheEntry(
38+
value=func_res,
39+
time=datetime.now(),
40+
stale=False,
41+
being_calculated=False,
42+
condition=cond,
43+
)
4044

41-
def mark_entry_being_calculated(self, key):
45+
def mark_entry_being_calculated(self, key: str) -> None:
4246
with self.lock:
4347
condition = threading.Condition()
4448
# condition.acquire()
4549
try:
46-
self.cache[self._hash_func_key(key)]["being_calculated"] = True
47-
self.cache[self._hash_func_key(key)]["condition"] = condition
50+
self.cache[self._hash_func_key(key)].being_calculated = True
51+
self.cache[self._hash_func_key(key)].condition = condition
4852
except KeyError:
49-
self.cache[self._hash_func_key(key)] = {
50-
"value": None,
51-
"time": datetime.now(),
52-
"stale": False,
53-
"being_calculated": True,
54-
"condition": condition,
55-
}
53+
self.cache[self._hash_func_key(key)] = CacheEntry(
54+
value=None,
55+
time=datetime.now(),
56+
stale=False,
57+
being_calculated=True,
58+
condition=condition,
59+
)
5660

57-
def mark_entry_not_calculated(self, key):
61+
def mark_entry_not_calculated(self, key: str) -> None:
5862
with self.lock:
5963
try:
6064
entry = self.cache[self._hash_func_key(key)]
6165
except KeyError: # pragma: no cover
6266
return # that's ok, we don't need an entry in that case
63-
entry["being_calculated"] = False
64-
cond = entry["condition"]
67+
entry.being_calculated = False
68+
cond = entry.condition
6569
if cond:
6670
cond.acquire()
6771
cond.notify_all()
6872
cond.release()
69-
entry["condition"] = None
73+
entry.condition = None
7074

71-
def wait_on_entry_calc(self, key):
75+
def wait_on_entry_calc(self, key: str) -> Any:
7276
with self.lock: # pragma: no cover
7377
entry = self.cache[self._hash_func_key(key)]
74-
if not entry["being_calculated"]:
75-
return entry["value"]
76-
entry["condition"].acquire()
77-
entry["condition"].wait()
78-
entry["condition"].release()
79-
return self.cache[self._hash_func_key(key)]["value"]
78+
if not entry.being_calculated:
79+
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
8084

81-
def clear_cache(self):
85+
def clear_cache(self) -> None:
8286
with self.lock:
8387
self.cache.clear()
8488

85-
def clear_being_calculated(self):
89+
def clear_being_calculated(self) -> None:
8690
with self.lock:
8791
for entry in self.cache.values():
88-
entry["being_calculated"] = False
89-
entry["condition"] = None
92+
entry.being_calculated = False
93+
entry.condition = None

src/cachier/cores/mongo.py

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
import warnings # to warn if pymongo is missing
1414
from contextlib import suppress
1515
from datetime import datetime
16+
from typing import Any, Optional, Tuple
1617

1718
from .._types import HashFunc, Mongetter
19+
from ..config import CacheEntry
1820

1921
with suppress(ImportError):
2022
from bson.binary import Binary # to save binary data to mongodb
@@ -65,29 +67,29 @@ def __init__(
6567
def _func_str(self) -> str:
6668
return _get_func_str(self.func)
6769

68-
def get_entry_by_key(self, key):
70+
def get_entry_by_key(self, key: str) -> Tuple[str, Optional[CacheEntry]]:
6971
res = self.mongo_collection.find_one(
7072
{"func": self._func_str, "key": key}
7173
)
7274
if not res:
7375
return key, None
7476
try:
75-
entry = {
76-
"value": pickle.loads(res["value"]), # noqa: S301
77-
"time": res.get("time", None),
78-
"stale": res.get("stale", False),
79-
"being_calculated": res.get("being_calculated", False),
80-
}
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+
)
8183
except KeyError:
82-
entry = {
83-
"value": None,
84-
"time": res.get("time", None),
85-
"stale": res.get("stale", False),
86-
"being_calculated": res.get("being_calculated", False),
87-
}
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+
)
8890
return key, entry
8991

90-
def set_entry(self, key, func_res):
92+
def set_entry(self, key: str, func_res: Any) -> None:
9193
thebytes = pickle.dumps(func_res)
9294
self.mongo_collection.update_one(
9395
filter={"func": self._func_str, "key": key},
@@ -104,14 +106,14 @@ def set_entry(self, key, func_res):
104106
upsert=True,
105107
)
106108

107-
def mark_entry_being_calculated(self, key):
109+
def mark_entry_being_calculated(self, key: str) -> None:
108110
self.mongo_collection.update_one(
109111
filter={"func": self._func_str, "key": key},
110112
update={"$set": {"being_calculated": True}},
111113
upsert=True,
112114
)
113115

114-
def mark_entry_not_calculated(self, key):
116+
def mark_entry_not_calculated(self, key: str) -> None:
115117
with suppress(OperationFailure): # don't care in this case
116118
self.mongo_collection.update_one(
117119
filter={
@@ -122,22 +124,22 @@ def mark_entry_not_calculated(self, key):
122124
upsert=False, # should not insert in this case
123125
)
124126

125-
def wait_on_entry_calc(self, key):
127+
def wait_on_entry_calc(self, key: str) -> Any:
126128
time_spent = 0
127129
while True:
128130
time.sleep(MONGO_SLEEP_DURATION_IN_SEC)
129131
time_spent += MONGO_SLEEP_DURATION_IN_SEC
130132
key, entry = self.get_entry_by_key(key)
131133
if entry is None:
132134
raise RecalculationNeeded()
133-
if not entry["being_calculated"]:
134-
return entry["value"]
135+
if not entry.being_calculated:
136+
return entry.value
135137
self.check_calc_timeout(time_spent)
136138

137-
def clear_cache(self):
139+
def clear_cache(self) -> None:
138140
self.mongo_collection.delete_many(filter={"func": self._func_str})
139141

140-
def clear_being_calculated(self):
142+
def clear_being_calculated(self) -> None:
141143
self.mongo_collection.update_many(
142144
filter={
143145
"func": self._func_str,

0 commit comments

Comments
 (0)