Judge / backend /app /cache.py
GonzaViss's picture
refactor(cache): make get_cached/set_cached synchronous
022aba6
Raw
History Blame Contribute Delete
2.45 kB
import hashlib
import json
import logging
logger = logging.getLogger(__name__)
_redis_client = None
def init_redis(url: str, token: str):
"""Create and store the Upstash Redis client. Call once at startup."""
global _redis_client
try:
from upstash_redis import Redis # type: ignore[import-untyped]
_redis_client = Redis(url=url, token=token)
logger.info("Upstash Redis client initialised.")
except Exception as e:
logger.warning("Redis init failed — cache disabled: %s", e)
_redis_client = None
def close_redis() -> None:
"""No-op for upstash-redis (HTTP client, no persistent connections)."""
global _redis_client
_redis_client = None
def make_cache_key(question: str, corpus_version: str, card_mentions: list[str] | None = None, prompt_version: str = "v1") -> str:
"""Derive a deterministic cache key.
Key = SHA-256({"q": normalize(question), "cv": corpus_version, "pv": prompt_version, "m": sorted(card_mentions)})
"""
normalized = question.strip().lower()
mentions = sorted(card_mentions) if card_mentions else []
payload = json.dumps(
{"q": normalized, "cv": corpus_version, "pv": prompt_version, "m": mentions},
ensure_ascii=False,
sort_keys=True,
)
return hashlib.sha256(payload.encode()).hexdigest()
def get_cached(key: str) -> str | None:
"""Return the cached JSON string for *key*, or None on miss/error.
Synchronous: the Upstash client is an HTTP client with no async API, so an
``async def`` here never yielded to the event loop — it only masked a
blocking call. The pipeline runs in FastAPI's threadpool (sync handler), so
a plain blocking call is correct and honest about what happens.
"""
if _redis_client is None:
return None
try:
value = _redis_client.get(key)
return value if isinstance(value, str) else None
except Exception as e:
logger.warning("Redis GET failed (cache miss): %s", e)
return None
def set_cached(key: str, value: str, ttl: int) -> None:
"""Store *value* under *key* with *ttl* seconds. No-op on error.
Synchronous — see get_cached for why the previous ``async`` was decorative.
"""
if _redis_client is None:
return
try:
_redis_client.set(key, value, ex=ttl)
except Exception as e:
logger.warning("Redis SET failed (caching skipped): %s", e)