"""Redis-Client für TimeMaster (sync + async, für Kiosk-Nonce-Cache, Sessions, Locks).""" from __future__ import annotations import logging from typing import Optional log = logging.getLogger(__name__) _redis_client = None _async_redis_client = None def get_redis_client(): """Gibt den (sync) Redis-Client zurück oder None wenn nicht konfiguriert/erreichbar.""" global _redis_client if _redis_client is not None: return _redis_client try: import redis as redis_lib from app.core.config import settings url = getattr(settings, "redis_url", "redis://localhost:6379/0") _redis_client = redis_lib.from_url(url, decode_responses=True, socket_connect_timeout=2) # Verbindung testen _redis_client.ping() log.info("Redis-Verbindung hergestellt: %s", url) return _redis_client except Exception as exc: log.warning("Redis nicht verfügbar: %s", exc) return None def get_async_redis(): """Gepoolter async Redis-Client (eine Verbindungspool-Instanz pro Prozess). Nicht pro Request neu verbinden/schließen (frühere Falle in totp_login und kiosk_security._check_and_set_nonce) – der Pool verwaltet Connections selbst. Verbindungsfehler zeigen sich erst beim ersten Call (kein Ping hier), Aufrufer müssen weiterhin except behandeln (Nonce-Fallback, TOTP-Lockout). """ global _async_redis_client if _async_redis_client is None: import redis.asyncio as aioredis from app.core.config import settings url = getattr(settings, "redis_url", "redis://localhost:6379/0") _async_redis_client = aioredis.from_url(url, decode_responses=True) return _async_redis_client async def close_async_redis() -> None: """Pool sauber schließen – im FastAPI-Lifespan-Shutdown aufrufen.""" global _async_redis_client if _async_redis_client is not None: await _async_redis_client.aclose() _async_redis_client = None