Skip to content

Scoped cache

provide.foundation.utils.scoped_cache

TODO: Add module docstring.

Classes

ContextScopedCache

ContextScopedCache(name: str = 'cache')

Bases: Generic[K, V]

Thread-safe, async-safe cache scoped to context managers.

Unlike global LRU caches (for memoization), this provides isolated cache instances per execution context - ideal for recursive operations that need temporary storage without memory leaks.

The cache uses ContextVar for automatic thread/async isolation, and context managers for automatic cleanup. Nested contexts reuse the parent's cache to maintain consistency within an operation.

Examples:

>>> cache = ContextScopedCache[str, int]("user_ids")
>>>
>>> with cache.scope():
...     cache.set("alice", 1)
...     cache.set("bob", 2)
...     print(cache.get("alice"))  # 1
...
>>> # Cache is automatically cleared when exiting scope
>>> with cache.scope():
...     print(cache.get("alice"))  # None (fresh scope)

Nested contexts reuse parent cache:

>>> with cache.scope():
...     cache.set("key", "outer")
...     with cache.scope():
...         print(cache.get("key"))  # "outer" (same cache)
...         cache.set("key", "inner")
...     print(cache.get("key"))  # "inner" (modified in nested scope)

Initialize a context-scoped cache.

Parameters:

Name Type Description Default
name str

Identifier for the cache (used in ContextVar name)

'cache'
Source code in provide/foundation/utils/scoped_cache.py
def __init__(self, name: str = "cache") -> None:
    """Initialize a context-scoped cache.

    Args:
        name: Identifier for the cache (used in ContextVar name)
    """
    self._context_var: ContextVar[dict[K, V] | None] = ContextVar(name, default=None)
    self.name = name
Functions
clear
clear() -> None

Clear current context's cache.

Raises:

Type Description
RuntimeError

If called outside a cache scope

Source code in provide/foundation/utils/scoped_cache.py
def clear(self) -> None:
    """Clear current context's cache.

    Raises:
        RuntimeError: If called outside a cache scope
    """
    cache = self._context_var.get()
    if cache is None:
        raise RuntimeError(f"Cache '{self.name}' accessed outside scope context")
    cache.clear()
contains
contains(key: K) -> bool

Check if key exists in current context's cache.

Parameters:

Name Type Description Default
key K

Cache key to check

required

Returns:

Type Description
bool

True if key exists in cache

Raises:

Type Description
RuntimeError

If called outside a cache scope

Source code in provide/foundation/utils/scoped_cache.py
def contains(self, key: K) -> bool:
    """Check if key exists in current context's cache.

    Args:
        key: Cache key to check

    Returns:
        True if key exists in cache

    Raises:
        RuntimeError: If called outside a cache scope
    """
    cache = self._context_var.get()
    if cache is None:
        raise RuntimeError(f"Cache '{self.name}' accessed outside scope context")
    return key in cache
get
get(key: K, default: V | None = None) -> V | None

Get value from current context's cache.

Parameters:

Name Type Description Default
key K

Cache key

required
default V | None

Value to return if key not found

None

Returns:

Type Description
V | None

Cached value or default

Raises:

Type Description
RuntimeError

If called outside a cache scope

Source code in provide/foundation/utils/scoped_cache.py
def get(self, key: K, default: V | None = None) -> V | None:
    """Get value from current context's cache.

    Args:
        key: Cache key
        default: Value to return if key not found

    Returns:
        Cached value or default

    Raises:
        RuntimeError: If called outside a cache scope
    """
    cache = self._context_var.get()
    if cache is None:
        raise RuntimeError(f"Cache '{self.name}' accessed outside scope context")
    return cache.get(key, default)
is_active
is_active() -> bool

Check if cache context is currently active.

Returns:

Type Description
bool

True if inside a cache scope, False otherwise

Source code in provide/foundation/utils/scoped_cache.py
def is_active(self) -> bool:
    """Check if cache context is currently active.

    Returns:
        True if inside a cache scope, False otherwise
    """
    return self._context_var.get() is not None
scope
scope() -> Generator[None]

Create an isolated cache scope.

If a cache context already exists (nested call), reuses the existing cache. Otherwise, creates a new cache and cleans it up on exit.

Yields:

Type Description
Generator[None]

None (use cache methods within the context)

Note

Cleanup is guaranteed even on errors.

Source code in provide/foundation/utils/scoped_cache.py
@contextmanager
def scope(self) -> Generator[None]:
    """Create an isolated cache scope.

    If a cache context already exists (nested call), reuses the
    existing cache. Otherwise, creates a new cache and cleans it
    up on exit.

    Yields:
        None (use cache methods within the context)

    Note:
        Cleanup is guaranteed even on errors.
    """
    if self._context_var.get() is None:
        # No existing cache - create new scope
        token = self._context_var.set({})
        try:
            yield
        finally:
            self._context_var.reset(token)
    else:
        # Reuse existing cache (nested scope)
        yield
set
set(key: K, value: V) -> None

Set value in current context's cache.

Parameters:

Name Type Description Default
key K

Cache key

required
value V

Value to cache

required

Raises:

Type Description
RuntimeError

If called outside a cache scope

Source code in provide/foundation/utils/scoped_cache.py
def set(self, key: K, value: V) -> None:
    """Set value in current context's cache.

    Args:
        key: Cache key
        value: Value to cache

    Raises:
        RuntimeError: If called outside a cache scope
    """
    cache = self._context_var.get()
    if cache is None:
        raise RuntimeError(f"Cache '{self.name}' accessed outside scope context")
    cache[key] = value
size
size() -> int

Get number of items in current context's cache.

Returns:

Type Description
int

Number of cached items

Raises:

Type Description
RuntimeError

If called outside a cache scope

Source code in provide/foundation/utils/scoped_cache.py
def size(self) -> int:
    """Get number of items in current context's cache.

    Returns:
        Number of cached items

    Raises:
        RuntimeError: If called outside a cache scope
    """
    cache = self._context_var.get()
    if cache is None:
        raise RuntimeError(f"Cache '{self.name}' accessed outside scope context")
    return len(cache)