|
|
@@ -0,0 +1,73 @@
|
|
|
+"""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)
|