""" Cache module - Redis with in-memory fallback for development """ import json import time from typing import Optional, Any from config import settings from logging_config import get_logger logger = get_logger("cache") # Try to import redis, fall back to in-memory if not available try: import redis REDIS_AVAILABLE = True except ImportError: REDIS_AVAILABLE = False class InMemoryCache: """Simple in-memory cache with TTL support for development""" def __init__(self): self._cache: dict[str, tuple[Any, float]] = {} def get(self, key: str) -> Optional[str]: if key in self._cache: value, expiry = self._cache[key] if time.time() < expiry: return value del self._cache[key] return None def set(self, key: str, value: str, ex: int = 10800) -> None: self._cache[key] = (value, time.time() + ex) def delete(self, key: str) -> None: self._cache.pop(key, None) def exists(self, key: str) -> bool: return self.get(key) is not None class CacheManager: """ Cache manager with Redis support and in-memory fallback. Default TTL: 3 hours (10800 seconds) """ def __init__(self): self.client = None self.is_redis = False self._connect() @property def default_ttl(self) -> int: """Get default TTL from settings""" return settings.cache_default_ttl def _connect(self): redis_url = settings.redis_url if REDIS_AVAILABLE: try: self.client = redis.from_url(redis_url, decode_responses=True) self.client.ping() self.is_redis = True logger.info("Connected to Redis") except Exception as e: logger.warning(f"Redis not available ({e}), using in-memory cache") self.client = InMemoryCache() else: logger.warning("Redis package not installed, using in-memory cache") self.client = InMemoryCache() def get(self, key: str) -> Optional[Any]: """Get cached data by key""" data = self.client.get(key) if data: try: return json.loads(data) except (json.JSONDecodeError, TypeError): return data return None def set(self, key: str, value: Any, ttl: int = None) -> None: """Cache data by key""" if isinstance(value, (dict, list)): value = json.dumps(value) self.client.set(key, value, ex=ttl or self.default_ttl) def invalidate(self, key: str) -> None: """Remove cached data""" self.client.delete(key) # Singleton instance cache = CacheManager()