AI-Life-Coach-Streamlit2 / core /redis_client.py
rdune71's picture
Fix Redis Cloud SSL connection, Streamlit version compatibility, and update requirements
4f1a78a
raw
history blame
4.04 kB
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")
# Redis Cloud specific connection settings
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, # Important for Redis Cloud
health_check_interval=30,
retry_on_timeout=True
)
# Test connection
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
# Remove any whitespace and control characters
host_string = host_string.strip()
host_string = re.sub(r'[\r\n\t\0]+', '', host_string)
# Handle case where port is included in REDIS_HOST (e.g., "host:port")
if ':' in host_string and not host_string.startswith('['): # Not IPv6
# Check if the part after : is a valid port number
parts = host_string.rsplit(':', 1)
try:
host = parts[0]
port = int(parts[1])
# Validate that this looks like a port (0-65535)
if 1 <= port <= 65535:
return host, port
else:
# Invalid port, use default
return host_string, default_port
except ValueError:
# Port is not a valid integer, use default
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()
# Global Redis client instance
redis_client = RedisClient()