import redis.asyncio as redis import json import logging from typing import Any, Optional logger = logging.getLogger(__name__) class CacheService: """Redis cache service with JSON serialization""" def __init__(self, redis_client: Optional[redis.Redis], enabled: bool = True, default_ttl: int = 3600): self.redis = redis_client self.enabled = enabled and redis_client is not None self.default_ttl = default_ttl async def get(self, key: str) -> Optional[Any]: """Get value from cache""" if not self.enabled: return None try: value = await self.redis.get(key) if value: return json.loads(value) return None except Exception as e: logger.error(f"Cache get error for key {key}: {e}") return None async def set(self, key: str, value: Any, ttl: Optional[int] = None) -> bool: """Set value in cache""" if not self.enabled: return False try: ttl = ttl or self.default_ttl json_value = json.dumps(value, default=str) # Handle datetime objects await self.redis.setex(key, ttl, json_value) return True except Exception as e: logger.error(f"Cache set error for key {key}: {e}") return False async def delete(self, key: str) -> bool: """Delete key from cache""" if not self.enabled: return False try: deleted = await self.redis.delete(key) return deleted > 0 except Exception as e: logger.error(f"Cache delete error for key {key}: {e}") return False async def invalidate_dropdown_cache(self) -> int: """Invalidate all dropdown cache entries""" if not self.enabled: return 0 try: pattern = "dropdown:*" keys = await self.redis.keys(pattern) if keys: deleted = await self.redis.delete(*keys) logger.info(f"Invalidated {deleted} dropdown cache entries") return deleted return 0 except Exception as e: logger.error(f"Cache invalidation error: {e}") return 0 async def get_stats(self) -> dict: """Get cache statistics""" if not self.enabled: return {"enabled": False} try: info = await self.redis.info("memory") return { "enabled": True, "used_memory": info.get("used_memory_human"), "used_memory_peak": info.get("used_memory_peak_human"), "connected_clients": await self.redis.client_list() } except Exception as e: logger.error(f"Cache stats error: {e}") return {"enabled": True, "error": str(e)}