fastapi-cache2 Documentation

repository·main·Indexed 23 days ago

https://github.com/long2ice/fastapi-cache

A caching tool for FastAPI endpoints and function results. It supports multiple backends including Redis, Memcached, DynamoDB, and in-memory storage. The library provides a @cache decorator for managing expiration, namespaces, and custom key generation, and supports caching Pydantic models and dataclasses via JSON encoding.

Tokens
1.9K
Snippets
5
Records
8
Agent score
34%

What's inside fastapi-cache2

  1. Backend behavior: InMemory vs Redis

    main

    InMemoryBackend

    Data is stored in memory and is only deleted when an expired key is accessed. This means data that is never accessed again will remain in memory until the process restarts.

    RedisBackend

    Requires a Redis client where decode_responses is set to False (the default). If decode_responses=True is used, the client will attempt to decode binary cached data as strings, which will break the caching mechanism.

  2. How injected Request and Response dependencies work

    main

    The @cache decorator automatically injects Request and Response dependencies into your endpoint to:

    1. Add cache control headers (like ETag and Cache-Control) to the response.
    2. Return a 304 Not Modified response if the incoming request has a matching If-Non-Match header.

    This injection only occurs if your endpoint does not already explicitly list these dependencies. The internal keyword arguments used are __fastapi_cache_request and __fastapi_cache_response. You can change these prefixes using the injected_dependency_namespace parameter in @cache to avoid collisions.

  3. Install fastapi-cache2

    main

    Install the core package or use extras to include dependencies for specific backends:

    • Base installation: pip install fastapi-cache2
    • Redis backend: pip install "fastapi-cache2[redis]"
    • Memcached backend: pip install "fastapi-cache2[memcache]"
    • DynamoDB backend: pip install "fastapi-cache2[dynamodb]"
    pip install "fastapi-cache2[redis]"
  4. Quick Start with FastAPI and Redis

    main

    To use fastapi-cache with Redis, initialize it within your FastAPI lifespan handler using FastAPICache.init. You can then apply the @cache decorator to FastAPI endpoints or regular functions.

    Note: When using RedisBackend, ensure your Redis client does not have decode_responses=True (the default is False), as cached data must be stored as bytes.

    from collections.abc import AsyncIterator
    from contextlib import asynccontextmanager
    
    from fastapi import FastAPI
    from starlette.requests import Request
    from starlette.responses import Response
    
    from fastapi_cache import FastAPICache
    from fastapi_cache.backends.redis import RedisBackend
    from fastapi_cache.decorator import cache
    
    from redis import asyncio as aioredis
    
    
    @asynccontextmanager
    async def lifespan(_: FastAPI) -> AsyncIterator[None]:
        redis = aioredis.from_url("redis://localhost")
        FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache")
        yield
    
    
    app = FastAPI(lifespan=lifespan)
    
    
    @cache()
    async def get_cache():
        return 1
    
    
    @app.get("/")
    @cache(expire=60)
    async def index():
        return dict(hello="world")
  5. Caching Pydantic models and dataclasses

    main

    When using the default JsonCoder, you can cache any data type compatible with JSON, including Pydantic models and dataclasses.

    Crucial: You must provide a correct return type annotation on the function itself. While configuring a response_model in the FastAPI route decorator is helpful for the API, the @cache decorator relies on the function's own type hint to convert the cached JSON back into the correct class instance.

    If no return type annotation is provided, the cache will return a primitive JSON type (like a dict) instead of the model instance.

    from .models import SomeModel, create_some_model
    
    @app.get("/foo")
    @cache(expire=60)
    async def foo() -> SomeModel:
        return create_some_model()
  6. Use the @cache decorator

    main

    The @cache decorator can be used on FastAPI endpoints (placed between the router decorator and the view function) or on regular functions to cache results.

    Parameters

    ParameterTypeDefaultDescription
    expireintCaching time in seconds
    namespacestr""Namespace for storing cache items
    coderCoderJsonCoderThe coder used for encoding/decoding
    key_builderKeyBuilderdefault_key_builderCallable to generate cache keys
    injected_dependency_namespacestr"__fastapi_cache"Prefix for injected dependency keywords
    cache_status_headerstr"X-FastAPI-Cache"Header name indicating HIT or MISS
  7. Implement a custom Key Builder

    main

    The default key builder uses the function's module, name, and the repr() of its arguments to create an MD5 hash. You can provide a custom key_builder callable to @cache() or globally via FastAPICache.init() to change how keys are generated (e.g., including request URL or method).

    def request_key_builder(
        func, 
        namespace: str = "", 
        *, 
        request: Request = None, 
        response: Response = None, 
        *args, 
        **kwargs
    ):
        return ":".join([
            namespace,
            request.method.lower(),
            request.url.path,
            repr(sorted(request.query_params.items()))
        ])
    
    
    @app.get("/")
    @cache(expire=60, key_builder=request_key_builder)
    async def index():
        return dict(hello="world")
  8. Implement a custom Coder

    main

    If you need broader type support (e.g., for NumPy or complex objects) or different serialization formats, you can implement a custom coder by inheriting from fastapi_cache.coder.Coder and implementing encode and decode class methods.

    from typing import Any
    import orjson
    from fastapi.encoders import jsonable_encoder
    from fastapi_cache import Coder
    
    class ORJsonCoder(Coder):
        @classmethod
        def encode(cls, value: Any) -> bytes:
            return orjson.dumps(
                value,
                default=jsonable_encoder,
                option=orjson.OPT_NON_STR_KEYS | orjson.OPT_SERIALIZE_NUMPY,
            )
    
        @classmethod
        def decode(cls, value: bytes) -> Any:
            return orjson.loads(value)
    
    
    @app.get("/")
    @cache(expire=60, coder=ORJsonCoder)
    async def index():
        return dict(hello="world")