|
31 | 31 |
|
32 | 32 | MAX_WORKERS_ENVAR_NAME = "CACHIER_MAX_WORKERS" |
33 | 33 | DEFAULT_MAX_WORKERS = 8 |
| 34 | +ZERO_TIMEDELTA = timedelta(seconds=0) |
34 | 35 |
|
35 | 36 |
|
36 | 37 | def _max_workers(): |
@@ -225,8 +226,31 @@ def cachier( |
225 | 226 | def _cachier_decorator(func): |
226 | 227 | core.set_func(func) |
227 | 228 |
|
228 | | - @wraps(func) |
229 | | - def func_wrapper(*args, **kwds): |
| 229 | + # --- |
| 230 | + # MAINTAINER NOTE: max_age parameter |
| 231 | + # |
| 232 | + # The _call function below supports a per-call 'max_age' parameter, |
| 233 | + # allowing users to specify a maximum allowed age for a cached value. |
| 234 | + # If the cached value is older than 'max_age', |
| 235 | + # a recalculation is triggered. This is in addition to the |
| 236 | + # per-decorator 'stale_after' parameter. |
| 237 | + # |
| 238 | + # The effective staleness threshold is the minimum of 'stale_after' |
| 239 | + # and 'max_age' (if provided). |
| 240 | + # This ensures that the strictest max age requirement is enforced. |
| 241 | + # |
| 242 | + # The main function wrapper is a standard function that passes |
| 243 | + # *args and **kwargs to _call. By default, max_age is None, |
| 244 | + # so only 'stale_after' is considered unless overridden. |
| 245 | + # |
| 246 | + # The user-facing API exposes: |
| 247 | + # - Per-call: myfunc(..., max_age=timedelta(...)) |
| 248 | + # |
| 249 | + # This design allows both one-off (per-call) and default |
| 250 | + # (per-decorator) max age constraints. |
| 251 | + # --- |
| 252 | + |
| 253 | + def _call(*args, max_age: Optional[timedelta] = None, **kwds): |
230 | 254 | nonlocal allow_none |
231 | 255 | _allow_none = _update_with_defaults(allow_none, "allow_none", kwds) |
232 | 256 | # print('Inside general wrapper for {}.'.format(func.__name__)) |
@@ -271,7 +295,23 @@ def func_wrapper(*args, **kwds): |
271 | 295 | if _allow_none or entry.value is not None: |
272 | 296 | _print("Cached result found.") |
273 | 297 | now = datetime.now() |
274 | | - if now - entry.time <= _stale_after: |
| 298 | + max_allowed_age = _stale_after |
| 299 | + nonneg_max_age = True |
| 300 | + if max_age is not None: |
| 301 | + if max_age < ZERO_TIMEDELTA: |
| 302 | + _print( |
| 303 | + "max_age is negative. " |
| 304 | + "Cached result considered stale." |
| 305 | + ) |
| 306 | + nonneg_max_age = False |
| 307 | + else: |
| 308 | + max_allowed_age = ( |
| 309 | + min(_stale_after, max_age) |
| 310 | + if max_age is not None |
| 311 | + else _stale_after |
| 312 | + ) |
| 313 | + # note: if max_age < 0, we always consider a value stale |
| 314 | + if nonneg_max_age and (now - entry.time <= max_allowed_age): |
275 | 315 | _print("And it is fresh!") |
276 | 316 | return entry.value |
277 | 317 | _print("But it is stale... :(") |
@@ -305,6 +345,14 @@ def func_wrapper(*args, **kwds): |
305 | 345 | _print("No entry found. No current calc. Calling like a boss.") |
306 | 346 | return _calc_entry(core, key, func, args, kwds) |
307 | 347 |
|
| 348 | + # MAINTAINER NOTE: The main function wrapper is now a standard function |
| 349 | + # that passes *args and **kwargs to _call. This ensures that user |
| 350 | + # arguments are not shifted, and max_age is only settable via keyword |
| 351 | + # argument. |
| 352 | + @wraps(func) |
| 353 | + def func_wrapper(*args, **kwargs): |
| 354 | + return _call(*args, **kwargs) |
| 355 | + |
308 | 356 | def _clear_cache(): |
309 | 357 | """Clear the cache.""" |
310 | 358 | core.clear_cache() |
|
0 commit comments