first commit
This commit is contained in:
128
backend/app/core/cache.py
Normal file
128
backend/app/core/cache.py
Normal file
@@ -0,0 +1,128 @@
|
||||
"""Redis caching service"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from typing import Optional, Any
|
||||
|
||||
import redis
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Lazy Redis client initialization
|
||||
class _RedisClient:
|
||||
_client = None
|
||||
|
||||
@classmethod
|
||||
def get_client(cls):
|
||||
if cls._client is None:
|
||||
# Parse REDIS_URL or use default
|
||||
redis_url = settings.REDIS_URL
|
||||
if redis_url.startswith("redis://"):
|
||||
cls._client = redis.from_url(redis_url, decode_responses=True)
|
||||
else:
|
||||
cls._client = redis.Redis(
|
||||
host=settings.REDIS_SERVER,
|
||||
port=settings.REDIS_PORT,
|
||||
db=settings.REDIS_DB,
|
||||
decode_responses=True,
|
||||
)
|
||||
return cls._client
|
||||
|
||||
|
||||
class CacheService:
|
||||
"""Redis caching service with JSON serialization"""
|
||||
|
||||
def __init__(self):
|
||||
self.client = _RedisClient.get_client()
|
||||
|
||||
def get(self, key: str) -> Optional[Any]:
|
||||
"""Get value from cache"""
|
||||
try:
|
||||
value = self.client.get(key)
|
||||
if value:
|
||||
return json.loads(value)
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning(f"Cache get error: {e}")
|
||||
return None
|
||||
|
||||
def set(
|
||||
self,
|
||||
key: str,
|
||||
value: Any,
|
||||
expire_seconds: int = 300,
|
||||
) -> bool:
|
||||
"""Set value in cache with expiration"""
|
||||
try:
|
||||
serialized = json.dumps(value, default=str)
|
||||
return self.client.setex(key, expire_seconds, serialized)
|
||||
except Exception as e:
|
||||
logger.warning(f"Cache set error: {e}")
|
||||
return False
|
||||
|
||||
def delete(self, key: str) -> bool:
|
||||
"""Delete key from cache"""
|
||||
try:
|
||||
return self.client.delete(key) > 0
|
||||
except Exception as e:
|
||||
logger.warning(f"Cache delete error: {e}")
|
||||
return False
|
||||
|
||||
def delete_pattern(self, pattern: str) -> int:
|
||||
"""Delete all keys matching pattern"""
|
||||
try:
|
||||
keys = self.client.keys(pattern)
|
||||
if keys:
|
||||
return self.client.delete(*keys)
|
||||
return 0
|
||||
except Exception as e:
|
||||
logger.warning(f"Cache delete_pattern error: {e}")
|
||||
return 0
|
||||
|
||||
def get_or_set(
|
||||
self,
|
||||
key: str,
|
||||
fallback: callable,
|
||||
expire_seconds: int = 300,
|
||||
) -> Optional[Any]:
|
||||
"""Get value from cache or set it using fallback"""
|
||||
value = self.get(key)
|
||||
if value is not None:
|
||||
return value
|
||||
|
||||
value = fallback()
|
||||
if value is not None:
|
||||
self.set(key, value, expire_seconds)
|
||||
return value
|
||||
|
||||
def invalidate_pattern(self, pattern: str) -> int:
|
||||
"""Invalidate all keys matching pattern"""
|
||||
return self.delete_pattern(pattern)
|
||||
|
||||
|
||||
cache = CacheService()
|
||||
|
||||
|
||||
def cached(expire_seconds: int = 300, key_prefix: str = ""):
|
||||
"""Decorator for caching function results"""
|
||||
|
||||
def decorator(func):
|
||||
async def wrapper(*args, **kwargs):
|
||||
cache_key = f"{key_prefix}:{func.__name__}:{args}:{kwargs}"
|
||||
cache_key = cache_key.replace(":", "_").replace(" ", "")
|
||||
|
||||
cached_value = cache.get(cache_key)
|
||||
if cached_value is not None:
|
||||
return cached_value
|
||||
|
||||
result = await func(*args, **kwargs)
|
||||
cache.set(cache_key, result, expire_seconds)
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
Reference in New Issue
Block a user