62 lines
1.5 KiB
Python
62 lines
1.5 KiB
Python
"""Cache abstraction over Redis (section 21).
|
|||
|
|
|
||
|
|
Redis is disposable acceleration — every function here fails open: if Redis
|
||
|
|
is unreachable, reads report a cache miss and writes are silently skipped,
|
||
|
|
so the API keeps serving from PostgreSQL instead of failing.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import orjson
|
||
|
|
from redis.asyncio import Redis
|
||
|
|
|
||
|
|
from app.core.config import get_settings
|
||
|
|
|
||
|
|
settings = get_settings()
|
||
|
|
|
||
|
|
CACHE_VERSION = "v1"
|
||
|
|
|
||
|
|
|
||
|
|
def build_key(*parts: str) -> str:
|
||
|
|
return ":".join(("aurora", CACHE_VERSION, *parts))
|
||
|
|
|
||
|
|
|
||
|
|
async def cache_get_json(redis: Redis, key: str) -> dict | list | None:
|
||
|
|
try:
|
||
|
|
raw = await redis.get(key)
|
||
|
|
except Exception:
|
||
|
|
return None
|
||
|
|
if raw is None:
|
||
|
|
return None
|
||
|
|
try:
|
||
|
|
return orjson.loads(raw)
|
||
|
|
except orjson.JSONDecodeError:
|
||
|
|
return None
|
||
|
|
|
||
|
|
|
||
|
|
async def cache_set_json(
|
||
|
|
redis: Redis, key: str, value: dict | list, ttl: int | None = None
|
||
|
|
) -> None:
|
||
|
|
ttl = ttl if ttl is not None else settings.CACHE_DEFAULT_TTL
|
||
|
|
try:
|
||
|
|
await redis.set(key, orjson.dumps(value), ex=ttl)
|
||
|
|
except Exception:
|
||
|
|
return
|
||
|
|
|
||
|
|
|
||
|
|
async def cache_delete(redis: Redis, *keys: str) -> None:
|
||
|
|
if not keys:
|
||
|
|
return
|
||
|
|
try:
|
||
|
|
await redis.delete(*keys)
|
||
|
|
except Exception:
|
||
|
|
return
|
||
|
|
|
||
|
|
|
||
|
|
async def cache_delete_prefix(redis: Redis, prefix: str) -> None:
|
||
|
|
try:
|
||
|
|
async for key in redis.scan_iter(match=f"{prefix}*"):
|
||
|
|
await redis.delete(key)
|
||
|
|
except Exception:
|
||
|
|
return
|