Skip to content

Commit 243357a

Browse files
committed
feat: add async guard and improve handling for instance methods in @cachier
- Raise TypeError for async instance methods without `allow_non_static_methods` opt-in. - Document `allow_non_static_methods` usage in README and config. - Generalize hashing utilities to reduce redundancy in test code.
1 parent 8e0e7f4 commit 243357a

6 files changed

Lines changed: 22 additions & 11 deletions

File tree

README.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@ Class and object methods can also be cached. Cachier will automatically ignore t
118118
return result
119119
120120
# Instance method relies on object attribute, NOT good to cache
121-
@cachier(allow_non_static_methods=True)
121+
# @cachier() would raise TypeError here -- this is intentional
122+
@cachier()
122123
def bad_usage(self, arg_1, arg_2):
123124
return arg_1 + arg_2 + self.arg_3
124125

src/cachier/config.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,9 @@ def set_global_params(**params: Any) -> None:
131131
'cleanup_interval', and 'caching_enabled'. In some cores, if the
132132
decorator was created without concrete value for 'wait_for_calc_timeout',
133133
calls that check calculation timeouts will fall back to the global
134-
'wait_for_calc_timeout' as well.
134+
'wait_for_calc_timeout' as well. 'allow_non_static_methods'
135+
(decoration-time only) controls whether instance methods are
136+
permitted; it is read once when @cachier is applied, not on each call.
135137
136138
Note that ``allow_non_static_methods`` is a **decoration-time**
137139
parameter: it is checked once when the ``@cachier`` decorator is

src/cachier/core.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -374,11 +374,11 @@ def _cachier_decorator(func):
374374
raise TypeError(
375375
f"@cachier cannot decorate instance method "
376376
f"'{func.__qualname__}' because the 'self' parameter is "
377-
f"excluded from cache-key computation and all instances "
378-
f"would share a single cache. Pass allow_non_static_methods=True "
379-
f"to the decorator or call "
380-
f"set_global_params(allow_non_static_methods=True) if "
381-
f"cross-instance cache sharing is intentional."
377+
"excluded from cache-key computation and all instances "
378+
"would share a single cache. Pass allow_non_static_methods=True "
379+
"to the decorator or call "
380+
"set_global_params(allow_non_static_methods=True) if "
381+
"cross-instance cache sharing is intentional."
382382
)
383383

384384
is_coroutine = inspect.iscoroutinefunction(func)

src/cachier/cores/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def set_func(self, func):
6262
-----
6363
Detection is name-based: only ``func_params[0] == "self"`` is
6464
checked. ``@classmethod`` functions whose first parameter is
65-
conventionally named ``cls`` are **not** detected as methods --
65+
conventionally named ``cls`` are not detected as methods --
6666
this is a known gap.
6767
6868
"""

tests/test_async_core.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,15 @@ async def async_method(self, x):
315315

316316
obj1.async_method.clear_cache()
317317

318+
async def test_guard_raises_without_opt_in(self):
319+
"""Test that @cachier raises TypeError for async instance methods without opt-in."""
320+
with pytest.raises(TypeError, match="allow_non_static_methods"):
321+
322+
class MyClass:
323+
@cachier(backend="memory")
324+
async def async_method(self, x):
325+
return x
326+
318327

319328
# =============================================================================
320329
# Sync Function Compatibility Tests

tests/test_smoke.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""Smoke tests for cachier - fast, no external service dependencies."""
22

33
import datetime
4+
import hashlib
5+
import pickle
46

57
import pytest
68

@@ -186,9 +188,6 @@ def test_classmethod_not_guarded():
186188
# ``cls``.
187189
def _hash_ignore_cls(args, kwds):
188190
filtered = {k: v for k, v in kwds.items() if k != "cls"}
189-
import hashlib
190-
import pickle
191-
192191
return hashlib.sha256(pickle.dumps((args, sorted(filtered.items())))).hexdigest()
193192

194193
class Foo:

0 commit comments

Comments
 (0)