Source code for merton.cache
"""Disk-backed memoization for expensive calibration outputs.
Wraps :class:`joblib.Memory` keyed on input array hashes. Off by default;
call :func:`enable` to opt in.
"""
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING
from joblib import Memory
from ._config import get_config
if TYPE_CHECKING:
from collections.abc import Callable
from typing import TypeVar
F = TypeVar("F", bound=Callable[..., object])
_memory: Memory | None = None
_enabled: bool = False
def _get_memory() -> Memory:
global _memory
if _memory is None:
cache_dir: Path = get_config().cache_dir / "joblib"
cache_dir.mkdir(parents=True, exist_ok=True)
_memory = Memory(location=str(cache_dir), verbose=0)
return _memory
[docs]
def enable(cache_dir: Path | str | None = None) -> None:
"""Turn on persistent calibration caching."""
global _enabled, _memory
if cache_dir is not None:
_memory = Memory(location=str(cache_dir), verbose=0)
_enabled = True
[docs]
def disable() -> None:
"""Turn off persistent calibration caching."""
global _enabled
_enabled = False
[docs]
def is_enabled() -> bool:
return _enabled
[docs]
def clear() -> None:
"""Drop the entire on-disk cache."""
_get_memory().clear(warn=False)
[docs]
def cached(func: F) -> F:
"""Decorator: cache the function's outputs on disk when caching is on.
No-op (returns the function unchanged) when caching is disabled, so users
pay zero overhead unless they opt in.
"""
def wrapper(*args: object, **kwargs: object) -> object:
if _enabled:
return _get_memory().cache(func)(*args, **kwargs)
return func(*args, **kwargs)
wrapper.__wrapped__ = func # type: ignore[attr-defined]
wrapper.__name__ = getattr(func, "__name__", "cached")
wrapper.__doc__ = func.__doc__
return wrapper # type: ignore[return-value]
__all__ = ["cached", "clear", "disable", "enable", "is_enabled"]