""" High-performance in-memory caching module with LRU eviction policy. """ import time from typing import Any, Callable, Optional, Dict, List, Tuple import threading import functools import logging # Set up logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class CacheEntry: """Represents a single cache entry with metadata.""" __slots__ = ('value', 'timestamp', 'expires_at', 'hits') def __init__(self, value: Any, timeout: int): self.value = value self.timestamp = time.time() self.expires_at = self.timestamp + timeout self.hits = 0 def is_expired(self) -> bool: """Check if the cache entry has expired.""" return time.time() >= self.expires_at def hit(self) -> None: """Increment the hit counter.""" self.hits += 1 class FastMemoryCache: """ High-performance in-memory cache with LRU eviction policy. Thread-safe and optimized for frequent reads. """ def __init__(self, max_size: int = 1000, default_timeout: int = 300): """ Initialize the cache. Args: max_size: Maximum number of items to store in cache default_timeout: Default expiration time in seconds """ self.max_size = max_size self.default_timeout = default_timeout self._cache: Dict[str, CacheEntry] = {} self._lock = threading.RLock() self._hits = 0 self._misses = 0 self._evictions = 0 # Start background cleaner thread self._cleaner_thread = threading.Thread(target=self._clean_expired, daemon=True) self._cleaner_thread.start() def get(self, key: str) -> Optional[Any]: """ Get a value from the cache. Args: key: Cache key Returns: Cached value or None if not found/expired """ with self._lock: entry = self._cache.get(key) if entry is None: self._misses += 1 return None if entry.is_expired(): del self._cache[key] self._misses += 1 self._evictions += 1 return None entry.hit() self._hits += 1 return entry.value def set(self, key: str, value: Any, timeout: Optional[int] = None) -> None: """ Set a value in the cache. Args: key: Cache key value: Value to cache timeout: Optional timeout in seconds (uses default if None) """ if timeout is None: timeout = self.default_timeout with self._lock: # Evict if cache is full (LRU policy) if len(self._cache) >= self.max_size and key not in self._cache: self._evict_lru() self._cache[key] = CacheEntry(value, timeout) def delete(self, key: str) -> bool: """ Delete a key from the cache. Args: key: Cache key to delete Returns: True if key was deleted, False if not found """ with self._lock: if key in self._cache: del self._cache[key] self._evictions += 1 return True return False def clear(self) -> None: """Clear all items from the cache.""" with self._lock: self._cache.clear() self._evictions += len(self._cache) def _evict_lru(self) -> None: """Evict the least recently used item from the cache.""" if not self._cache: return # Find the entry with the fewest hits (simplified LRU) lru_key = min(self._cache.keys(), key=lambda k: self._cache[k].hits) del self._cache[lru_key] self._evictions += 1 def _clean_expired(self) -> None: """Background thread to clean expired entries.""" while True: time.sleep(60) # Clean every minute with self._lock: expired_keys = [ key for key, entry in self._cache.items() if entry.is_expired() ] for key in expired_keys: del self._cache[key] self._evictions += 1 if expired_keys: logger.info(f"Cleaned {len(expired_keys)} expired cache entries") def get_stats(self) -> Dict[str, Any]: """ Get cache statistics. Returns: Dictionary with cache statistics """ with self._lock: return { 'size': len(self._cache), 'hits': self._hits, 'misses': self._misses, 'hit_ratio': self._hits / (self._hits + self._misses) if (self._hits + self._misses) > 0 else 0, 'evictions': self._evictions, 'max_size': self.max_size } def keys(self) -> List[str]: """Get all cache keys.""" with self._lock: return list(self._cache.keys()) # Global cache instance cache = FastMemoryCache(max_size=2000, default_timeout=300) def cached(timeout: Optional[int] = None, unless: Optional[Callable] = None): """ Decorator for caching function results. Args: timeout: Cache timeout in seconds unless: Callable that returns True to bypass cache """ def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): # Bypass cache if unless condition is met if unless and unless(): return func(*args, **kwargs) # Create cache key from function name and arguments key_parts = [func.__module__, func.__name__] key_parts.extend(str(arg) for arg in args) key_parts.extend(f"{k}={v}" for k, v in sorted(kwargs.items())) key = "|".join(key_parts) # Try to get from cache cached_result = cache.get(key) if cached_result is not None: logger.info(f"Cache hit for {func.__name__}") return cached_result # Call function and cache result result = func(*args, **kwargs) cache.set(key, result, timeout) logger.info(f"Cache miss for {func.__name__}, caching result") return result return wrapper return decorator def cache_clear() -> None: """Clear the entire cache.""" cache.clear() logger.info("Cache cleared") def cache_stats() -> Dict[str, Any]: """Get cache statistics.""" return cache.get_stats()