Skip to content

Commit 45f3a6e

Browse files
authored
Merge pull request #152 from Krukov/use-result-in-ttl
use a result of call in cache ttl def #149
2 parents 200665a + bff301d commit 45f3a6e

File tree

9 files changed

+90
-47
lines changed

9 files changed

+90
-47
lines changed

cashews/decorators/cache/hit.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,19 @@ def _decor(func: AsyncCallable_T) -> AsyncCallable_T:
4848

4949
@wraps(func)
5050
async def _wrap(*args, **kwargs):
51-
_ttl = ttl_to_seconds(ttl, *args, **kwargs, with_callable=True)
5251
_cache_key = get_cache_key(func, _key_template, args, kwargs)
5352
_tags = [get_cache_key(func, tag, args, kwargs) for tag in tags]
5453

55-
call_args = (func, args, kwargs, backend, _cache_key, _ttl, condition, _tags)
54+
call_args = (func, args, kwargs, backend, _cache_key, ttl, condition, _tags)
5655

5756
cached, hits = await asyncio.gather(
5857
backend.get(_cache_key, default=_empty),
59-
backend.incr(_cache_key + ":counter", expire=_ttl, tags=_tags),
58+
backend.incr(_cache_key + ":counter", expire=ttl, tags=_tags),
6059
)
6160
if cached is not _empty and hits and hits <= cache_hits:
6261
context_cache_detect._set(
6362
_cache_key,
64-
ttl=_ttl,
63+
ttl=ttl,
6564
cache_hits=cache_hits,
6665
name="hit",
6766
template=_key_template,
@@ -84,6 +83,7 @@ async def _get_and_save(func, args, kwargs, backend: "Cache", key: Key, ttl, sto
8483
_exc = exc
8584
result = exc
8685

86+
ttl = ttl_to_seconds(ttl, *args, result=result, **kwargs, with_callable=True)
8787
cond_result = store(result, args, kwargs, key=key)
8888
to_cache = None
8989
if isinstance(cond_result, bool) and cond_result and not isinstance(result, Exception):

cashews/decorators/cache/simple.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,12 @@ def _decor(func: AsyncCallable_T) -> AsyncCallable_T:
4444

4545
@wraps(func)
4646
async def _wrap(*args, **kwargs):
47-
_ttl = ttl_to_seconds(ttl, *args, **kwargs, with_callable=True)
4847
_tags = [get_cache_key(func, tag, args, kwargs) for tag in tags]
4948
_cache_key = get_cache_key(func, _key_template, args, kwargs)
5049

5150
cached = await backend.get(_cache_key, default=_empty)
5251
if cached is not _empty:
53-
context_cache_detect._set(_cache_key, ttl=_ttl, name="simple", template=_key_template)
52+
context_cache_detect._set(_cache_key, ttl=ttl, name="simple", template=_key_template)
5453
return return_or_raise(cached)
5554
_exc = None
5655
try:
@@ -59,6 +58,7 @@ async def _wrap(*args, **kwargs):
5958
_exc = exc
6059
result = exc
6160

61+
_ttl = ttl_to_seconds(ttl, *args, **kwargs, result=result, with_callable=True)
6262
cond_result = condition(result, args, kwargs, key=_cache_key)
6363
if isinstance(cond_result, bool) and cond_result and not isinstance(result, Exception):
6464
await backend.set(_cache_key, result, expire=_ttl, tags=_tags)

cashews/decorators/cache/soft.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,16 @@ def _decor(func: AsyncCallable_T) -> AsyncCallable_T:
5151

5252
@wraps(func)
5353
async def _wrap(*args, **kwargs):
54-
_ttl = ttl_to_seconds(ttl, *args, **kwargs, with_callable=True)
5554
_tags = [get_cache_key(func, tag, args, kwargs) for tag in tags]
56-
_soft_ttl = ttl_to_seconds(soft_ttl, *args, **kwargs) or _ttl * 0.33
5755
_cache_key = get_cache_key(func, _key_template, args, kwargs)
5856
cached = await backend.get(_cache_key, default=_empty)
5957
if cached is not _empty:
6058
soft_expire_at, result = cached
6159
if soft_expire_at > datetime.utcnow():
6260
context_cache_detect._set(
6361
_cache_key,
64-
ttl=_ttl,
65-
soft_ttl=_soft_ttl,
62+
ttl=ttl,
63+
soft_ttl=soft_ttl,
6664
name="soft",
6765
template=_key_template,
6866
)
@@ -75,15 +73,17 @@ async def _wrap(*args, **kwargs):
7573
_, result = cached
7674
context_cache_detect._set(
7775
_cache_key,
78-
ttl=_ttl,
79-
soft_ttl=_soft_ttl,
76+
ttl=ttl,
77+
soft_ttl=soft_ttl,
8078
name="soft",
8179
template=_key_template,
8280
)
8381
return result
8482
raise
8583
else:
8684
if condition(result, args, kwargs, _cache_key):
85+
_ttl = ttl_to_seconds(ttl, *args, result=result, **kwargs, with_callable=True)
86+
_soft_ttl = ttl_to_seconds(soft_ttl, *args, result=result, **kwargs) or _ttl * 0.33
8787
soft_expire_at = datetime.utcnow() + timedelta(seconds=_soft_ttl)
8888
await backend.set(_cache_key, [soft_expire_at, result], expire=_ttl, tags=_tags)
8989
return result

cashews/ttl.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
from cashews._typing import TTL
55

66

7-
def ttl_to_seconds(ttl: Union[float, None, TTL], *args, with_callable=False, **kwargs) -> Union[int, None, float]:
7+
def ttl_to_seconds(
8+
ttl: Union[float, None, TTL], *args, with_callable=False, result=None, **kwargs
9+
) -> Union[int, None, float]:
810
if ttl is None:
911
return None
1012
_type = type(ttl)
@@ -15,7 +17,11 @@ def ttl_to_seconds(ttl: Union[float, None, TTL], *args, with_callable=False, **k
1517
if _type == str:
1618
return _ttl_from_str(ttl)
1719
if callable(ttl) and with_callable:
18-
return ttl_to_seconds(ttl(*args, **kwargs))
20+
try:
21+
ttl = ttl(*args, result=result, **kwargs)
22+
except TypeError:
23+
ttl = ttl(*args, **kwargs)
24+
return ttl_to_seconds(ttl)
1925
return ttl
2026

2127

examples/condition.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import asyncio
22

3-
from cashews import NOT_NONE, cache
3+
from cashews import NOT_NONE, cache, with_exceptions
44

55
cache.setup("redis://")
66

@@ -21,6 +21,11 @@ async def cache_only_none_more_1000(foo):
2121
return foo if foo < 100 else None
2222

2323

24+
@cache(ttl="10m", condition=with_exceptions())
25+
async def cache_exception():
26+
raise Exception()
27+
28+
2429
@cache(ttl="10m", time_condition="1s")
2530
async def time_condition(foo):
2631
await asyncio.sleep(foo)

examples/more_decorators.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ async def function_with_limit(foo):
1616

1717
@cache.failover(ttl="1m", prefix="fast:failover")
1818
@cache.failover(ttl="10m", exceptions=(CircuitBreakerOpen,))
19-
@cache.circuit_breaker(errors_rate=10, period="10m", ttl="5m", half_open_ttl="1m")
19+
@cache.circuit_breaker(errors_rate=10, period="10m", ttl="5m", half_open_ttl="1m", min_calls=4)
2020
async def function_that_may_fail(foo):
2121
await asyncio.sleep(0.1)
2222
if random.choice([0, 0, 0, 1]):

examples/simple.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ async def basic():
1010
await cache.clear()
1111

1212
await cache.set("key", 1)
13-
print(await cache.get("key"))
1413
assert await cache.get("key") == 1
1514
await cache.set("key1", value={"any": True}, expire="1m")
1615

@@ -36,6 +35,7 @@ async def basic():
3635
print("Locked: ", await cache.is_locked("lock"))
3736

3837
print("Ping: ", await cache.ping()) # -> bytes
38+
print("Count: ", await cache.get_keys_count())
3939

4040
await cache.close()
4141

tests/test_backend_commands.py

+31-30
Original file line numberDiff line numberDiff line change
@@ -3,51 +3,52 @@
33

44
import pytest
55

6-
from cashews.backends.interface import NOT_EXIST, UNLIMITED, Backend
6+
from cashews import Cache
7+
from cashews.backends.interface import NOT_EXIST, UNLIMITED
78
from cashews.backends.memory import Memory
89

910
pytestmark = pytest.mark.asyncio
1011
VALUE = Decimal("100.2")
1112

1213

13-
async def test_set_get(cache):
14+
async def test_set_get(cache: Cache):
1415
await cache.set("key", VALUE)
1516
assert await cache.get("key") == VALUE
1617

1718

18-
async def test_set_get_bytes(cache):
19+
async def test_set_get_bytes(cache: Cache):
1920
await cache.set("key", b"10")
2021
assert await cache.get("key") == b"10"
2122

2223

23-
async def test_incr_get(cache):
24+
async def test_incr_get(cache: Cache):
2425
await cache.incr("key")
2526
assert await cache.get("key") == 1
2627

2728

28-
async def test_incr_value_get(cache):
29+
async def test_incr_value_get(cache: Cache):
2930
await cache.incr("key")
3031
await cache.incr("key", 2)
3132
assert await cache.get("key") == 3
3233

3334

34-
async def test_incr_expire(cache):
35+
async def test_incr_expire(cache: Cache):
3536
await cache.incr("key", expire=10)
3637
assert await cache.get_expire("key") == 10
3738

3839

39-
async def test_set_get_many(cache):
40+
async def test_set_get_many(cache: Cache):
4041
await cache.set("key", VALUE)
4142
assert await cache.get_many("key", "no_exists") == (VALUE, None)
4243

4344

44-
async def test_diff_types_get_many(cache):
45+
async def test_diff_types_get_many(cache: Cache):
4546
await cache.incr("key")
4647
await cache.incr_bits("key2", 0)
4748
assert await cache.get_many("key", "key2") == (1, None)
4849

4950

50-
async def test_set_exist(cache):
51+
async def test_set_exist(cache: Cache):
5152
assert await cache.set("key", "value")
5253
assert await cache.set("key", VALUE, exist=True)
5354
assert await cache.get("key") == VALUE
@@ -62,49 +63,49 @@ async def test_set_exist(cache):
6263
assert await cache.get("key2") == "value"
6364

6465

65-
async def test_set_many(cache):
66+
async def test_set_many(cache: Cache):
6667
await cache.set_many({"key1": VALUE, "key2": "value2"}, expire=1)
6768
assert await cache.get("key1", VALUE)
6869
assert await cache.get("key2", "value2")
6970

7071

71-
async def test_get_no_value(cache):
72+
async def test_get_no_value(cache: Cache):
7273
assert await cache.get("key2") is None
7374
assert await cache.get("key2", default=VALUE) is VALUE
7475

7576

76-
async def test_incr(cache):
77+
async def test_incr(cache: Cache):
7778
assert await cache.incr("incr") == 1
7879
assert await cache.incr("incr") == 2
7980
assert await cache.incr("incr", 5) == 7
8081
assert await cache.get("incr") == 7
8182

8283

83-
async def test_incr_set(cache):
84+
async def test_incr_set(cache: Cache):
8485
await cache.set("incr", "test")
8586
with pytest.raises(Exception):
8687
assert await cache.incr("incr") == 1
8788
assert await cache.get("incr") == "test"
8889

8990

90-
async def test_ping(cache):
91+
async def test_ping(cache: Cache):
9192
assert await cache.ping() == b"PONG"
9293

9394

94-
async def test_exists(cache):
95+
async def test_exists(cache: Cache):
9596
assert not await cache.exists("not")
9697
await cache.set("yes", VALUE)
9798
assert await cache.exists("yes")
9899

99100

100-
async def test_expire(cache):
101+
async def test_expire(cache: Cache):
101102
await cache.set("key", VALUE, expire=0.01)
102103
assert await cache.get("key") == VALUE
103104
await asyncio.sleep(0.01)
104105
assert await cache.get("key") is None
105106

106107

107-
async def test_get_set_expire(cache):
108+
async def test_get_set_expire(cache: Cache):
108109
assert await cache.get_expire("key") == NOT_EXIST
109110
await cache.set("key", VALUE)
110111
assert await cache.get("key") == VALUE
@@ -113,7 +114,7 @@ async def test_get_set_expire(cache):
113114
assert await cache.get_expire("key") == 1
114115

115116

116-
async def test_delete_many(cache: Backend):
117+
async def test_delete_many(cache: Cache):
117118
await cache.set("key1", VALUE)
118119
await cache.set("key2", VALUE)
119120
await cache.set("key3", VALUE)
@@ -125,7 +126,7 @@ async def test_delete_many(cache: Backend):
125126
assert await cache.get("key3") == VALUE
126127

127128

128-
async def test_delete_match(cache: Backend):
129+
async def test_delete_match(cache: Cache):
129130
await cache.set("pref:test:test", VALUE)
130131
await cache.set("pref:value:test", b"value2")
131132
await cache.set("pref:-:test", b"-")
@@ -145,7 +146,7 @@ async def test_delete_match(cache: Backend):
145146
assert await cache.get("pref:test:tests") is not None
146147

147148

148-
async def test_scan(cache: Backend):
149+
async def test_scan(cache: Cache):
149150
await cache.set("pref:test:test", VALUE)
150151
await cache.set("pref:value:test", b"value2")
151152
await cache.set("pref:-:test", b"-")
@@ -160,7 +161,7 @@ async def test_scan(cache: Backend):
160161
assert set(keys) == {"pref:test:test", "pref:value:test", "pref:-:test", "pref:*:test"}
161162

162163

163-
async def test_get_match(cache: Backend):
164+
async def test_get_match(cache: Cache):
164165
await cache.set("pref:test:test", VALUE)
165166
await cache.set("pref:value:test", b"value2")
166167
await cache.set("pref:-:test", b"-")
@@ -182,39 +183,39 @@ async def test_get_match(cache: Backend):
182183
assert len(match) == 0
183184

184185

185-
async def test_set_lock_unlock(cache: Backend):
186+
async def test_set_lock_unlock(cache: Cache):
186187
await cache.set_lock("lock", "lock", 10)
187188
assert await cache.is_locked("lock")
188189
await cache.unlock("lock", "lock")
189190
assert not await cache.is_locked("lock")
190191

191192

192-
async def test_lock(cache: Backend):
193+
async def test_lock(cache: Cache):
193194
async with cache.lock("lock", 10):
194195
assert await cache.is_locked("lock")
195196

196197
assert not await cache.is_locked("lock")
197198

198199

199-
async def test_diff_types_get_match(cache):
200+
async def test_diff_types_get_match(cache: Cache):
200201
await cache.incr("key")
201202
await cache.incr_bits("key2", 0)
202203
match = [(key, value) async for key, value in cache.get_match("*")]
203204
assert len(match) == 1, match
204205
assert dict(match) == {"key": 1}
205206

206207

207-
async def test_get_size(cache: Backend):
208+
async def test_get_size(cache: Cache):
208209
await cache.set("test", b"1")
209210
assert isinstance(await cache.get_size("test"), int)
210211

211212

212-
async def test_get_keys_count(cache: Backend):
213+
async def test_get_keys_count(cache: Cache):
213214
await cache.set("test", b"1")
214215
assert await cache.get_keys_count() == 1
215216

216217

217-
async def test_get_bits(cache: Backend):
218+
async def test_get_bits(cache: Cache):
218219
assert await cache.get_bits("test", 0, 2, 10, 50000, size=1) == (0, 0, 0, 0)
219220
assert await cache.get_bits("test", 0, 1, 3, size=15) == (
220221
0,
@@ -223,17 +224,17 @@ async def test_get_bits(cache: Backend):
223224
)
224225

225226

226-
async def test_incr_bits(cache: Backend):
227+
async def test_incr_bits(cache: Cache):
227228
await cache.incr_bits("test", 0, 1, 4)
228229
assert await cache.get_bits("test", 0, 1, 2, 3, 4) == (1, 1, 0, 0, 1)
229230

230231

231-
async def test_bits_size(cache: Backend):
232+
async def test_bits_size(cache: Cache):
232233
await cache.incr_bits("test", 0, 1, 4, size=5, by=3)
233234
assert await cache.get_bits("test", 0, 1, 2, 3, 4, size=5) == (3, 3, 0, 0, 3)
234235

235236

236-
async def test_slice_incr(cache: Backend):
237+
async def test_slice_incr(cache: Cache):
237238
assert await cache.slice_incr("test", 0, 5, maxvalue=10) == 1
238239
assert await cache.slice_incr("test", 1, 6, maxvalue=10) == 2
239240
assert await cache.slice_incr("test", 2, 7, maxvalue=10) == 3

0 commit comments

Comments
 (0)