""" Client Redis partagé — pool de connexions async pour ARQ et Pub/Sub. Inclut le parsing d'URL Redis centralisé pour éviter la duplication. """ from typing import Tuple, Optional from redis.asyncio import ConnectionPool, Redis from app.config import settings _pool: ConnectionPool | None = None def parse_redis_url(url: str | None = None) -> Tuple[str, int, Optional[str], int]: """ Parse une URL Redis en (host, port, password, database). Format accepté : redis://[user:password@]host[:port][/db] Retourne ('localhost', 6379, None, 0) si l'URL est vide. """ if url is None: url = settings.REDIS_URL if not url: return "localhost", 6379, None, 0 if url.startswith("redis://"): url = url[8:] if url.startswith("rediss://"): url = url[9:] password: Optional[str] = None if "@" in url: auth_part, url = url.rsplit("@", 1) if ":" in auth_part: password = auth_part.split(":", 1)[1] else: password = auth_part database = 0 if "/" in url: host_port, db_str = url.split("/", 1) if db_str: try: database = int(db_str) except ValueError: pass else: host_port = url host = "localhost" port = 6379 if ":" in host_port: host, port_str = host_port.rsplit(":", 1) if port_str: try: port = int(port_str) except ValueError: port = 6379 else: host = host_port or "localhost" return host, port, password, database async def get_redis_pool() -> Redis: """Retourne un client Redis avec pool de connexions partagé.""" global _pool if _pool is None: host, port, password, database = parse_redis_url() _pool = ConnectionPool.from_url( settings.REDIS_URL, max_connections=20, decode_responses=True, ) return Redis(connection_pool=_pool) async def close_redis_pool() -> None: """Ferme proprement le pool de connexions Redis.""" global _pool if _pool is not None: await _pool.disconnect() _pool = None