Skip to content

Commit a72adb6

Browse files
CopilotBorda
andcommitted
Address PR review feedback: fix test assertions, add allow_none test, improve error handling, document async limitations
Co-authored-by: Borda <6035284+Borda@users.noreply.github.com>
1 parent 496cb20 commit a72adb6

3 files changed

Lines changed: 86 additions & 11 deletions

File tree

examples/async_example.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ async def fetch_github_user(username: str) -> dict:
8282
if duration2 > 0:
8383
print(f" Cache speedup: {duration1 / duration2:.1f}x")
8484
else:
85-
print(
86-
" Cache speedup: instantaneous (duration too small to measure)"
87-
)
85+
msg = " Cache speedup: instantaneous "
86+
msg += "(duration too small to measure)"
87+
print(msg)
8888

8989
except ImportError:
9090
msg = " (Skipping - httpx not installed. "
@@ -109,7 +109,10 @@ async def main():
109109
user = await fetch_user_data(42)
110110
duration2 = time.time() - start
111111
print(f"Second call: {user} (took {duration2:.2f}s)")
112-
print(f"Speedup: {duration1 / duration2:.1f}x faster!")
112+
if duration2 > 0:
113+
print(f"Speedup: {duration1 / duration2:.1f}x faster!")
114+
else:
115+
print("Speedup: instantaneous (duration too small to measure)")
113116

114117
# Example 2: Memory backend
115118
print("\n=== Example 2: Memory Backend (Fast, Non-Persistent) ===")

src/cachier/core.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ async def _function_thread_async(core, key, func, args, kwds):
6161
try:
6262
func_res = await func(*args, **kwds)
6363
core.set_entry(key, func_res)
64-
except BaseException as exc:
64+
except Exception as exc:
6565
print(f"Function call failed with the following exception:\n{exc}")
6666

6767

@@ -422,6 +422,10 @@ def _call(*args, max_age: Optional[timedelta] = None, **kwds):
422422
async def _call_async(
423423
*args, max_age: Optional[timedelta] = None, **kwds
424424
):
425+
# NOTE: For async functions, wait_for_calc_timeout is not honored.
426+
# Instead of blocking the event loop waiting for concurrent
427+
# calculations, async functions will recalculate in parallel.
428+
# This avoids deadlocks and maintains async efficiency.
425429
nonlocal allow_none, last_cleanup
426430
_allow_none = _update_with_defaults(allow_none, "allow_none", kwds)
427431
# print('Inside async wrapper for {}.'.format(func.__name__))

tests/test_async_core.py

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,11 @@ async def async_func(x):
8787
assert result1 == 10
8888
assert call_count == 1
8989

90-
# Second call - should use cache
90+
# Second call - should use cache (no additional call)
91+
call_count_before = call_count
9192
result2 = await async_func(5)
9293
assert result2 == 10
93-
assert call_count == 1
94+
assert call_count == call_count_before # Verify cache was used
9495

9596
# Wait for cache to become stale
9697
await asyncio.sleep(1.5)
@@ -127,10 +128,11 @@ async def async_func(x):
127128
assert result1 == 10
128129
assert call_count == 1
129130

130-
# Second call - should use cache
131+
# Second call - should use cache (no additional call)
132+
call_count_before = call_count
131133
result2 = await async_func(5)
132134
assert result2 == 10
133-
assert call_count == 1
135+
assert call_count == call_count_before # Verify cache was used
134136

135137
# Wait for cache to become stale
136138
await asyncio.sleep(1.5)
@@ -329,10 +331,11 @@ async def async_func(x):
329331
assert result1 == 10
330332
assert call_count == 1
331333

332-
# Second call - should use cache
334+
# Second call - should use cache (no additional call)
335+
call_count_before = call_count
333336
result2 = await async_func(5)
334337
assert result2 == 10
335-
assert call_count == 1
338+
assert call_count == call_count_before # Verify cache was used
336339

337340
# Wait a bit
338341
await asyncio.sleep(0.5)
@@ -391,3 +394,68 @@ async def async_func(x):
391394
assert call_count == 0 # No new calls, all from cache
392395

393396
async_func.clear_cache()
397+
398+
399+
# Test async with allow_none parameter
400+
@pytest.mark.memory
401+
@pytest.mark.asyncio
402+
async def test_async_allow_none():
403+
"""Test async caching with allow_none parameter."""
404+
call_count = 0
405+
406+
# Test with allow_none=False (default)
407+
@cachier(backend="memory")
408+
async def async_func_no_none(x):
409+
nonlocal call_count
410+
call_count += 1
411+
await asyncio.sleep(0.1)
412+
return None if x == 0 else x * 2
413+
414+
async_func_no_none.clear_cache()
415+
call_count = 0
416+
417+
# First call returning None - should not be cached
418+
result1 = await async_func_no_none(0)
419+
assert result1 is None
420+
assert call_count == 1
421+
422+
# Second call with same args - should recalculate (None not cached)
423+
result2 = await async_func_no_none(0)
424+
assert result2 is None
425+
assert call_count == 2
426+
427+
# Call with non-None result - should be cached
428+
result3 = await async_func_no_none(5)
429+
assert result3 == 10
430+
assert call_count == 3
431+
432+
# Call again - should use cache
433+
result4 = await async_func_no_none(5)
434+
assert result4 == 10
435+
assert call_count == 3 # No additional call
436+
437+
async_func_no_none.clear_cache()
438+
439+
# Test with allow_none=True
440+
call_count = 0
441+
442+
@cachier(backend="memory", allow_none=True)
443+
async def async_func_with_none(x):
444+
nonlocal call_count
445+
call_count += 1
446+
await asyncio.sleep(0.1)
447+
return None if x == 0 else x * 2
448+
449+
async_func_with_none.clear_cache()
450+
451+
# First call returning None - should be cached
452+
result1 = await async_func_with_none(0)
453+
assert result1 is None
454+
assert call_count == 1
455+
456+
# Second call with same args - should use cached None
457+
result2 = await async_func_with_none(0)
458+
assert result2 is None
459+
assert call_count == 1 # No additional call
460+
461+
async_func_with_none.clear_cache()

0 commit comments

Comments
 (0)