import os import json import asyncio from typing import Optional, Any, Callable, Awaitable from datetime import timedelta REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379/0") CACHE_TTL = int(os.getenv("CACHE_TTL", "300")) _redis_pool = None _lock = asyncio.Lock() async def get_redis(): global _redis_pool if _redis_pool is None: import aioredis async with _lock: if _redis_pool is None: _redis_pool = await aioredis.from_url(REDIS_URL, decode_responses=True) return _redis_pool async def cache_get(key: str) -> Optional[Any]: try: r = await get_redis() val = await r.get(key) if val: return json.loads(val) except Exception: pass return None async def cache_set(key: str, value: Any, ttl: int = CACHE_TTL): try: r = await get_redis() await r.setex(key, ttl, json.dumps(value, default=str)) except Exception: pass async def cache_delete(key: str): try: r = await get_redis() await r.delete(key) except Exception: pass async def cache_get_or_set(key: str, factory: Callable[[], Awaitable[Any]], ttl: int = CACHE_TTL) -> Any: cached = await cache_get(key) if cached is not None: return cached value = await factory() await cache_set(key, value, ttl) return value async def cache_invalidate_pattern(pattern: str): try: r = await get_redis() keys = await r.keys(pattern) if keys: await r.delete(*keys) except Exception: pass