from __future__ import annotations import os import threading import redis from app.config import config _CLIENTS: dict[tuple[str, bool], redis.Redis] = {} _LOCK = threading.Lock() def _safe_int(raw_value: object, default: int, minimum: int) -> int: try: parsed = int(raw_value) except Exception: parsed = default return max(minimum, parsed) def _safe_float(raw_value: object, default: float, minimum: float) -> float: try: parsed = float(raw_value) except Exception: parsed = default return max(minimum, parsed) def _pool_options(role: str, *, decode_responses: bool) -> dict: normalized_role = str(role or "standard").strip().lower() or "standard" node_code = str(getattr(config, "NODE_CODE", "") or "").strip() or "unknown" if normalized_role == "pubsub": return { "host": config.REDIS_HOST, "port": config.REDIS_PORT, "password": config.REDIS_PASSWORD or None, "db": config.REDIS_DB, "decode_responses": decode_responses, "socket_connect_timeout": _safe_float( os.getenv("DOMAINCHECK_REDIS_PUBSUB_CONNECT_TIMEOUT", "30"), 30.0, 1.0, ), "socket_timeout": _safe_float( os.getenv("DOMAINCHECK_REDIS_PUBSUB_SOCKET_TIMEOUT", "60"), 60.0, 1.0, ), "health_check_interval": _safe_int( os.getenv("DOMAINCHECK_REDIS_HEALTH_CHECK_INTERVAL", "30"), 30, 0, ), "retry_on_timeout": True, "max_connections": _safe_int( os.getenv("DOMAINCHECK_REDIS_PUBSUB_MAX_CONNECTIONS", "2"), 2, 1, ), "timeout": _safe_float( os.getenv("DOMAINCHECK_REDIS_PUBSUB_POOL_TIMEOUT", "5"), 5.0, 0.1, ), "client_name": f"domaincheck:pubsub:{node_code}:{os.getpid()}", } return { "host": config.REDIS_HOST, "port": config.REDIS_PORT, "password": config.REDIS_PASSWORD or None, "db": config.REDIS_DB, "decode_responses": decode_responses, "socket_connect_timeout": _safe_float( os.getenv("DOMAINCHECK_REDIS_CONNECT_TIMEOUT", "5"), 5.0, 0.5, ), "socket_timeout": _safe_float( os.getenv("DOMAINCHECK_REDIS_SOCKET_TIMEOUT", "10"), 10.0, 0.5, ), "health_check_interval": _safe_int( os.getenv("DOMAINCHECK_REDIS_HEALTH_CHECK_INTERVAL", "30"), 30, 0, ), "retry_on_timeout": True, "max_connections": _safe_int( os.getenv("DOMAINCHECK_REDIS_MAX_CONNECTIONS", "12"), 12, 1, ), "timeout": _safe_float( os.getenv("DOMAINCHECK_REDIS_POOL_TIMEOUT", "1.5"), 1.5, 0.1, ), "client_name": f"domaincheck:standard:{node_code}:{os.getpid()}", } def get_redis_client(*, role: str = "standard", decode_responses: bool = True) -> redis.Redis: normalized_role = str(role or "standard").strip().lower() or "standard" cache_key = (normalized_role, bool(decode_responses)) with _LOCK: cached = _CLIENTS.get(cache_key) if cached is not None: return cached pool = redis.BlockingConnectionPool(**_pool_options(normalized_role, decode_responses=decode_responses)) client = redis.Redis(connection_pool=pool) _CLIENTS[cache_key] = client return client def reset_redis_clients_for_tests() -> None: with _LOCK: clients = list(_CLIENTS.values()) _CLIENTS.clear() for client in clients: try: client.close() except Exception: pass