fastapi-cache icon indicating copy to clipboard operation
fastapi-cache copied to clipboard

Caching is not working on routes that are generated from a factory function

Open jjmurre opened this issue 1 year ago • 3 comments

My router is generate from a factory function like this:

def api_router_factory(prefix, schema, model):
    router = APIRouter(prefix=prefix, responses={404: {"description": "Not found"}})
    router.get("/", response_model=list[schema])(
        cache(expire=100)(
            multi_fetch_router_factory(multi_fetch_crud_factory(model))
        )
    )
    return router

However, the cache is never HIT. I also tried adding explicit return types to the functions that are generated in the factory functions (multi_fetch_router_factory and multi_fetch_crud_factory), but that does not help.

jjmurre avatar Dec 11 '24 17:12 jjmurre

I do have the same issue when using APIRouter nothing is cached

Eg:

router = APIRouter(
    prefix="/emails/provider",
    responses={404: {"description": "Not found"}},
)

@router.post(
    "/sync/{email_type}",
    dependencies=[Security(get_current_user, scopes=["lj:admin:all", "lj:email:read"])],
)
@cache(expire=60, key_builder=custom_key_builder)
async def sync(
    email_type: str,
    request: Request,
    params: Params = Depends(),
    current_user: dict = Security(get_current_user, scopes=["lj:admin:all", "lj:email:read"]),
):

It's never hit

pierre-sigwalt avatar Feb 11 '25 22:02 pierre-sigwalt

I found it did not work with Depends, but im looking into it

peterdudfield avatar Feb 14 '25 15:02 peterdudfield

in my case the issue was that a new cache key was generated each time. The key builder uses the string representation of the module, function name, arguments, and keyword arguments to generate the cache key. This causes issues when using custom class in Depends, like Params.

class Params:
    pass

By default, the string representation of Params is inherited from Python’s object class, which includes the memory address, e.g., <main.Params object at 0x1098a2e90>. When this address changes, a new cache key is generated, leading to a cache miss when expected to hit.

To fix this issue, you can override the __repr__ method in your Params class to provide a consistent string representation. Here’s how you can do it:

from fastapi import Depends, FastAPI
from fastapi_cache import FastAPICache
from fastapi_cache.backends.inmemory import InMemoryBackend
from fastapi_cache.decorator import cache

app = FastAPI()
FastAPICache.init(InMemoryBackend())

class Params:
    def __repr__(self):
        return "Params()"

@app.get("/test")
@cache(namespace="test", expire=10)
def test(params: Params = Depends()):
    return {"ok": True}

Without overriding repr, the generated cache keys can vary each time, like:

:test:01e4b84ba7818803a3a3da1bd1b5f032
:test:f99a3f33170b71f1cf0f65d772eaefbb
:test:cc929569696662be729ab3ec4bcd8f7c

After overriding repr, the cache key becomes consistent and remains the same for identical parameters:

:test:473ba42f62c0c987026985045eccd0a8

This ensures that the cache key remains the same for the same parameters, avoiding cache misses

Hamza-nabil avatar Mar 12 '25 08:03 Hamza-nabil