Files
hms-licht-ton/backend/app/cache.py
T

100 lines
3.1 KiB
Python
Raw Normal View History

"""Redis cache helper for equipment data and rate limiting."""
import json
import logging
from typing import Any
import redis.asyncio as redis
from app.config import settings
logger = logging.getLogger(__name__)
_cache_client: redis.Redis | None = None
def _build_key(*parts: str) -> str:
"""Build a colon-separated Redis key."""
return ":".join(part.replace(":", "_") for part in parts)
async def get_cache() -> redis.Redis:
"""Return the Redis client singleton, creating it on first use."""
global _cache_client
if _cache_client is None:
_cache_client = redis.from_url(settings.REDIS_URL, decode_responses=True)
return _cache_client
async def set_cache(key: str, value: Any, ttl: int = 3600) -> None:
"""Store a JSON-serialisable value in Redis with a TTL."""
try:
client = await get_cache()
await client.setex(key, ttl, json.dumps(value))
except Exception as exc: # noqa: BLE001
logger.warning("Cache set failed for key=%s: %s", key, exc)
async def get_cached(key: str) -> Any | None:
"""Retrieve and JSON-deserialise a cached value."""
try:
client = await get_cache()
raw = await client.get(key)
if raw is None:
return None
return json.loads(raw)
except Exception as exc: # noqa: BLE001
logger.warning("Cache get failed for key=%s: %s", key, exc)
return None
async def delete_pattern(pattern: str) -> None:
"""Delete all keys matching a glob pattern."""
try:
client = await get_cache()
async for key in client.scan_iter(match=pattern, count=100):
await client.delete(key)
except Exception as exc: # noqa: BLE001
logger.warning("Cache delete_pattern failed for pattern=%s: %s", pattern, exc)
async def check_rate_limit(identifier: str, max_requests: int = 5, window: int = 60) -> bool:
"""Return True if the identifier is within the rate limit."""
try:
client = await get_cache()
key = _build_key("rate", identifier)
count = await client.incr(key)
if count == 1:
await client.expire(key, window)
return count <= max_requests
except Exception as exc: # noqa: BLE001
logger.warning("Rate limit check failed: %s", exc)
return True
async def ping_cache() -> bool:
"""Check if Redis is reachable."""
try:
client = await get_cache()
return bool(await client.ping())
except Exception:
return False
def equipment_list_key(page: int, category: str | None, sort: str | None, search: str | None) -> str:
"""Build the Redis cache key for an equipment list query."""
cat = category or "all"
srt = sort or "default"
srch = search or "all"
return _build_key("equipment", "list", str(page), cat, srt, srch)
def equipment_detail_key(item_id: int) -> str:
"""Build the Redis cache key for an equipment detail."""
return _build_key("equipment", "detail", str(item_id))
def equipment_categories_key() -> str:
"""Build the Redis cache key for the equipment categories list."""
return _build_key("equipment", "categories")