| """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 |
| """ |
| |
| 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): |
| |
| cache_key = f"{key_prefix}:{func.__name__}:{self._generate_key(*args, **kwargs)}" |
|
|
| |
| cached_value = self.get(cache_key) |
| if cached_value is not None: |
| return cached_value |
|
|
| |
| result = func(*args, **kwargs) |
| self.set(cache_key, result) |
| return result |
|
|
| return wrapper |
| return decorator |
|
|
|
|
| |
| cache_manager = CacheManager() |
|
|