CiteScan / src /core /cache.py
aivolcano
FastAPI + Gradio + src
3d83b62
"""Cache management for CiteScan."""
import hashlib
import json
from typing import Any, Callable, Optional
from functools import wraps
from cachetools import TTLCache
import threading
from .config import settings
from .logging import get_logger
logger = get_logger(__name__)
class CacheManager:
"""Thread-safe cache manager using TTLCache."""
def __init__(self):
"""Initialize cache manager."""
self._cache: Optional[TTLCache] = None
self._lock = threading.Lock()
self._initialize_cache()
def _initialize_cache(self) -> None:
"""Initialize the cache based on settings."""
if settings.cache_enabled:
self._cache = TTLCache(
maxsize=settings.cache_max_size,
ttl=settings.cache_ttl
)
logger.info(
f"Cache initialized: max_size={settings.cache_max_size}, "
f"ttl={settings.cache_ttl}s"
)
else:
logger.info("Cache disabled")
def _generate_key(self, *args, **kwargs) -> str:
"""Generate a cache key from arguments.
Args:
*args: Positional arguments
**kwargs: Keyword arguments
Returns:
Cache key as hex string
"""
# Create a stable string representation
key_data = {
"args": args,
"kwargs": sorted(kwargs.items())
}
key_str = json.dumps(key_data, sort_keys=True, default=str)
return hashlib.md5(key_str.encode()).hexdigest()
def get(self, key: str) -> Optional[Any]:
"""Get value from cache.
Args:
key: Cache key
Returns:
Cached value or None if not found
"""
if not settings.cache_enabled or self._cache is None:
return None
with self._lock:
value = self._cache.get(key)
if value is not None:
logger.debug(f"Cache hit: {key}")
return value
def set(self, key: str, value: Any) -> None:
"""Set value in cache.
Args:
key: Cache key
value: Value to cache
"""
if not settings.cache_enabled or self._cache is None:
return
with self._lock:
self._cache[key] = value
logger.debug(f"Cache set: {key}")
def delete(self, key: str) -> None:
"""Delete value from cache.
Args:
key: Cache key
"""
if not settings.cache_enabled or self._cache is None:
return
with self._lock:
if key in self._cache:
del self._cache[key]
logger.debug(f"Cache deleted: {key}")
def clear(self) -> None:
"""Clear all cache entries."""
if not settings.cache_enabled or self._cache is None:
return
with self._lock:
self._cache.clear()
logger.info("Cache cleared")
def get_stats(self) -> dict[str, Any]:
"""Get cache statistics.
Returns:
Dictionary with cache stats
"""
if not settings.cache_enabled or self._cache is None:
return {
"enabled": False,
"size": 0,
"max_size": 0,
"ttl": 0
}
with self._lock:
return {
"enabled": True,
"size": len(self._cache),
"max_size": self._cache.maxsize,
"ttl": self._cache.ttl
}
def cached(self, key_prefix: str = "") -> Callable:
"""Decorator to cache function results.
Args:
key_prefix: Optional prefix for cache key
Returns:
Decorator function
"""
def decorator(func: Callable) -> Callable:
@wraps(func)
def wrapper(*args, **kwargs):
# Generate cache key
cache_key = f"{key_prefix}:{func.__name__}:{self._generate_key(*args, **kwargs)}"
# Try to get from cache
cached_value = self.get(cache_key)
if cached_value is not None:
return cached_value
# Call function and cache result
result = func(*args, **kwargs)
self.set(cache_key, result)
return result
return wrapper
return decorator
# Global cache manager instance
cache_manager = CacheManager()