Skip to content

Commit 4f69bce

Browse files
authored
Merge branch 'master' into copilot/add-cache-analytics-framework
2 parents 6f82691 + a683275 commit 4f69bce

24 files changed

Lines changed: 882 additions & 622 deletions

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ repos:
4242
- id: mdformat
4343
additional_dependencies:
4444
- mdformat-gfm
45-
- mdformat-black
45+
- mdformat-ruff
4646
- mdformat_frontmatter
4747
args: ["--number"]
4848

@@ -61,7 +61,7 @@ repos:
6161
- id: ruff-format
6262
name: Black by Ruff
6363
# basic check
64-
- id: ruff
64+
- id: ruff-check
6565
name: Ruff check
6666
args: ["--fix"] #, "--unsafe-fixes"
6767

examples/redis_example.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,7 @@ def demo_callable_client():
113113

114114
def get_redis_client():
115115
"""Get a Redis client."""
116-
return redis.Redis(
117-
host="localhost", port=6379, db=0, decode_responses=False
118-
)
116+
return redis.Redis(host="localhost", port=6379, db=0, decode_responses=False)
119117

120118
@cachier(backend="redis", redis_client=get_redis_client)
121119
def cached_with_callable(n):

pyproject.toml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,11 @@ namespaces = false # to disable scanning PEP 420 namespaces (true by default)
7272

7373
# === Linting & Formatting ===
7474

75-
[tool.black]
76-
line-length = 79
77-
7875
# --- ruff ---
7976

