Skip to content

Commit 6f2127f

Browse files
Copilotshaypal5pre-commit-ci[bot]
authored
Fix caching regression: enable_caching() now affects existing decorators (#279)
* Initial plan * Initial analysis of caching regression issue Co-authored-by: shaypal5 <917954+shaypal5@users.noreply.github.com> * Fix caching regression by making caching_enabled check dynamic Co-authored-by: shaypal5 <917954+shaypal5@users.noreply.github.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: shaypal5 <917954+shaypal5@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent bd455da commit 6f2127f

2 files changed

Lines changed: 132 additions & 2 deletions

File tree

src/cachier/core.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,6 @@ def cachier(
185185
None will not be cached and are recalculated every call.
186186
187187
"""
188-
from .config import _global_params
189-
190188
# Check for deprecated parameters
191189
if hash_params is not None:
192190
message = (
@@ -289,6 +287,9 @@ def _call(*args, max_age: Optional[timedelta] = None, **kwds):
289287

290288
_print = print if verbose else lambda x: None
291289

290+
# Check current global caching state dynamically
291+
from .config import _global_params
292+
292293
if ignore_cache or not _global_params.caching_enabled:
293294
return (
294295
func(args[0], **kwargs)

tests/test_caching_regression.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
"""Test for caching enable/disable regression issue.
2+
3+
This test ensures that decorators defined when caching is disabled can still be
4+
enabled later via enable_caching().
5+
6+
"""
7+
8+
import datetime
9+
10+
import cachier
11+
12+
13+
def test_enable_caching_after_decorator_definition():
14+
"""Test that enable_caching() affects decorators when caching disabled."""
15+
# Start with caching disabled
16+
cachier.set_global_params(caching_enabled=False)
17+
18+
call_count = 0
19+
20+
# Use memory backend to avoid file cache persistence issues
21+
@cachier.cachier(backend="memory")
22+
def test_func(param):
23+
nonlocal call_count
24+
call_count += 1
25+
return f"result_{param}"
26+
27+
# Initially caching is disabled, so function should be called each time
28+
result1 = test_func("a")
29+
assert result1 == "result_a"
30+
assert call_count == 1
31+
32+
result2 = test_func("a") # Same args, but caching disabled
33+
assert result2 == "result_a"
34+
assert call_count == 2 # Function called again
35+
36+
# Now enable caching
37+
cachier.enable_caching()
38+
39+
# Function should now be cached
40+
result3 = test_func("a") # Same args, caching now enabled
41+
assert result3 == "result_a"
42+
assert call_count == 3 # Called once more to populate cache
43+
44+
result4 = test_func("a") # Same args, should come from cache
45+
assert result4 == "result_a"
46+
assert call_count == 3 # Not called again - came from cache!
47+
48+
# Different args should still call the function
49+
result5 = test_func("b")
50+
assert result5 == "result_b"
51+
assert call_count == 4
52+
53+
# Same new args should be cached
54+
result6 = test_func("b")
55+
assert result6 == "result_b"
56+
assert call_count == 4 # Not called again - came from cache!
57+
58+
59+
def test_disable_caching_after_decorator_definition():
60+
"""Test that disable_caching() affects decorators when caching enabled."""
61+
# Start with caching enabled
62+
cachier.enable_caching()
63+
64+
call_count = 0
65+
66+
# Use memory backend to avoid file cache persistence issues
67+
@cachier.cachier(backend="memory")
68+
def test_func(param):
69+
nonlocal call_count
70+
call_count += 1
71+
return f"result_{param}"
72+
73+
# Caching is enabled, so function should be cached
74+
result1 = test_func("a")
75+
assert result1 == "result_a"
76+
assert call_count == 1
77+
78+
result2 = test_func("a") # Same args, should come from cache
79+
assert result2 == "result_a"
80+
assert call_count == 1 # Not called again
81+
82+
# Now disable caching
83+
cachier.disable_caching()
84+
85+
# Function should no longer use cache
86+
result3 = test_func("a") # Same args, but caching now disabled
87+
assert result3 == "result_a"
88+
assert call_count == 2 # Function called again
89+
90+
result4 = test_func("a") # Same args, caching still disabled
91+
assert result4 == "result_a"
92+
assert call_count == 3 # Called again
93+
94+
95+
def test_original_issue_scenario():
96+
"""Test the exact scenario from the GitHub issue."""
97+
# Set up the same initial state as the issue
98+
cachier.set_global_params(caching_enabled=False, separate_files=True)
99+
100+
class Test:
101+
def __init__(self, cache_ttl=None):
102+
self.counter = 0
103+
if cache_ttl is not None:
104+
stale_after = datetime.timedelta(seconds=cache_ttl)
105+
cachier.set_global_params(stale_after=stale_after)
106+
cachier.enable_caching()
107+
108+
# Use memory backend to avoid file cache persistence issues
109+
@cachier.cachier(backend="memory")
110+
def test(self, param):
111+
self.counter += 1
112+
return param
113+
114+
# This should work without assertion error
115+
t = Test(cache_ttl=1)
116+
result1 = t.test("a")
117+
assert result1 == "a"
118+
assert t.counter == 1
119+
120+
result2 = t.test("a") # Should come from cache
121+
assert result2 == "a"
122+
assert t.counter == 1 # Counter should not increment
123+
124+
125+
if __name__ == "__main__":
126+
test_enable_caching_after_decorator_definition()
127+
test_disable_caching_after_decorator_definition()
128+
test_original_issue_scenario()
129+
print("All tests passed!")

0 commit comments

Comments
 (0)