Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions cashews/backends/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,7 @@ class ControlMixin:
enable_by_default = True

def __init__(self, *args, **kwargs) -> None:
self.__disable: ContextVar[set[Command]] = ContextVar(str(id(self)))
self.__disable.set(set())
self.__disable: ContextVar[set[Command]] = ContextVar(str(id(self)), default=set())
self._control_set = False
super().__init__(*args, **kwargs)

Expand Down
40 changes: 40 additions & 0 deletions tests/test_bugs.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import asyncio

from cashews import Cache
from cashews.commands import Command


async def test_issue_382():
Expand All @@ -15,3 +16,42 @@ def sync_get_item(item_id: int):
return asyncio.run(get_item(item_id))

await asyncio.get_running_loop().run_in_executor(None, sync_get_item, 123)


async def test_control_mixin_cross_context():
"""Test that ControlMixin's ContextVar works across different async contexts.

This simulates the FastAPI lifespan pattern where cache.setup() is called in one
context (lifespan) and cache operations are called in different contexts (HTTP requests).
"""
cache = Cache()

# Setup cache in one context (simulating FastAPI lifespan)
cache.setup("mem://")
cache.enable() # This calls self.__disable.get() internally

@cache(ttl=60, key="test:{value}")
async def cached_func(value: int):
return f"result_{value}"

# Test in a different async context (simulating HTTP request)
async def test_in_new_context():
# This should not raise LookupError when checking if cache is enabled/disabled
result = await cached_func(123)
assert result == "result_123"

# Test disable/enable in new context
cache.disable(Command.SET)
assert cache.is_disable(Command.SET)

cache.enable(Command.SET)
assert cache.is_enable(Command.SET)

return result

def sync_runner():
return asyncio.run(test_in_new_context())

# Run in executor with new event loop (different context)
result = await asyncio.get_running_loop().run_in_executor(None, sync_runner)
assert result == "result_123"
Loading