Skip to content

Response cache

Cache, FastAPICache, the @cache decorator, key builders and coders. In fastapi_alcazaba.response_cache.

In the guide: Production setup, Configuration, Coders, Key builders. In an app: the example.

fastapi_alcazaba.response_cache.Cache

Cache(backend: Backend, prefix: str = '', expire: int | None = None, coder: type[Coder] = JsonCoder, key_builder: KeyBuilder = default_key_builder, cache_status: str | None = 'Alcazaba', enable: bool = True, *, signing_key: Secrets | None = None, encryption_key: Secrets | None = None, vary_headers: Iterable[str] = DEFAULT_VARY_HEADERS, respect_cache_control: bool = False, single_flight: bool = True, backend_timeout: float | None = 1.0, circuit_breaker_failures: int | None = 5, circuit_breaker_reset: float = 30.0, max_value_size: int | None = None, distributed_lock: bool = False, lock_timeout: float = 10.0, lock_wait: float = 5.0, early_refresh: float | None = None, compression: Codec | None = None, compression_min_size: int = 1024, compression_level: int | None = None, metrics: bool = True, meter_provider: Any = None, tracing: bool = True, tracer_provider: Any = None)

A cache: a backend and its settings. Create one per app, or several (e.g. a public one and an encrypted one for personal data), and decorate with cached(); or use the global FastAPICache with @cache.

Parameters:

Name Type Description Default
backend Backend

where entries are stored.

required
prefix str

prefix of every key.

''
expire int | None

default time to live, in seconds.

None
coder type[Coder]

how values are stored (JsonCoder by default).

JsonCoder
key_builder KeyBuilder

builds keys from the function and its arguments.

default_key_builder
cache_status str | None