8077
[tool.ruff]
8178
target-version = "py39"
82-
line-length = 79
79+
line-length = 120
8380
# Exclude a variety of commonly ignored directories.
8481
exclude = [
8582
".eggs",

src/cachier/config.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,7 @@ class CacheEntry:
8383
_completed: bool = False
8484

8585

86-
def _update_with_defaults(
87-
param, name: str, func_kwargs: Optional[dict] = None
88-
):
86+
def _update_with_defaults(param, name: str, func_kwargs: Optional[dict] = None):
8987
import cachier
9088

9189
if func_kwargs:
@@ -107,8 +105,7 @@ def set_default_params(**params: Any) -> None:
107105
import warnings
108106

109107
warnings.warn(
110-
"Called `set_default_params` is deprecated and will be removed."
111-
" Please use `set_global_params` instead.",
108+
"Called `set_default_params` is deprecated and will be removed. Please use `set_global_params` instead.",
112109
DeprecationWarning,
113110
stacklevel=2,
114111
)
@@ -138,11 +135,7 @@ def set_global_params(**params: Any) -> None:
138135
"""
139136
import cachier
140137

141-
valid_params = {
142-
k: v
143-
for k, v in params.items()
144-
if hasattr(cachier.config._global_params, k)
145-
}
138+
valid_params = {k: v for k, v in params.items() if hasattr(cachier.config._global_params, k)}
146139
cachier.config._global_params = replace(
147140
cachier.config._global_params,
148141
**valid_params,
@@ -159,8 +152,7 @@ def get_default_params() -> Params:
159152
import warnings
160153

161154
warnings.warn(
162-
"Called `get_default_params` is deprecated and will be removed."
163-
" Please use `get_global_params` instead.",
155+
"Called `get_default_params` is deprecated and will be removed. Please use `get_global_params` instead.",
164156
DeprecationWarning,
165157
stacklevel=2,
166158
)

src/cachier/core.py

Lines changed: 63 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,7 @@ def _function_thread(core, key, func, args, kwds):
5858
print(f"Function call failed with the following exception:\n{exc}")
5959

6060

61-
def _calc_entry(
62-
core, key, func, args, kwds, printer=lambda *_: None
63-
) -> Optional[Any]:
61+
def _calc_entry(core, key, func, args, kwds, printer=lambda *_: None) -> Optional[Any]:
6462
core.mark_entry_being_calculated(key)
6563
try:
6664
func_res = func(*args, **kwds)
@@ -75,37 +73,67 @@ def _calc_entry(
7573
core.mark_entry_not_calculated(key)
7674

7775

78-
def _convert_args_kwargs(
79-
func, _is_method: bool, args: tuple, kwds: dict
80-
) -> dict:
76+
def _convert_args_kwargs(func, _is_method: bool, args: tuple, kwds: dict) -> dict:
8177
"""Convert mix of positional and keyword arguments to aggregated kwargs."""
8278
# unwrap if the function is functools.partial
8379
if hasattr(func, "func"):
8480
args = func.args + args
8581
kwds.update({k: v for k, v in func.keywords.items() if k not in kwds})
8682
func = func.func
87-
func_params = list(inspect.signature(func).parameters)
88-
args_as_kw = dict(
89-
zip(func_params[1:], args[1:])
90-
if _is_method
91-
else zip(func_params, args)
92-
)
93-
# init with default values
94-
kwargs = {
95-
k: v.default
96-
for k, v in inspect.signature(func).parameters.items()
97-
if v.default is not inspect.Parameter.empty
98-
}
99-
# merge args expanded as kwargs and the original kwds
100-
kwargs.update(dict(**args_as_kw, **kwds))
83+
84+
sig = inspect.signature(func)
85+
func_params = list(sig.parameters)
86+
87+
# Separate regular parameters from VAR_POSITIONAL
88+
regular_params = []
89+
var_positional_name = None
90+
91+
for param_name in func_params:
92+
param = sig.parameters[param_name]
93+
if param.kind == inspect.Parameter.VAR_POSITIONAL:
94+
var_positional_name = param_name
95+
elif param.kind in (
96+
inspect.Parameter.POSITIONAL_ONLY,
97+
inspect.Parameter.POSITIONAL_OR_KEYWORD
98+
):
99+
regular_params.append(param_name)
100+
101+
# Map positional arguments to regular parameters
102+
if _is_method:
103+
# Skip 'self' for methods
104+
args_to_map = args[1:]
105+
params_to_use = regular_params[1:]
106+
else:
107+
args_to_map = args
108+
params_to_use = regular_params
109+
110+
# Map as many args as possible to regular parameters
111+
num_regular = len(params_to_use)
112+
args_as_kw = dict(zip(params_to_use, args_to_map[:num_regular]))
113+
114+
# Handle variadic positional arguments
115+
# Store them with indexed keys like __varargs_0__, __varargs_1__, etc.
116+
if var_positional_name and len(args_to_map) > num_regular:
117+
var_args = args_to_map[num_regular:]
118+
for i, arg in enumerate(var_args):
119+
args_as_kw[f"__varargs_{i}__"] = arg
120+
121+
# Init with default values
122+
kwargs = {k: v.default for k, v in sig.parameters.items() if v.default is not inspect.Parameter.empty}
123+
124+
# Merge args expanded as kwargs and the original kwds
125+
kwargs.update(args_as_kw)
126+
127+
# Handle keyword arguments (including variadic keyword arguments)
128+
kwargs.update(kwds)
129+
101130
return OrderedDict(sorted(kwargs.items()))
102131

103132

104133
def _pop_kwds_with_deprecation(kwds, name: str, default_value: bool):
105134
if name in kwds:
106135
warnings.warn(
107-
f"`{name}` is deprecated and will be removed in a future release,"
108-
" use `cachier__` alternative instead.",
136+
f"`{name}` is deprecated and will be removed in a future release, use `cachier__` alternative instead.",
109137
DeprecationWarning,
110138
stacklevel=2,
111139
)
@@ -216,10 +244,7 @@ def cachier(
216244
"""
217245
# Check for deprecated parameters
218246
if hash_params is not None:
219-
message = (
220-
"hash_params will be removed in a future release, "
221-
"please use hash_func instead"
222-
)
247+
message = "hash_params will be removed in a future release, please use hash_func instead"
223248
warn(message, DeprecationWarning, stacklevel=2)
224249
hash_func = hash_params
225250
# Update parameters with defaults if input is None
@@ -261,7 +286,7 @@ def cachier(
261286
hash_func=hash_func,
262287
wait_for_calc_timeout=wait_for_calc_timeout,
263288
entry_size_limit=size_limit_bytes,
264-
metrics=cache_metrics,
289+
metrics=cache_metrics
265290
)
266291
elif backend == "sql":
267292
core = _SQLCore(
@@ -316,41 +341,25 @@ def _call(*args, max_age: Optional[timedelta] = None, **kwds):
316341
nonlocal allow_none, last_cleanup
317342
_allow_none = _update_with_defaults(allow_none, "allow_none", kwds)
318343
# print('Inside general wrapper for {}.'.format(func.__name__))
319-
ignore_cache = _pop_kwds_with_deprecation(
320-
kwds, "ignore_cache", False
321-
)
322-
overwrite_cache = _pop_kwds_with_deprecation(
323-
kwds, "overwrite_cache", False
324-
)
344+
ignore_cache = _pop_kwds_with_deprecation(kwds, "ignore_cache", False)
345+
overwrite_cache = _pop_kwds_with_deprecation(kwds, "overwrite_cache", False)
325346
verbose = _pop_kwds_with_deprecation(kwds, "verbose_cache", False)
326347
ignore_cache = kwds.pop("cachier__skip_cache", ignore_cache)
327-
overwrite_cache = kwds.pop(
328-
"cachier__overwrite_cache", overwrite_cache
329-
)
348+
overwrite_cache = kwds.pop("cachier__overwrite_cache", overwrite_cache)
330349
verbose = kwds.pop("cachier__verbose", verbose)
331-
_stale_after = _update_with_defaults(
332-
stale_after, "stale_after", kwds
333-
)
350+
_stale_after = _update_with_defaults(stale_after, "stale_after", kwds)
334351
_next_time = _update_with_defaults(next_time, "next_time", kwds)
335-
_cleanup_flag = _update_with_defaults(
336-
cleanup_stale, "cleanup_stale", kwds
337-
)
338-
_cleanup_interval_val = _update_with_defaults(
339-
cleanup_interval, "cleanup_interval", kwds
340-
)
352+
_cleanup_flag = _update_with_defaults(cleanup_stale, "cleanup_stale", kwds)
353+
_cleanup_interval_val = _update_with_defaults(cleanup_interval, "cleanup_interval", kwds)
341354
# merge args expanded as kwargs and the original kwds
342-
kwargs = _convert_args_kwargs(
343-
func, _is_method=core.func_is_method, args=args, kwds=kwds
344-
)
355+
kwargs = _convert_args_kwargs(func, _is_method=core.func_is_method, args=args, kwds=kwds)
345356

346357
if _cleanup_flag:
347358
now = datetime.now()
348359
with cleanup_lock:
349360
if now - last_cleanup >= _cleanup_interval_val:
350361
last_cleanup = now
351-
_get_executor().submit(
352-
core.delete_stale_entries, _stale_after
353-
)
362+
_get_executor().submit(core.delete_stale_entries, _stale_after)
354363

355364
_print = print if verbose else lambda x: None
356365

@@ -401,10 +410,7 @@ def _call(*args, max_age: Optional[timedelta] = None, **kwds):
401410
nonneg_max_age = True
402411
if max_age is not None:
403412
if max_age < ZERO_TIMEDELTA:
404-
_print(
405-
"max_age is negative. "
406-
"Cached result considered stale."
407-
)
413+
_print("max_age is negative. Cached result considered stale.")
408414
nonneg_max_age = False
409415
else:
410416
assert max_age is not None # noqa: S101
@@ -459,9 +465,7 @@ def _call(*args, max_age: Optional[timedelta] = None, **kwds):
459465
cache_metrics.record_recalculation()
460466
core.mark_entry_being_calculated(key)
461467
try:
462-
_get_executor().submit(
463-
_function_thread, core, key, func, args, kwds
464-
)
468+
_get_executor().submit(_function_thread, core, key, func, args, kwds)
465469
finally:
466470
core.mark_entry_not_calculated(key)
467471
if cache_metrics:
@@ -542,9 +546,7 @@ def _precache_value(*args, value_to_cache, **kwds): # noqa: D417
542546
543547
"""
544548
# merge args expanded as kwargs and the original kwds
545-
kwargs = _convert_args_kwargs(
546-
func, _is_method=core.func_is_method, args=args, kwds=kwds
547-
)
549+
kwargs = _convert_args_kwargs(func, _is_method=core.func_is_method, args=args, kwds=kwds)
548550
return core.precache_value((), kwargs, value_to_cache)
549551

550552
func_wrapper.clear_cache = _clear_cache

src/cachier/cores/base.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,7 @@ def precache_value(self, args, kwds, value_to_cache):
9090

9191
def check_calc_timeout(self, time_spent):
9292
"""Raise an exception if a recalculation is needed."""
93-
calc_timeout = _update_with_defaults(
94-
self.wait_for_calc_timeout, "wait_for_calc_timeout"
95-
)
93+
calc_timeout = _update_with_defaults(self.wait_for_calc_timeout, "wait_for_calc_timeout")
9694
if calc_timeout > 0 and (time_spent >= calc_timeout):
9795
raise RecalculationNeeded()
9896

src/cachier/cores/memory.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,7 @@ def __init__(
3030
def _hash_func_key(self, key: str) -> str:
3131
return f"{_get_func_str(self.func)}:{key}"
3232

33-
def get_entry_by_key(
34-
self, key: str, reload=False
35-
) -> Tuple[str, Optional[CacheEntry]]:
33+
def get_entry_by_key(self, key: str, reload=False) -> Tuple[str, Optional[CacheEntry]]:
3634
with self.lock:
3735
return key, self.cache.get(self._hash_func_key(key), None)
3836

@@ -122,9 +120,7 @@ def delete_stale_entries(self, stale_after: timedelta) -> None:
122120
"""Remove stale entries from the in-memory cache."""
123121
now = datetime.now()
124122
with self.lock:
125-
keys_to_delete = [
126-
k for k, v in self.cache.items() if now - v.time > stale_after
127-
]
123+
keys_to_delete = [k for k, v in self.cache.items() if now - v.time > stale_after]
128124
for key in keys_to_delete:
129125
del self.cache[key]
130126
# Update size metrics after deletion

src/cachier/cores/mongo.py

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,7 @@ def __init__(
6060
metrics=metrics,
6161
)
6262
if mongetter is None:
63-
raise MissingMongetter(
64-
"must specify ``mongetter`` when using the mongo core"
65-
)
63+
raise MissingMongetter("must specify ``mongetter`` when using the mongo core")
6664
self.mongetter = mongetter
6765
self.mongo_collection = self.mongetter()
6866
index_inf = self.mongo_collection.index_information()
@@ -78,9 +76,7 @@ def _func_str(self) -> str:
7876
return _get_func_str(self.func)
7977

8078
def get_entry_by_key(self, key: str) -> Tuple[str, Optional[CacheEntry]]:
81-
res = self.mongo_collection.find_one(
82-
{"func": self._func_str, "key": key}
83-
)
79+
res = self.mongo_collection.find_one({"func": self._func_str, "key": key})
8480
if not res:
8581
return key, None
8682
val = None
@@ -151,16 +147,11 @@ def clear_cache(self) -> None:
151147

152148
def clear_being_calculated(self) -> None:
153149
self.mongo_collection.update_many(
154-
filter={
155-
"func": self._func_str,
156-
"processing": True,
157-
},
150+
filter={"func": self._func_str, "processing": True},
158151
update={"$set": {"processing": False}},
159152
)
160153

161154
def delete_stale_entries(self, stale_after: timedelta) -> None:
162155
"""Delete stale entries from the MongoDB cache."""
163156
threshold = datetime.now() - stale_after
164-
self.mongo_collection.delete_many(
165-
filter={"func": self._func_str, "time": {"$lt": threshold}}
166-
)
157+
self.mongo_collection.delete_many(filter={"func": self._func_str, "time": {"$lt": threshold}})

0 commit comments

Comments
 (0)