Skip to content

Commit 35a0064

Browse files
authored
refactor: cleaning of using package/global defaults (#193)
* refactor: cleaning of using package/global defaults * Literal * MissingMongetter * typos * cleaning * types * note * isort * move loweer * resolve * global * nonlocal * import * fall * core = _MongoCore(None, _test_mongetter, 0)
1 parent 6de95e8 commit 35a0064

11 files changed

Lines changed: 197 additions & 194 deletions

File tree

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,10 @@ repos:
6666
- repo: https://github.com/astral-sh/ruff-pre-commit
6767
rev: v0.2.1
6868
hooks:
69+
# use black formatting
70+
- id: ruff-format
71+
name: Black by Ruff
6972
# basic check
7073
- id: ruff
7174
name: Ruff check
7275
args: ["--fix"]
73-
# use black formatting
74-
- id: ruff-format
75-
name: Black by Ruff

cachier/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from ._version import * # noqa: F403
2-
from .core import (
3-
cachier,
2+
from .config import (
43
disable_caching,
54
enable_caching,
65
get_default_params,
76
set_default_params,
87
)
8+
from .core import cachier
99

1010
__all__ = [
1111
"cachier",

cachier/_types.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from typing import TYPE_CHECKING, Callable, Literal
2+
3+
if TYPE_CHECKING:
4+
import pymongo.collection
5+
6+
7+
HashFunc = Callable[..., str]
8+
Mongetter = Callable[[], "pymongo.collection.Collection"]
9+
Backend = Literal["pickle", "mongo", "memory"]

cachier/config.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import datetime
2+
import hashlib
3+
import os
4+
import pickle
5+
from typing import Optional, TypedDict, Union
6+
7+
from ._types import Backend, HashFunc, Mongetter
8+
9+
10+
def _default_hash_func(args, kwds):
11+
# Sort the kwargs to ensure consistent ordering
12+
sorted_kwargs = sorted(kwds.items())
13+
# Serialize args and sorted_kwargs using pickle or similar
14+
serialized = pickle.dumps((args, sorted_kwargs))
15+
# Create a hash of the serialized data
16+
return hashlib.sha256(serialized).hexdigest()
17+
18+
19+
class Params(TypedDict):
20+
caching_enabled: bool
21+
hash_func: HashFunc
22+
backend: Backend
23+
mongetter: Optional[Mongetter]
24+
stale_after: datetime.timedelta
25+
next_time: bool
26+
cache_dir: Union[str, os.PathLike]
27+
pickle_reload: bool
28+
separate_files: bool
29+
wait_for_calc_timeout: int
30+
allow_none: bool
31+
32+
33+
_default_params: Params = {
34+
"caching_enabled": True,
35+
"hash_func": _default_hash_func,
36+
"backend": "pickle",
37+
"mongetter": None,
38+
"stale_after": datetime.timedelta.max,
39+
"next_time": False,
40+
"cache_dir": "~/.cachier/",
41+
"pickle_reload": True,
42+
"separate_files": False,
43+
"wait_for_calc_timeout": 0,
44+
"allow_none": False,
45+
}
46+
47+
48+
def _update_with_defaults(param, name: str):
49+
import cachier
50+
51+
if param is None:
52+
return cachier.config._default_params[name]
53+
return param
54+
55+
56+
def set_default_params(**params):
57+
"""Configure global parameters applicable to all memoized functions.
58+
59+
This function takes the same keyword parameters as the ones defined in the
60+
decorator, which can be passed all at once or with multiple calls.
61+
Parameters given directly to a decorator take precedence over any values
62+
set by this function.
63+
64+
Only 'stale_after', 'next_time', and 'wait_for_calc_timeout' can be changed
65+
after the memoization decorator has been applied. Other parameters will
66+
only have an effect on decorators applied after this function is run.
67+
68+
"""
69+
import cachier
70+
71+
valid_params = (
72+
p for p in params.items() if p[0] in cachier.config._default_params
73+
)
74+
_default_params.update(valid_params)
75+
76+
77+
def get_default_params():
78+
"""Get current set of default parameters."""
79+
import cachier
80+
81+
return cachier.config._default_params
82+
83+
84+
def enable_caching():
85+
"""Enable caching globally."""
86+
import cachier
87+
88+
cachier.config._default_params["caching_enabled"] = True
89+
90+
91+
def disable_caching():
92+
"""Disable caching globally."""
93+
import cachier
94+
95+
cachier.config._default_params["caching_enabled"] = False

cachier/core.py

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

10-
# python 2 compatibility
11-
1210
import datetime
13-
import hashlib
1411
import inspect
1512
import os
16-
import pickle
1713
from collections import OrderedDict
1814
from concurrent.futures import ThreadPoolExecutor
1915
from functools import wraps
20-
from typing import TYPE_CHECKING, Callable, Literal, Optional, TypedDict, Union
16+
from typing import Optional, Union
2117
from warnings import warn
2218

19+
from .config import (
20+
Backend,
21+
HashFunc,
22+
Mongetter,
23+
_default_params,
24+
_update_with_defaults,
25+
)
2326
from .cores.base import RecalculationNeeded, _BaseCore
2427
from .cores.memory import _MemoryCore
2528
from .cores.mongo import _MongoCore
2629
from .cores.pickle import _PickleCore
2730

28-
if TYPE_CHECKING:
29-
import pymongo.collection
30-
31-
3231
MAX_WORKERS_ENVAR_NAME = "CACHIER_MAX_WORKERS"
3332
DEFAULT_MAX_WORKERS = 8
3433

@@ -68,15 +67,6 @@ def _calc_entry(core, key, func, args, kwds):
6867
core.mark_entry_not_calculated(key)
6968

7069

71-
def _default_hash_func(args, kwds):
72-
# Sort the kwargs to ensure consistent ordering
73-
sorted_kwargs = sorted(kwds.items())
74-
# Serialize args and sorted_kwargs using pickle or similar
75-
serialized = pickle.dumps((args, sorted_kwargs))
76-
# Create a hash of the serialized data
77-
return hashlib.sha256(serialized).hexdigest()
78-
79-
8070
def _convert_args_kwargs(
8171
func, _is_method: bool, args: tuple, kwds: dict
8272
) -> dict:
@@ -103,44 +93,6 @@ def _convert_args_kwargs(
10393
return OrderedDict(sorted(kwargs.items()))
10494

10595

106-
class MissingMongetter(ValueError):
107-
"""Thrown when the mongetter keyword argument is missing."""
108-
109-
110-
HashFunc = Callable[..., str]
111-
Mongetter = Callable[[], "pymongo.collection.Collection"]
112-
Backend = Literal["pickle", "mongo", "memory"]
113-
114-
115-
class Params(TypedDict):
116-
caching_enabled: bool
117-
hash_func: HashFunc
118-
backend: Backend
119-
mongetter: Optional[Mongetter]
120-
stale_after: datetime.timedelta
121-
next_time: bool
122-
cache_dir: Union[str, os.PathLike]
123-
pickle_reload: bool
124-
separate_files: bool
125-
wait_for_calc_timeout: int
126-
allow_none: bool
127-
128-
129-
_default_params: Params = {
130-
"caching_enabled": True,
131-
"hash_func": _default_hash_func,
132-
"backend": "pickle",
133-
"mongetter": None,
134-
"stale_after": datetime.timedelta.max,
135-
"next_time": False,
136-
"cache_dir": "~/.cachier/",
137-
"pickle_reload": True,
138-
"separate_files": False,
139-
"wait_for_calc_timeout": 0,
140-
"allow_none": False,
141-
}
142-
143-
14496
def cachier(
14597
hash_func: Optional[HashFunc] = None,
14698
hash_params: Optional[HashFunc] = None,
@@ -219,13 +171,12 @@ def cachier(
219171
)
220172
warn(message, DeprecationWarning, stacklevel=2)
221173
hash_func = hash_params
174+
# Update parameters with defaults if input is None
175+
backend = _update_with_defaults(backend, "backend")
176+
mongetter = _update_with_defaults(mongetter, "mongetter")
222177
# Override the backend parameter if a mongetter is provided.
223-
if mongetter is None:
224-
mongetter = _default_params["mongetter"]
225178
if callable(mongetter):
226179
backend = "mongo"
227-
if backend is None:
228-
backend = _default_params["backend"]
229180
core: _BaseCore
230181
if backend == "pickle":
231182
core = _PickleCore(
@@ -234,23 +185,16 @@ def cachier(
234185
cache_dir=cache_dir,
235186
separate_files=separate_files,
236187
wait_for_calc_timeout=wait_for_calc_timeout,
237-
default_params=_default_params,
238188
)
239189
elif backend == "mongo":
240-
if mongetter is None:
241-
raise MissingMongetter(
242-
"must specify ``mongetter`` when using the mongo core"
243-
)
244190
core = _MongoCore(
245-
mongetter=mongetter,
246191
hash_func=hash_func,
192+
mongetter=mongetter,
247193
wait_for_calc_timeout=wait_for_calc_timeout,
248-
default_params=_default_params,
249194
)
250195
elif backend == "memory":
251196
core = _MemoryCore(
252-
hash_func=hash_func,
253-
default_params=_default_params,
197+
hash_func=hash_func, wait_for_calc_timeout=wait_for_calc_timeout
254198
)
255199
else:
256200
raise ValueError("specified an invalid core: %s" % backend)
@@ -261,11 +205,7 @@ def _cachier_decorator(func):
261205
@wraps(func)
262206
def func_wrapper(*args, **kwds):
263207
nonlocal allow_none
264-
_allow_none = (
265-
allow_none
266-
if allow_none is not None
267-
else _default_params["allow_none"]
268-
)
208+
_allow_none = _update_with_defaults(allow_none, "allow_none")
269209
# print('Inside general wrapper for {}.'.format(func.__name__))
270210
ignore_cache = kwds.pop("ignore_cache", False)
271211
overwrite_cache = kwds.pop("overwrite_cache", False)
@@ -289,10 +229,10 @@ def func_wrapper(*args, **kwds):
289229
_print("Entry found.")
290230
if _allow_none or entry.get("value", None) is not None:
291231
_print("Cached result found.")
292-
local_stale_after = (
293-
stale_after or _default_params["stale_after"]
232+
local_stale_after = _update_with_defaults(
233+
stale_after, "stale_after"
294234
)
295-
local_next_time = next_time or _default_params["next_time"] # noqa: E501
235+
local_next_time = _update_with_defaults(next_time, "next_time")
296236
now = datetime.datetime.now()
297237
if now - entry["time"] <= local_stale_after:
298238
_print("And it is fresh!")
@@ -362,35 +302,3 @@ def precache_value(*args, value_to_cache, **kwds):
362302
return func_wrapper
363303

364304
return _cachier_decorator
365-
366-
367-
def set_default_params(**params):
368-
"""Configure global parameters applicable to all memoized functions.
369-
370-
This function takes the same keyword parameters as the ones defined in the
371-
decorator, which can be passed all at once or with multiple calls.
372-
Parameters given directly to a decorator take precedence over any values
373-
set by this function.
374-
375-
Only 'stale_after', 'next_time', and 'wait_for_calc_timeout' can be changed
376-
after the memoization decorator has been applied. Other parameters will
377-
only have an effect on decorators applied after this function is run.
378-
379-
"""
380-
valid_params = (p for p in params.items() if p[0] in _default_params)
381-
_default_params.update(valid_params)
382-
383-
384-
def get_default_params():
385-
"""Get current set of default parameters."""
386-
return _default_params
387-
388-
389-
def enable_caching():
390-
"""Enable caching globally."""
391-
_default_params["caching_enabled"] = True
392-
393-
394-
def disable_caching():
395-
"""Disable caching globally."""
396-
_default_params["caching_enabled"] = False

cachier/cores/base.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88

99
import abc # for the _BaseCore abstract base class
1010
import inspect
11+
import threading
12+
13+
from .._types import HashFunc
14+
from ..config import _update_with_defaults
1115

1216

1317
class RecalculationNeeded(Exception):
@@ -17,9 +21,10 @@ class RecalculationNeeded(Exception):
1721
class _BaseCore:
1822
__metaclass__ = abc.ABCMeta
1923

20-
def __init__(self, hash_func, default_params):
21-
self.default_params = default_params
22-
self.hash_func = hash_func
24+
def __init__(self, hash_func: HashFunc, wait_for_calc_timeout: int):
25+
self.hash_func = _update_with_defaults(hash_func, "hash_func")
26+
self.wait_for_calc_timeout = wait_for_calc_timeout
27+
self.lock = threading.RLock()
2328

2429
def set_func(self, func):
2530
"""Sets the function this core will use.
@@ -37,10 +42,7 @@ def set_func(self, func):
3742

3843
def get_key(self, args, kwds):
3944
"""Returns a unique key based on the arguments provided."""
40-
if self.hash_func is not None:
41-
return self.hash_func(args, kwds)
42-
else:
43-
return self.default_params["hash_func"](args, kwds)
45+
return self.hash_func(args, kwds)
4446

4547
def get_entry(self, args, kwds):
4648
"""Returns the result mapped to the given arguments in this core's
@@ -56,10 +58,9 @@ def precache_value(self, args, kwds, value_to_cache):
5658

5759
def check_calc_timeout(self, time_spent):
5860
"""Raise an exception if a recalculation is needed."""
59-
if self.wait_for_calc_timeout is not None:
60-
calc_timeout = self.wait_for_calc_timeout
61-
else:
62-
calc_timeout = self.default_params["wait_for_calc_timeout"]
61+
calc_timeout = _update_with_defaults(
62+
self.wait_for_calc_timeout, "wait_for_calc_timeout"
63+
)
6364
if calc_timeout > 0 and (time_spent >= calc_timeout):
6465
raise RecalculationNeeded()
6566

0 commit comments

Comments
 (0)