Skip to content

Commit 82d2341

Browse files
authored
typing: adding check and fixing (#250)
* typing: adding check and fixing * timedelta * refactor * typo * more * note * names * typo * lint * fixed loading * save * save
1 parent 84fe27a commit 82d2341

8 files changed

Lines changed: 106 additions & 75 deletions

File tree

.pre-commit-config.yaml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,6 @@ repos:
5454
# https://prettier.io/docs/en/options.html#print-width
5555
args: ["--print-width=79"]
5656

57-
# - repo: https://github.com/pre-commit/mirrors-mypy
58-
# rev: v1.8.0
59-
# hooks:
60-
# - id: mypy
61-
6257
- repo: https://github.com/astral-sh/ruff-pre-commit
6358
rev: v0.6.9
6459
hooks:
@@ -70,6 +65,13 @@ repos:
7065
name: Ruff check
7166
args: ["--fix"]
7267

68+
# it needs to be after formatting hooks because the lines might be changed
69+
- repo: https://github.com/pre-commit/mirrors-mypy
70+
rev: v1.8.0
71+
hooks:
72+
- id: mypy
73+
files: "src/*"
74+
7375
- repo: https://github.com/tox-dev/pyproject-fmt
7476
rev: 2.2.4
7577
hooks:

src/cachier/config.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import datetime
21
import hashlib
32
import os
43
import pickle
54
import threading
65
from collections.abc import Mapping
76
from dataclasses import dataclass, replace
7+
from datetime import datetime, timedelta
88
from typing import Any, Optional, Union
99

1010
from ._types import Backend, HashFunc, Mongetter
@@ -27,7 +27,7 @@ class Params:
2727
hash_func: HashFunc = _default_hash_func
2828
backend: Backend = "pickle"
2929
mongetter: Optional[Mongetter] = None
30-
stale_after: datetime.timedelta = datetime.timedelta.max
30+
stale_after: timedelta = timedelta.max
3131
next_time: bool = False
3232
cache_dir: Union[str, os.PathLike] = "~/.cachier/"
3333
pickle_reload: bool = True
@@ -100,7 +100,8 @@ def set_global_params(**params: Mapping) -> None:
100100
if hasattr(cachier.config._global_params, k)
101101
}
102102
cachier.config._global_params = replace(
103-
cachier.config._global_params, **valid_params
103+
cachier.config._global_params,
104+
**valid_params, # type: ignore[arg-type]
104105
)
105106

106107

src/cachier/core.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
# http://www.opensource.org/licenses/MIT-license
88
# Copyright (c) 2016, Shay Palachy <shaypal5@gmail.com>
99

