Ir para o conteúdo

Cache

Primitivos de cache apoiados em Redis. Requer o extra [cache].

AsyncRedisManager

AsyncRedisManager embrulha redis.asyncio com a mesma superfície de connect/disconnect/health-check do AsyncDatabaseManager. Instale com [cache].

# src/api/app.py
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager

from fastapi import FastAPI

from tempest_fastapi_sdk import AsyncRedisManager
from src.core.settings import settings

cache = AsyncRedisManager(settings.REDIS_URL, decode_responses=True)


@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
    await cache.connect()           # primeira chamada — sem isso, .client levanta RuntimeError
    try:
        yield
    finally:
        await cache.disconnect()


app = FastAPI(lifespan=lifespan)

# Uso direto (dentro de um handler, depois do startup do lifespan)
await cache.client.set("user:123:name", "Ana", ex=300)
name = await cache.client.get("user:123:name")

# Dependência FastAPI — entrega o client ativo.
from fastapi import Depends
from redis.asyncio import Redis


@router.get("/cached")
async def cached_endpoint(
    redis: Redis = Depends(cache.client_dependency),
) -> dict[str, str]:
    value = await redis.get("greeting") or "hello"
    return {"value": value}

Conecte o health check no router canônico com make_health_router(checks={"redis": cache.health_check}) para que as readiness probes falhem quando o Redis cair.

Decorator @cached

@cached(redis, ttl=..., key_prefix=...) memoiza o resultado de uma função async no Redis. As chaves de cache são derivadas do __qualname__ da função mais um SHA-256 de args/kwargs; passe key_prefix= para dar namespace às entradas, de modo que a invalidação funcione por scan de prefixo.

from tempest_fastapi_sdk.cache import AsyncRedisManager, cached

from src.core.settings import settings


redis = AsyncRedisManager(settings.REDIS_URL)


@cached(redis, ttl=300, key_prefix="users:")
async def get_user_profile(user_id: str) -> dict[str, str]:
    """Hits Redis on warm cache; runs the body once every 5 minutes."""
    return await load_from_db(user_id)


# Pula o cache (leitura E escrita) seletivamente em algumas chamadas
@cached(
    redis,
    ttl=60,
    skip_cache=lambda args, kwargs: kwargs.get("fresh") is True,
)
async def list_orders(user_id: str, *, fresh: bool = False) -> list[dict]:
    ...

Defaults: ttl=300 segundos (0 desabilita a expiração), serializer=json.dumps / deserializer=json.loads. Sobrescreva serializer / deserializer para payloads não-JSON (modelos Pydantic — passe model_dump_json / MyModel.model_validate_json, ou use pickle.dumps / pickle.loads para objetos arbitrários). Valores corrompidos no cache caem de volta para rodar a função embrulhada e emitem um warning no logger do SDK.