import time import logging import threading from typing import Any, Dict, Optional logger = logging.getLogger("ifixkart.cache") class MemoryCache: """Thread-safe in-process memory cache with TTL (Time To Live).""" def __init__(self): self._cache: Dict[str, Dict[str, Any]] = {} self._lock = threading.Lock() def get(self, key: str) -> Optional[Any]: with self._lock: item = self._cache.get(key) if not item: return None if item["expire_at"] is not None and time.time() > item["expire_at"]: del self._cache[key] return None return item["value"] def set(self, key: str, value: Any, ttl_seconds: Optional[int] = None) -> None: expire_at = (time.time() + ttl_seconds) if ttl_seconds is not None else None with self._lock: self._cache[key] = { "value": value, "expire_at": expire_at } def delete(self, key: str) -> None: with self._lock: if key in self._cache: del self._cache[key] def invalidate_prefix(self, prefix: str) -> None: with self._lock: keys_to_del = [k for k in self._cache.keys() if k.startswith(prefix)] for k in keys_to_del: del self._cache[k] def clear(self) -> None: with self._lock: self._cache.clear() class CacheManager: """Consolidated cache interface with Redis connection fallback.""" def __init__(self): self.memory_cache = MemoryCache() self.redis_client = None self._try_init_redis() def _try_init_redis(self): try: import redis import os redis_host = os.getenv("REDIS_HOST", "127.0.0.1") redis_port = int(os.getenv("REDIS_PORT", 6379)) self.redis_client = redis.Redis( host=redis_host, port=redis_port, db=0, socket_connect_timeout=1, decode_responses=False ) self.redis_client.ping() logger.info("CacheManager: Redis cache connected successfully.") except Exception: self.redis_client = None logger.warning("CacheManager: Redis is offline or not installed. Falling back to In-Memory cache.") def get(self, key: str) -> Optional[Any]: if self.redis_client: try: import json data = self.redis_client.get(key) if data: return json.loads(data.decode("utf-8")) except Exception as e: logger.error(f"Redis get failed: {e}") return self.memory_cache.get(key) def set(self, key: str, value: Any, ttl_seconds: Optional[int] = None) -> None: if self.redis_client: try: import json serialized = json.dumps(value) self.redis_client.set(key, serialized, ex=ttl_seconds) return except Exception as e: logger.error(f"Redis set failed: {e}") self.memory_cache.set(key, value, ttl_seconds) def delete(self, key: str) -> None: if self.redis_client: try: self.redis_client.delete(key) except Exception as e: logger.error(f"Redis delete failed: {e}") self.memory_cache.delete(key) def invalidate_prefix(self, prefix: str) -> None: if self.redis_client: try: keys = self.redis_client.keys(f"{prefix}*") if keys: self.redis_client.delete(*keys) except Exception as e: logger.error(f"Redis prefix invalidation failed: {e}") self.memory_cache.invalidate_prefix(prefix) def clear(self) -> None: if self.redis_client: try: self.redis_client.flushdb() except Exception as e: logger.error(f"Redis flushdb failed: {e}") self.memory_cache.clear() cache = CacheManager()