"""Shared async Redis client for Phase 3 (rate limits, job queue).""" from __future__ import annotations import logging from typing import TYPE_CHECKING from app.config import settings if TYPE_CHECKING: from redis.asyncio import Redis logger = logging.getLogger(__name__) _redis: Redis | None = None def redis_configured() -> bool: return bool((settings.redis_url or "").strip()) def job_queue_enabled() -> bool: return bool(settings.enable_job_queue and redis_configured()) async def _disconnect_redis() -> None: global _redis if _redis is not None: try: await _redis.aclose() except Exception: # noqa: BLE001 pass _redis = None async def get_redis() -> Redis: """Return a process-wide async Redis client (lazy connect, reconnect on failure).""" global _redis if _redis is not None: try: await _redis.ping() return _redis except Exception: # noqa: BLE001 logger.warning("Redis connection lost; reconnecting") await _disconnect_redis() if not redis_configured(): raise RuntimeError("Redis is not configured (set REDIS_URL)") try: from redis.asyncio import Redis except ImportError as exc: # pragma: no cover raise RuntimeError( "redis package is required for REDIS_URL. Install: pip install 'redis>=5.0.0'" ) from exc client = Redis.from_url( settings.redis_url, encoding="utf-8", decode_responses=True, ) try: await client.ping() except Exception as exc: # noqa: BLE001 await client.aclose() raise RuntimeError(f"Redis ping failed for {settings.redis_url!r}: {exc}") from exc _redis = client logger.info("Redis connected url=%s", settings.redis_url.split("@")[-1]) return _redis async def close_redis() -> None: await _disconnect_redis() async def redis_health_ok() -> bool: if not redis_configured(): return False try: client = await get_redis() await client.ping() return True except Exception: # noqa: BLE001 return False async def generation_queue_depth() -> int | None: """Length of the generation job queue, or None if Redis is unavailable.""" if not redis_configured(): return None try: client = await get_redis() return int(await client.llen(settings.job_queue_key)) except Exception: # noqa: BLE001 return None