- Translated comments and docstrings from German to English for better clarity. - Improved logging consistency across various webhook handlers for create, delete, and update operations. - Centralized logging functionality by utilizing a dedicated logger utility. - Added new enums for file and XAI sync statuses in models. - Updated Redis client factory to use a centralized logger and improved error handling. - Enhanced API responses to include more descriptive messages and status codes.
203 lines
5.8 KiB
Python
203 lines
5.8 KiB
Python
"""
|
|
Redis Client Factory
|
|
|
|
Centralized Redis client management with:
|
|
- Singleton pattern
|
|
- Connection pooling
|
|
- Automatic reconnection
|
|
- Health checks
|
|
"""
|
|
|
|
import redis
|
|
import os
|
|
from typing import Optional
|
|
from services.exceptions import RedisConnectionError
|
|
from services.logging_utils import get_service_logger
|
|
|
|
|
|
class RedisClientFactory:
|
|
"""
|
|
Singleton factory for Redis clients.
|
|
|
|
Benefits:
|
|
- Centralized configuration
|
|
- Connection pooling
|
|
- Lazy initialization
|
|
- Better error handling
|
|
"""
|
|
|
|
_instance: Optional[redis.Redis] = None
|
|
_connection_pool: Optional[redis.ConnectionPool] = None
|
|
_logger = None
|
|
|
|
@classmethod
|
|
def _get_logger(cls):
|
|
"""Get logger instance (lazy initialization)"""
|
|
if cls._logger is None:
|
|
cls._logger = get_service_logger('redis_factory', None)
|
|
return cls._logger
|
|
|
|
@classmethod
|
|
def get_client(cls, strict: bool = False) -> Optional[redis.Redis]:
|
|
"""
|
|
Return Redis client (creates if needed).
|
|
|
|
Args:
|
|
strict: If True, raises exception on connection failures.
|
|
If False, returns None (for optional Redis usage).
|
|
|
|
Returns:
|
|
Redis client or None (if strict=False and connection fails)
|
|
|
|
Raises:
|
|
RedisConnectionError: If strict=True and connection fails
|
|
"""
|
|
logger = cls._get_logger()
|
|
if cls._instance is None:
|
|
try:
|
|
cls._instance = cls._create_client()
|
|
logger.info("Redis client created successfully")
|
|
except Exception as e:
|
|
logger.error(f"Failed to create Redis client: {e}")
|
|
if strict:
|
|
raise RedisConnectionError(
|
|
f"Could not connect to Redis: {e}",
|
|
operation="get_client"
|
|
)
|
|
logger.warning("Redis unavailable - continuing without caching")
|
|
return None
|
|
|
|
return cls._instance
|
|
|
|
@classmethod
|
|
def _create_client(cls) -> redis.Redis:
|
|
"""
|
|
Create new Redis client with connection pool.
|
|
|
|
Returns:
|
|
Configured Redis client
|
|
|
|
Raises:
|
|
redis.ConnectionError: On connection problems
|
|
"""
|
|
logger = cls._get_logger()
|
|
# Load configuration from environment
|
|
redis_host = os.getenv('REDIS_HOST', 'localhost')
|
|
redis_port = int(os.getenv('REDIS_PORT', '6379'))
|
|
redis_db = int(os.getenv('REDIS_DB_ADVOWARE_CACHE', '1'))
|
|
redis_timeout = int(os.getenv('REDIS_TIMEOUT_SECONDS', '5'))
|
|
redis_max_connections = int(os.getenv('REDIS_MAX_CONNECTIONS', '50'))
|
|
|
|
logger.info(
|
|
f"Creating Redis client: {redis_host}:{redis_port} "
|
|
f"(db={redis_db}, timeout={redis_timeout}s)"
|
|
)
|
|
|
|
# Create connection pool
|
|
if cls._connection_pool is None:
|
|
cls._connection_pool = redis.ConnectionPool(
|
|
host=redis_host,
|
|
port=redis_port,
|
|
db=redis_db,
|
|
socket_timeout=redis_timeout,
|
|
socket_connect_timeout=redis_timeout,
|
|
max_connections=redis_max_connections,
|
|
decode_responses=True # Auto-decode bytes to strings
|
|
)
|
|
|
|
# Create client from pool
|
|
client = redis.Redis(connection_pool=cls._connection_pool)
|
|
|
|
# Verify connection
|
|
client.ping()
|
|
|
|
return client
|
|
|
|
@classmethod
|
|
def reset(cls) -> None:
|
|
"""
|
|
Reset factory state (mainly for tests).
|
|
|
|
Closes existing connections and resets singleton.
|
|
"""
|
|
logger = cls._get_logger()
|
|
if cls._instance:
|
|
try:
|
|
cls._instance.close()
|
|
except Exception as e:
|
|
logger.warning(f"Error closing Redis client: {e}")
|
|
|
|
if cls._connection_pool:
|
|
try:
|
|
cls._connection_pool.disconnect()
|
|
except Exception as e:
|
|
logger.warning(f"Error closing connection pool: {e}")
|
|
|
|
cls._instance = None
|
|
cls._connection_pool = None
|
|
logger.info("Redis factory reset")
|
|
|
|
@classmethod
|
|
def health_check(cls) -> bool:
|
|
"""
|
|
Check Redis connection.
|
|
|
|
Returns:
|
|
True if Redis is reachable, False otherwise
|
|
"""
|
|
logger = cls._get_logger()
|
|
try:
|
|
client = cls.get_client(strict=False)
|
|
if client is None:
|
|
return False
|
|
|
|
client.ping()
|
|
return True
|
|
except Exception as e:
|
|
logger.warning(f"Redis health check failed: {e}")
|
|
return False
|
|
|
|
@classmethod
|
|
def get_info(cls) -> Optional[dict]:
|
|
"""
|
|
Return Redis server info (for monitoring).
|
|
|
|
Returns:
|
|
Redis info dict or None on error
|
|
"""
|
|
logger = cls._get_logger()
|
|
try:
|
|
client = cls.get_client(strict=False)
|
|
if client is None:
|
|
return None
|
|
|
|
return client.info()
|
|
except Exception as e:
|
|
logger.error(f"Failed to get Redis info: {e}")
|
|
return None
|
|
|
|
|
|
# ========== Convenience Functions ==========
|
|
|
|
def get_redis_client(strict: bool = False) -> Optional[redis.Redis]:
|
|
"""
|
|
Convenience function for Redis client.
|
|
|
|
Args:
|
|
strict: If True, raises exception on error
|
|
|
|
Returns:
|
|
Redis client or None
|
|
"""
|
|
return RedisClientFactory.get_client(strict=strict)
|
|
|
|
|
|
def is_redis_available() -> bool:
|
|
"""
|
|
Check if Redis is available.
|
|
|
|
Returns:
|
|
True if Redis is reachable
|
|
"""
|
|
return RedisClientFactory.health_check()
|