10-
import datetime
1110
import inspect
1211
import os
1312
import warnings
1413
from collections import OrderedDict
1514
from concurrent.futures import ThreadPoolExecutor
15+
from datetime import datetime, timedelta
1616
from functools import wraps
1717
from typing import Any, Optional, Union
1818
from warnings import warn
@@ -107,7 +107,7 @@ def cachier(
107107
hash_params: Optional[HashFunc] = None,
108108
backend: Optional[Backend] = None,
109109
mongetter: Optional[Mongetter] = None,
110-
stale_after: Optional[datetime.timedelta] = None,
110+
stale_after: Optional[timedelta] = None,
111111
next_time: Optional[bool] = None,
112112
cache_dir: Optional[Union[str, os.PathLike]] = None,
113113
pickle_reload: Optional[bool] = None,
@@ -259,7 +259,7 @@ def func_wrapper(*args, **kwds):
259259
_print("Entry found.")
260260
if _allow_none or entry.value is not None:
261261
_print("Cached result found.")
262-
now = datetime.datetime.now()
262+
now = datetime.now()
263263
if now - entry.time <= _stale_after:
264264
_print("And it is fresh!")
265265
return entry.value

src/cachier/cores/base.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ def _get_func_str(func: Callable) -> str:
2828
class _BaseCore:
2929
__metaclass__ = abc.ABCMeta
3030

31-
def __init__(self, hash_func: HashFunc, wait_for_calc_timeout: int):
31+
def __init__(
32+
self,
33+
hash_func: Optional[HashFunc],
34+
wait_for_calc_timeout: Optional[int],
35+
):
3236
self.hash_func = _update_with_defaults(hash_func, "hash_func")
3337
self.wait_for_calc_timeout = wait_for_calc_timeout
3438
self.lock = threading.RLock()

src/cachier/cores/memory.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import threading
44
from datetime import datetime
5-
from typing import Any, Optional, Tuple
5+
from typing import Any, Dict, Optional, Tuple
66

77
from .._types import HashFunc
88
from ..config import CacheEntry
@@ -12,9 +12,13 @@
1212
class _MemoryCore(_BaseCore):
1313
"""The memory core class for cachier."""
1414

15-
def __init__(self, hash_func: HashFunc, wait_for_calc_timeout: int):
15+
def __init__(
16+
self,
17+
hash_func: Optional[HashFunc],
18+
wait_for_calc_timeout: Optional[int],
19+
):
1620
super().__init__(hash_func, wait_for_calc_timeout)
17-
self.cache = {}
21+
self.cache: Dict[str, CacheEntry] = {}
1822

1923
def _hash_func_key(self, key: str) -> str:
2024
return f"{_get_func_str(self.func)}:{key}"
@@ -79,8 +83,12 @@ def wait_on_entry_calc(self, key: str) -> Any:
7983
hash_key = self._hash_func_key(key)
8084
with self.lock: # pragma: no cover
8185
entry = self.cache[hash_key]
86+
if entry is None:
87+
return None
8288
if not entry._processing:
8389
return entry.value
90+
if entry._condition is None:
91+
raise RuntimeError("No condition set for entry")
8492
entry._condition.acquire()
8593
entry._condition.wait()
8694
entry._condition.release()

src/cachier/cores/mongo.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ class _MongoCore(_BaseCore):
3737

3838
def __init__(
3939
self,
40-
hash_func: HashFunc,
41-
mongetter: Mongetter,
42-
wait_for_calc_timeout: int,
40+
hash_func: Optional[HashFunc],
41+
mongetter: Optional[Mongetter],
42+
wait_for_calc_timeout: Optional[int],
4343
):
4444
if "pymongo" not in sys.modules:
4545
warnings.warn(
@@ -48,7 +48,9 @@ def __init__(
4848
stacklevel=2,
4949
) # pragma: no cover
5050

51-
super().__init__(hash_func, wait_for_calc_timeout)
51+
super().__init__(
52+
hash_func=hash_func, wait_for_calc_timeout=wait_for_calc_timeout
53+
)
5254
if mongetter is None:
5355
raise MissingMongetter(
5456
"must specify ``mongetter`` when using the mongo core"

src/cachier/cores/pickle.py

Lines changed: 57 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import os
1010
import pickle # for local caching
1111
from datetime import datetime
12-
from typing import Any, Dict, Mapping, Optional, Tuple, Union
12+
from typing import Any, Dict, Optional, Tuple, Union
1313

1414
import portalocker # to lock on pickle cache IO
1515
from watchdog.events import PatternMatchingEventHandler
@@ -68,21 +68,22 @@ def on_modified(self, event) -> None:
6868

6969
def __init__(
7070
self,
71-
hash_func: HashFunc,
72-
pickle_reload: bool,
73-
cache_dir: str,
74-
separate_files: bool,
75-
wait_for_calc_timeout: int,
71+
hash_func: Optional[HashFunc],
72+
pickle_reload: Optional[bool],
73+
cache_dir: Optional[Union[str, os.PathLike]],
74+
separate_files: Optional[bool],
75+
wait_for_calc_timeout: Optional[int],
7676
):
7777
super().__init__(hash_func, wait_for_calc_timeout)
78-
self.cache = None
78+
self._cache_dict: Dict[str, CacheEntry] = {}
7979
self.reload = _update_with_defaults(pickle_reload, "pickle_reload")
8080
self.cache_dir = os.path.expanduser(
8181
_update_with_defaults(cache_dir, "cache_dir")
8282
)
8383
self.separate_files = _update_with_defaults(
8484
separate_files, "separate_files"
8585
)
86+
self._cache_used_fpath = ""
8687

8788
@property
8889
def cache_fname(self) -> str:
@@ -110,27 +111,30 @@ def _convert_legacy_cache_entry(
110111
_condition=entry.get("condition", None),
111112
)
112113

113-
def _load_cache(self) -> Mapping[str, CacheEntry]:
114+
def _load_cache_dict(self) -> Dict[str, CacheEntry]:
114115
try:
115116
with portalocker.Lock(self.cache_fpath, mode="rb") as cf:
116117
cache = pickle.load(cf) # noqa: S301
118+
self._cache_used_fpath = str(self.cache_fpath)
117119
except (FileNotFoundError, EOFError):
118120
cache = {}
119121
return {
120122
k: _PickleCore._convert_legacy_cache_entry(v)
121123
for k, v in cache.items()
122124
}
123125

124-
def _reload_cache(self) -> None:
126+
def get_cache_dict(self, reload: bool = False) -> Dict[str, CacheEntry]:
127+
if self._cache_used_fpath != self.cache_fpath:
128+
# force reload if the cache file has changed
129+
# this change is dies to using different wrapped function
130+
reload = True
131+
if self._cache_dict and not (self.reload or reload):
132+
return self._cache_dict
125133
with self.lock:
126-
self.cache = self._load_cache()
134+
self._cache_dict = self._load_cache_dict()
135+
return self._cache_dict
127136

128-
def _get_cache(self) -> Dict[str, CacheEntry]:
129-
if not self.cache:
130-
self._reload_cache()
131-
return self.cache
132-
133-
def _get_cache_by_key(
137+
def _load_cache_by_key(
134138
self, key=None, hash_str=None
135139
) -> Optional[CacheEntry]:
136140
fpath = self.cache_fpath
@@ -152,35 +156,42 @@ def _clear_being_calculated_all_cache_files(self) -> None:
152156
path, name = os.path.split(self.cache_fpath)
153157
for subpath in os.listdir(path):
154158
if subpath.startswith(name):
155-
entry = self._get_cache_by_key(hash_str=subpath.split("_")[-1])
159+
entry = self._load_cache_by_key(
160+
hash_str=subpath.split("_")[-1]
161+
)
156162
if entry is not None:
157-
entry.being_calculated = False
163+
entry._processing = False
158164
self._save_cache(entry, hash_str=subpath.split("_")[-1])
159165

160166
def _save_cache(
161-
self, cache, key: str = None, hash_str: str = None
167+
self,
168+
cache: Union[Dict[str, CacheEntry], CacheEntry],
169+
separate_file_key: Optional[str] = None,
170+
hash_str: Optional[str] = None,
162171
) -> None:
172+
if separate_file_key and not isinstance(cache, CacheEntry):
173+
raise ValueError(
174+
"`separate_file_key` should only be used with a CacheEntry"
175+
)
163176
fpath = self.cache_fpath
164-
if key is not None:
165-
fpath += f"_{key}"
177+
if separate_file_key is not None:
178+
fpath += f"_{separate_file_key}"
166179
elif hash_str is not None:
167180
fpath += f"_{hash_str}"
168181
with self.lock:
169-
self.cache = cache
170-
with portalocker.Lock(fpath, mode="wb") as cache_file:
171-
pickle.dump(cache, cache_file, protocol=4)
172-
if key is None:
173-
self._reload_cache()
182+
with portalocker.Lock(fpath, mode="wb") as cf:
183+
pickle.dump(cache, cf, protocol=4)
184+
# the same as check for separate_file, but changed for typing
185+
if isinstance(cache, dict):
186+
self._cache_dict = cache
187+
self._cache_used_fpath = str(self.cache_fpath)
174188

175189
def get_entry_by_key(
176190
self, key: str, reload: bool = False
177-
) -> Tuple[str, CacheEntry]:
178-
with self.lock:
179-
if self.separate_files:
180-
return key, self._get_cache_by_key(key)
181-
if self.reload or reload:
182-
self._reload_cache()
183-
return key, self._get_cache().get(key, None)
191+
) -> Tuple[str, Optional[CacheEntry]]:
192+
if self.separate_files:
193+
return key, self._load_cache_by_key(key)
194+
return key, self.get_cache_dict(reload).get(key)
184195

185196
def set_entry(self, key: str, func_res: Any) -> None:
186197
key_data = CacheEntry(
@@ -195,7 +206,7 @@ def set_entry(self, key: str, func_res: Any) -> None:
195206
return # pragma: no cover
196207

197208
with self.lock:
198-
cache = self._get_cache()
209+
cache = self.get_cache_dict()
199210
cache[key] = key_data
200211
self._save_cache(cache)
201212

@@ -207,21 +218,23 @@ def mark_entry_being_calculated_separate_files(self, key: str) -> None:
207218
stale=False,
208219
_processing=True,
209220
),
210-
key=key,
221+
separate_file_key=key,
211222
)
212223

213-
def mark_entry_not_calculated_separate_files(self, key: str) -> None:
224+
def _mark_entry_not_calculated_separate_files(self, key: str) -> None:
214225
_, entry = self.get_entry_by_key(key)
226+
if entry is None:
227+
return # that's ok, we don't need an entry in that case
215228
entry._processing = False
216-
self._save_cache(entry, key=key)
229+
self._save_cache(entry, separate_file_key=key)
217230

218231
def mark_entry_being_calculated(self, key: str) -> None:
219232
if self.separate_files:
220233
self.mark_entry_being_calculated_separate_files(key)
221234
return # pragma: no cover
222235

223236
with self.lock:
224-
cache = self._get_cache()
237+
cache = self.get_cache_dict()
225238
if key in cache:
226239
cache[key]._processing = True
227240
else:
@@ -235,24 +248,23 @@ def mark_entry_being_calculated(self, key: str) -> None:
235248

236249
def mark_entry_not_calculated(self, key: str) -> None:
237250
if self.separate_files:
238-
self.mark_entry_not_calculated_separate_files(key)
251+
self._mark_entry_not_calculated_separate_files(key)
239252
with self.lock:
240-
cache = self._get_cache()
253+
cache = self.get_cache_dict()
241254
# that's ok, we don't need an entry in that case
242255
if isinstance(cache, dict) and key in cache:
243256
cache[key]._processing = False
244257
self._save_cache(cache)
245258

246259
def wait_on_entry_calc(self, key: str) -> Any:
247260
if self.separate_files:
248-
entry = self._get_cache_by_key(key)
261+
entry = self._load_cache_by_key(key)
249262
filename = f"{self.cache_fname}_{key}"
250263
else:
251264
with self.lock:
252-
self._reload_cache()
253-
entry = self._get_cache()[key]
265+
entry = self.get_cache_dict()[key]
254266
filename = self.cache_fname
255-
if not entry._processing:
267+
if entry and not entry._processing:
256268
return entry.value
257269
event_handler = _PickleCore.CacheChangeHandler(
258270
filename=filename, core=self, key=key
@@ -280,7 +292,7 @@ def clear_being_calculated(self) -> None:
280292
return # pragma: no cover
281293

282294
with self.lock:
283-
cache = self._get_cache()
295+
cache = self.get_cache_dict()
284296
for key in cache:
285297
cache[key]._processing = False
286298
self._save_cache(cache)

0 commit comments

Comments
 (0)