"""Async lifespan helper to initialize both an asyncio Redis client and a synchronous Redis client and attach them to app.state.redis (async) and app.state.redis_sync (sync). This lets synchronous request handlers use the sync client while async handlers use the asyncio client. """ import os from contextlib import asynccontextmanager import redis.asyncio as aioredis import redis as redis_sync from fastapi import FastAPI import logging logger = logging.getLogger("app.redis") @asynccontextmanager async def lifespan(app: FastAPI): redis_url = os.getenv("REDIS_URL") redis_host = os.getenv("REDIS_HOST", "127.0.0.1") redis_port = int(os.getenv("REDIS_PORT", "6379")) redis_db = int(os.getenv("REDIS_DB", "0")) redis_password = os.getenv("REDIS_PASSWORD", None) # Create both async and sync clients if redis_url: async_client = aioredis.from_url(redis_url, decode_responses=True) sync_client = redis_sync.from_url(redis_url, decode_responses=True) else: async_client = aioredis.Redis(host=redis_host, port=redis_port, db=redis_db, password=redis_password, decode_responses=True) sync_client = redis_sync.Redis(host=redis_host, port=redis_port, db=redis_db, password=redis_password, decode_responses=True) try: # Verify connectivity for both clients await async_client.ping() try: sync_client.ping() except Exception: # If sync ping fails, log but still keep async client if it's ok logger.warning("Sync Redis ping failed during startup", exc_info=True) app.state.redis = async_client app.state.redis_sync = sync_client logger.info("Redis clients initialized and attached to app.state.redis (async) and app.state.redis_sync (sync) (host=%s port=%s db=%s)", redis_host, redis_port, redis_db) except Exception as e: logger.error("Failed to initialize Redis clients: %s", e, exc_info=True) app.state.redis = None app.state.redis_sync = None try: yield finally: # Close async client try: if getattr(app.state, "redis", None) is not None: await app.state.redis.close() logger.info("Async Redis client closed on shutdown") except Exception: logger.error("Error closing async Redis client", exc_info=True) # Close sync client try: if getattr(app.state, "redis_sync", None) is not None: try: app.state.redis_sync.close() except Exception: # Older redis-py may not have close(); disconnect pool as fallback try: app.state.redis_sync.connection_pool.disconnect() except Exception: pass logger.info("Sync Redis client closed on shutdown") except Exception: logger.error("Error closing sync Redis client", exc_info=True)