|
|
import redis |
|
|
import logging |
|
|
import time |
|
|
import re |
|
|
from typing import Optional |
|
|
from utils.config import config |
|
|
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
class RedisClient: |
|
|
"""Enhanced Redis client with proper connection handling""" |
|
|
|
|
|
_instance = None |
|
|
_redis_client = None |
|
|
|
|
|
def __new__(cls): |
|
|
if cls._instance is None: |
|
|
cls._instance = super(RedisClient, cls).__new__(cls) |
|
|
return cls._instance |
|
|
|
|
|
def __init__(self): |
|
|
if not hasattr(self, '_initialized'): |
|
|
self._initialized = True |
|
|
self._connect() |
|
|
|
|
|
def _connect(self): |
|
|
"""Establish Redis connection with proper error handling for Redis Cloud""" |
|
|
logger.info(f"Attempting Redis connection with:") |
|
|
logger.info(f" Host: {config.redis_host}") |
|
|
logger.info(f" Port: {config.redis_port}") |
|
|
logger.info(f" Username: {'SET' if config.redis_username else 'NOT SET'}") |
|
|
logger.info(f" Password: {'SET' if config.redis_password else 'NOT SET'}") |
|
|
|
|
|
if not config.redis_host or config.redis_host == "localhost": |
|
|
logger.info("Redis not configured, skipping connection") |
|
|
return None |
|
|
|
|
|
try: |
|
|
host, port = self._parse_host_port(config.redis_host, config.redis_port) |
|
|
logger.info(f"Connecting to Redis Cloud at {host}:{port} with SSL") |
|
|
|
|
|
|
|
|
self._redis_client = redis.Redis( |
|
|
host=host, |
|
|
port=port, |
|
|
username=config.redis_username if config.redis_username else "default", |
|
|
password=config.redis_password, |
|
|
decode_responses=True, |
|
|
socket_connect_timeout=10, |
|
|
socket_timeout=10, |
|
|
ssl=True, |
|
|
ssl_cert_reqs=None, |
|
|
health_check_interval=30, |
|
|
retry_on_timeout=True |
|
|
) |
|
|
|
|
|
|
|
|
self._redis_client.ping() |
|
|
logger.info("Successfully connected to Redis Cloud with SSL") |
|
|
return |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"Redis Cloud SSL connection failed: {e}") |
|
|
self._redis_client = None |
|
|
|
|
|
def _parse_host_port(self, host_string: str, default_port: int) -> tuple: |
|
|
"""Parse host and port from host string""" |
|
|
if not host_string: |
|
|
return "localhost", default_port |
|
|
|
|
|
|
|
|
host_string = host_string.strip() |
|
|
host_string = re.sub(r'[\r\n\t\0]+', '', host_string) |
|
|
|
|
|
|
|
|
if ':' in host_string and not host_string.startswith('['): |
|
|
|
|
|
parts = host_string.rsplit(':', 1) |
|
|
try: |
|
|
host = parts[0] |
|
|
port = int(parts[1]) |
|
|
|
|
|
if 1 <= port <= 65535: |
|
|
return host, port |
|
|
else: |
|
|
|
|
|
return host_string, default_port |
|
|
except ValueError: |
|
|
|
|
|
return host_string, default_port |
|
|
|
|
|
return host_string, default_port |
|
|
|
|
|
def get_client(self) -> Optional[redis.Redis]: |
|
|
"""Get Redis client instance""" |
|
|
return self._redis_client |
|
|
|
|
|
def is_healthy(self) -> bool: |
|
|
"""Check if Redis connection is healthy""" |
|
|
if not self._redis_client: |
|
|
return False |
|
|
try: |
|
|
self._redis_client.ping() |
|
|
return True |
|
|
except: |
|
|
return False |
|
|
|
|
|
def reconnect(self): |
|
|
"""Reconnect to Redis""" |
|
|
self._connect() |
|
|
|
|
|
|
|
|
redis_client = RedisClient() |
|
|
|