"""轻量 Redis 缓存工具(替代 fastapi-cache2,兼容 redis-py)""" import hashlib import json from collections.abc import Callable from functools import wraps from typing import Any from redis.asyncio.client import Redis from starlette.responses import Response _ENABLE: bool = True _EXPIRE: int = 300 _PREFIX: str = "fastapi-admin-cache" _REDIS: Redis | None = None async def init(redis: Redis, prefix: str = "fastapi-admin-cache", expire: int = 300, enable: bool = True) -> None: global _REDIS, _PREFIX, _EXPIRE, _ENABLE _REDIS = redis _PREFIX = prefix _EXPIRE = expire _ENABLE = enable def _build_key(namespace: str, func: Callable, *args: Any, **kwargs: Any) -> str: raw = f"{func.__module__}:{func.__qualname__}:{args}:{kwargs}" return f"{_PREFIX}:{namespace}:{hashlib.md5(raw.encode()).hexdigest()}" def cache(expire: int | None = None, namespace: str = "default"): def decorator(func: Callable): @wraps(func) async def wrapper(*args: Any, **kwargs: Any) -> Any: if not _ENABLE or _REDIS is None: return await func(*args, **kwargs) key = _build_key(namespace, func, *args, **kwargs) cached = await _REDIS.get(key) if cached: data = json.loads(cached) return Response( content=json.dumps(data, ensure_ascii=False), media_type="application/json; charset=utf-8", status_code=200, ) result = await func(*args, **kwargs) # 提取 Response 对象的 body 进行缓存 if isinstance(result, Response): body = result.body else: body = json.dumps(result).encode() await _REDIS.set(key, body, ex=expire or _EXPIRE) return result return wrapper return decorator async def clear(namespace: str | None = None) -> None: if _REDIS is None: return pattern = f"{_PREFIX}:{namespace}:*" if namespace else f"{_PREFIX}:*" keys = [key async for key in _REDIS.scan_iter(match=pattern)] if keys: await _REDIS.delete(*keys)