from typing import Any, Dict, Optional, Callable, TypeVar, Awaitable from datetime import datetime, timedelta import hashlib import json # In-memory cache cache: Dict[str, Dict[str, Any]] = {} T = TypeVar("T") class Cache: @staticmethod def _generate_key(prefix: str, *args: Any, **kwargs: Any) -> str: """ Generate a unique cache key based on the function arguments. """ key_parts = [prefix] # Add positional args to key for arg in args: key_parts.append(str(arg)) # Add keyword args to key (sorted by key to ensure consistency) for k, v in sorted(kwargs.items()): key_parts.append(f"{k}={v}") # Join all parts and hash key_data = ":".join(key_parts) return hashlib.md5(key_data.encode()).hexdigest() @staticmethod def set(key: str, value: Any, expire_seconds: int = 1800) -> None: """ Store a value in the cache with an expiration time. """ expiry = datetime.utcnow() + timedelta(seconds=expire_seconds) cache[key] = { "value": value, "expiry": expiry } @staticmethod def get(key: str) -> Optional[Any]: """ Retrieve a value from the cache if it exists and hasn't expired. """ if key not in cache: return None cache_item = cache[key] if cache_item["expiry"] < datetime.utcnow(): # Remove expired item del cache[key] return None return cache_item["value"] @staticmethod def invalidate(key: str) -> None: """ Remove a specific item from the cache. """ if key in cache: del cache[key] @staticmethod def clear_all() -> None: """ Clear the entire cache. """ cache.clear() def cached(prefix: str, expire_seconds: int = 1800): """ Decorator for async functions to cache their results. Args: prefix: Prefix for the cache key to avoid collisions expire_seconds: Time in seconds until the cached result expires """ def decorator(func: Callable[..., Awaitable[T]]) -> Callable[..., Awaitable[T]]: async def wrapper(*args: Any, **kwargs: Any) -> T: # Generate a unique key based on function arguments key = Cache._generate_key(prefix, *args, **kwargs) # Check if we have a cached result cached_result = Cache.get(key) if cached_result is not None: return cached_result # Call the original function result = await func(*args, **kwargs) # Cache the result Cache.set(key, result, expire_seconds) return result return wrapper return decorator