from typing import Optional, Dict, Any import json from datetime import datetime, timedelta import asyncio class MockCacheClient: """Mock cache client that simulates Redis behavior in memory""" def __init__(self): self._cache: Dict[str, Dict[str, Any]] = {} async def get(self, key: str) -> Optional[str]: """Get value from cache if not expired""" await asyncio.sleep(0.01) # Simulate network delay if key in self._cache: entry = self._cache[key] if entry['expires_at'] > datetime.utcnow(): return entry['value'] else: # Remove expired entry del self._cache[key] return None async def set(self, key: str, value: Any, ttl: int = 86400) -> bool: """Set value in cache with TTL in seconds""" await asyncio.sleep(0.01) # Simulate network delay self._cache[key] = { 'value': json.dumps(value) if not isinstance(value, str) else value, 'expires_at': datetime.utcnow() + timedelta(seconds=ttl), 'created_at': datetime.utcnow() } return True async def setex(self, key: str, ttl: int, value: Any) -> bool: """Set with explicit TTL (Redis compatibility)""" return await self.set(key, value, ttl) async def delete(self, key: str) -> bool: """Delete key from cache""" if key in self._cache: del self._cache[key] return True return False async def exists(self, key: str) -> bool: """Check if key exists and is not expired""" if key in self._cache: entry = self._cache[key] if entry['expires_at'] > datetime.utcnow(): return True else: del self._cache[key] return False def clear_all(self): """Clear entire cache (for testing)""" self._cache.clear() def get_stats(self) -> Dict[str, Any]: """Get cache statistics""" total_keys = len(self._cache) expired_keys = sum( 1 for entry in self._cache.values() if entry['expires_at'] <= datetime.utcnow() ) return { 'total_keys': total_keys, 'active_keys': total_keys - expired_keys, 'expired_keys': expired_keys, 'cache_size_bytes': sum( len(entry['value']) for entry in self._cache.values() ) }