this cache's name in the responses' Cache-Status header (RFC 9211): Alcazaba; hit; ttl=42 for a hit, else why the request reached the endpoint (fwd=miss, stale, request, method, bypass) and whether the result was stored. A token (letters, digits and !#$%&'*+-.^_|~:/`, starting with a letter); None for no header.

'Alcazaba'
enable bool

False turns the cache off.

True
signing_key Secrets | None

secret (at least 32 bytes; text, bytes, or pydantic's SecretStr or SecretBytes) used to sign every entry with HMAC-SHA256; entries without a valid signature are ignored. Strongly recommended when the backend is shared or reachable by others, and needed for PickleCoder. A list of keys rotates them: the first one signs, all of them are accepted.

None
encryption_key Secrets | None

like signing_key, but the entries are also encrypted (AES-256-GCM): a dump or a backup of the backend doesn't reveal the cached responses. Needs the crypto extra. Use one or the other, not both.

None
vary_headers Iterable[str]

request headers that give each value its own cache entry and make the response private (see DEFAULT_VARY_HEADERS). Pass () for endpoints whose response is the same for everybody.

DEFAULT_VARY_HEADERS
respect_cache_control bool

let clients skip the cache with Cache-Control: no-cache or no-store. Off by default: anyone could otherwise make every request reach the endpoint, which defeats the cache as a shield against load.

False
single_flight bool

concurrent misses for the same key wait for the first one instead of all computing the value (cache stampede protection), within each process.

True
backend_timeout float | None

seconds each backend call may take before the cache is skipped for that request; None waits as long as the client does.

1.0
circuit_breaker_failures int | None

consecutive backend failures after which the backend is not called for circuit_breaker_reset seconds (requests run uncached and don't wait for it); None disables the breaker.

5
max_value_size int | None

values larger than this many bytes (once encoded) aren't stored.

None
distributed_lock bool

on a miss, take a lock in the backend so that a single process computes the value while the others wait for it (up to lock_wait seconds, then they compute it themselves). The lock expires after lock_timeout seconds.

False
early_refresh float | None

fraction of the lifetime at its end in which a hit may recompute the value (more likely as the expiry nears), so that popular entries are refreshed before they expire instead of all callers missing at once. E.g. 0.1.

None
compression Codec | None

"zstd" (Python 3.14, or the zstd extra) or "zlib": entries of at least compression_min_size bytes are stored compressed when that makes them smaller, with compression_level (the codec's default if None). Compressed entries are read back whatever this setting, so it can be turned on and off freely.

None
metrics bool

record OpenTelemetry metrics (needs the otel extra).

True
meter_provider Any

OpenTelemetry meter provider; the global one by default.

None
tracing bool

record OpenTelemetry spans of cached calls and backend calls (otel extra).

True
tracer_provider Any

OpenTelemetry tracer provider; the global one by default.

None

cached

cached(expire: int | None = None, coder: type[Coder] | None = None, key_builder: KeyBuilder | None = None, namespace: str = '', injected_dependency_namespace: str = '__fastapi_alcazaba', **options: Any) -> _CacheDecorator

Like fastapi_alcazaba.response_cache.cache, with this cache instead of the global FastAPICache.

get_or_set async

get_or_set(key: str, compute: Callable[[], Awaitable[T]], *, expire: int | None = None, namespace: str = '', tags: Tags | None = None) -> T

The value cached under key, or else await compute(), stored. For caching inside an endpoint what doesn't fit a decorator. Same pipeline as cached() (signing, encryption, compression, single flight, locks); annotate compute's return type to get it back as that type. The key is hashed: user input in it never reaches the backend in clear.

state_key

state_key(kind: str, rest: str) -> str

Backend key of state other than cached responses: locks, rate-limit counters, revoked tokens, one-time codes, idempotency records. It goes under {prefix}!, not {prefix}:, so that clear() never frees a held lock, resets a rate limit or takes a revocation back, and InMemoryBackend keeps it apart from the entries it evicts.

tag_key

tag_key(tag: str) -> str

Backend key of a tag. The digest goes in {braces} (a cluster hash tag) so that the set and its temporary copy share a slot, whatever characters the tag has.

invalidate_tags async

invalidate_tags(*tags: str) -> int

Delete every entry stored with any of tags (see cached(tags=...)).

lock

lock(name: str, *, ttl: float = 30.0, wait: float | None = 0.0, renew: bool = True) -> Lock

A lock shared by every process using this backend (fastapi_alcazaba.background.Lock):

async with cache.lock("nightly-report", ttl=60):
    ...

ttl, wait and renew are those of Lock.

clear async

clear(namespace: str | None = None, key: str | None = None) -> int

Delete the entry key (a full cache key), or every entry of namespace, or of the whole prefix when neither is given. Cached responses only: locks, counters, revoked tokens and the like are kept apart (state_key).

close async

close() -> None

Close the backend if it holds resources (TieredBackend's listener, DynamoDB client).

install

install(app: Any) -> Cache

Make this cache the one get_cache returns for app.

fastapi_alcazaba.response_cache.get_cache

get_cache(request: HTTPConnection) -> Cache

FastAPI dependency: the cache installed on the app (Cache.install), else FastAPICache's. Works in WebSocket routes too.

@app.post("/products/{product_id}")
async def update(product_id: int, cache: Annotated[Cache, Depends(get_cache)]) -> None:
    await cache.invalidate_tags(f"product:{product_id}")

fastapi_alcazaba.response_cache.FastAPICache

The global cache that @cache uses: a Cache configured once with init(). For several caches, or one per app, create Cache instances instead.

init classmethod

init(backend: Backend, *args: Any, **kwargs: Any) -> None

Configure the global cache, with the arguments of Cache; later calls are ignored until reset().

invalidate_tags async classmethod

invalidate_tags(*tags: str) -> int

Delete every entry stored with any of tags (see @cache(tags=...)).

lock classmethod

lock(name: str, *, ttl: float = 30.0, wait: float | None = 0.0, renew: bool = True) -> Lock

A lock shared by every process (see Cache.lock).

clear async classmethod

clear(namespace: str | None = None, key: str | None = None) -> int

Delete the entry key (a full cache key), or every entry of namespace, or of the whole prefix when neither is given.

fastapi_alcazaba.response_cache.CacheOptions dataclass

CacheOptions(protector: EntryProtector | None = None, vary_headers: tuple[str, ...] = DEFAULT_VARY_HEADERS, respect_cache_control: bool = False, single_flight: bool = True, max_value_size: int | None = None, distributed_lock: bool = False, lock_timeout: float = 10.0, lock_wait: float = 5.0, early_refresh: float | None = None, compressor: Compressor | None = None)

How a cache protects, varies, locks, refreshes and compresses its entries.

fastapi_alcazaba.response_cache.cache

cache(expire: int | None = None, coder: type[Coder] | None = None, key_builder: KeyBuilder | None = None, namespace: str = '', injected_dependency_namespace: str = '__fastapi_alcazaba', *, exclude_params: Collection[str] | None = None, vary_headers: Iterable[str] | None = None, respect_cache_control: bool | None = None, stale_while_revalidate: int | None = None, single_flight: bool | None = None, max_value_size: int | None = None, distributed_lock: bool | None = None, early_refresh: float | None = None, tags: Tags | None = None, instance: Cache | None = None) -> _CacheDecorator

Cache the result of an endpoint, or of any function, sync or async.

Settings left as None use the ones given to FastAPICache.init().

Parameters:

Name Type Description Default
expire int | None

seconds to keep the entry.

None
coder type[Coder] | None

how the result is stored (JsonCoder by default).

None
key_builder KeyBuilder | None

builds the cache key from the function and its arguments.

None
namespace str

groups entries, to delete them together with FastAPICache.clear().

''
injected_dependency_namespace str

prefix of the request and response parameters added to the endpoint when it doesn't declare them.

'__fastapi_alcazaba'
exclude_params Collection[str] | None

arguments left out of the cache key: calls that only differ in them share an entry (e.g. a nonce, or a database session whose repr changes with each call).

None
vary_headers Iterable[str] | None

request headers that give each value its own entry and make the response private; () when the response is the same for everybody.

None
respect_cache_control bool | None

let clients skip the cache with Cache-Control no-cache or no-store.

None
stale_while_revalidate int | None

adds that directive to Cache-Control.

None
single_flight bool | None

concurrent misses for the same key wait for the first one.

None
max_value_size int | None

don't store values larger than this many bytes.

None
distributed_lock bool | None

one process computes a missing value while the others wait.

None
early_refresh float | None

refresh popular entries before they expire (a fraction of expire).

None
instance Cache | None

the Cache to use instead of the global FastAPICache (what Cache.cached() passes).

None
tags Tags | None

names to invalidate the entry by, with FastAPICache.invalidate_tags(); a list, or a function receiving the call's arguments, e.g. lambda product_id, **_: [f"product:{product_id}"].

None

fastapi_alcazaba.response_cache.default_key_builder

default_key_builder(func: Callable[..., Any], namespace: str = '', *, request: Request | None = None, response: Response | None = None, args: tuple[Any, ...], kwargs: dict[str, Any]) -> str

namespace plus the SHA-256 of the function's module, qualified name and the repr() of its arguments.

SHA-256, not MD5, and __qualname__, not __name__, so that two methods with the same name in one module don't share their entries. Request headers that change the response, such as Authorization, are added by the decorator (vary_headers), whatever the key builder.

fastapi_alcazaba.response_cache.Coder

Turns values into bytes for the backend, and back.

decode_as_type classmethod

decode_as_type(value: bytes, *, type_: Any | None) -> Any

Decode value and validate it as type_ (the return annotation of the cached function), so that a cache hit returns the same kind of object as a miss: a Pydantic model or a dataclass, not the dict it was stored as. Raises pydantic.ValidationError when the cached data doesn't fit the type (e.g. the model changed since it was cached).

fastapi_alcazaba.response_cache.JsonCoder

Bases: Coder

JSON. Values without dates or Decimals outside Pydantic models (the usual case) go through pydantic-core, in Rust: some 14 times faster than jsonable_encoder() for a list of models, and hits with a return annotation are validated straight from the bytes.

fastapi_alcazaba.response_cache.PickleCoder

Bases: Coder

Pickles the value. Unpickling runs code chosen by whoever wrote the bytes: only use it with a signing_key (Alcazaba then refuses entries it didn't sign) and a backend that untrusted parties can't write to.