File size: 5,014 Bytes
637183f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | """
Redis-based caching for API responses.
"""
import json
import hashlib
import logging
from typing import Optional, Any
from functools import wraps
try:
import redis
import msgpack
REDIS_AVAILABLE = True
except ImportError:
REDIS_AVAILABLE = False
from core.config import settings
logger = logging.getLogger(__name__)
class ResponseCache:
"""Redis-based response cache with fallback to in-memory."""
def __init__(self):
self.redis_client = None
self.memory_cache = {}
self.enabled = settings.REDIS_ENABLED and REDIS_AVAILABLE
if self.enabled:
try:
self.redis_client = redis.Redis(
host=settings.REDIS_HOST,
port=settings.REDIS_PORT,
db=0,
decode_responses=False, # We'll use msgpack
socket_timeout=2,
socket_connect_timeout=2
)
# Test connection
self.redis_client.ping()
logger.info(f"✅ Redis cache enabled at {settings.REDIS_HOST}:{settings.REDIS_PORT}")
except Exception as e:
logger.warning(f"⚠️ Redis connection failed, using in-memory cache: {e}")
self.enabled = False
self.redis_client = None
else:
logger.info("📝 Using in-memory cache (Redis disabled)")
def _generate_key(self, prefix: str, *args, **kwargs) -> str:
"""Generate cache key from arguments."""
key_data = f"{prefix}:{args}:{sorted(kwargs.items())}"
return f"hfviz:{hashlib.md5(key_data.encode()).hexdigest()}"
def get(self, key: str) -> Optional[Any]:
"""Get value from cache."""
try:
if self.enabled and self.redis_client:
data = self.redis_client.get(key)
if data:
return msgpack.unpackb(data, raw=False)
else:
return self.memory_cache.get(key)
except Exception as e:
logger.warning(f"Cache get error: {e}")
return None
def set(self, key: str, value: Any, ttl: Optional[int] = None) -> bool:
"""Set value in cache with TTL."""
try:
ttl = ttl or settings.REDIS_TTL
if self.enabled and self.redis_client:
packed_data = msgpack.packb(value, use_bin_type=True)
self.redis_client.setex(key, ttl, packed_data)
return True
else:
# In-memory cache with simple TTL tracking
self.memory_cache[key] = value
# Limit in-memory cache size
if len(self.memory_cache) > 100:
# Remove oldest entry
self.memory_cache.pop(next(iter(self.memory_cache)))
return True
except Exception as e:
logger.warning(f"Cache set error: {e}")
return False
def delete(self, key: str) -> bool:
"""Delete key from cache."""
try:
if self.enabled and self.redis_client:
self.redis_client.delete(key)
else:
self.memory_cache.pop(key, None)
return True
except Exception as e:
logger.warning(f"Cache delete error: {e}")
return False
def clear(self, pattern: str = "hfviz:*") -> bool:
"""Clear all cache keys matching pattern."""
try:
if self.enabled and self.redis_client:
keys = self.redis_client.keys(pattern)
if keys:
self.redis_client.delete(*keys)
else:
self.memory_cache.clear()
return True
except Exception as e:
logger.warning(f"Cache clear error: {e}")
return False
# Global cache instance
cache = ResponseCache()
def cached_response(ttl: int = 300, key_prefix: str = "api"):
"""
Decorator for caching API responses.
Usage:
@cached_response(ttl=600, key_prefix="models")
async def get_models(...):
...
"""
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
# Generate cache key from function args
cache_key = cache._generate_key(key_prefix, func.__name__, *args, **kwargs)
# Try to get from cache
cached_data = cache.get(cache_key)
if cached_data is not None:
logger.debug(f"Cache HIT: {cache_key[:20]}...")
return cached_data
# Execute function
logger.debug(f"Cache MISS: {cache_key[:20]}...")
result = await func(*args, **kwargs)
# Cache result
cache.set(cache_key, result, ttl=ttl)
return result
return wrapper
return decorator
|