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

redis produces multiple keys for the same request because args/kwargs aren't encoded

Open dveleztx opened this issue 4 years ago • 7 comments

Redis Multiple Keys

As seen from the screenshot, when trying to reach the same endpoint before expiring, nothing is being retrieved from redis.

dveleztx avatar Jul 27 '21 01:07 dveleztx

@long2ice can you check this one, please? I have the same issue

falled10 avatar Aug 07 '21 12:08 falled10

@long2ice Any progress on this? As stated, caching isn't actually working as shown above.

dveleztx avatar Sep 16 '21 14:09 dveleztx

@long2ice Status?

dveleztx avatar Sep 19 '21 20:09 dveleztx

@dveleztx I think you can write your own key_builder to deal with the issue. And I test your code on some situations will occur errors, e.g. kwargs has cannot encode data (maybe pydantic model)

k1dav avatar Sep 23 '21 05:09 k1dav

@dveleztx I had the same problem, and in my case the different cache keys where caused by enum parameters and a DB session parameter. I wrote a custom key builder to fix it:

def api_key_builder(
        func,
        namespace: Optional[str] = "",
        request: Optional[Request] = None,
        response: Optional[Response] = None,
        args: Optional[tuple] = None,
        kwargs: Optional[dict] = None,
):
    """
    Handle Enum and Session params properly.
    """
    prefix = f"{FastAPICache.get_prefix()}:{namespace}:"

    # Remove session and convert Enum parameters to strings
    arguments = {}
    for key, value in kwargs.items():
        if key != 'session':
            arguments[key] = value.value if isinstance(value, Enum) else value

    cache_key = prefix + hashlib.md5(f"{func.__module__}:{func.__name__}:{args}:{arguments}".encode()).hexdigest()

    return cache_key

I then use my custom api_key_builder globally like this: FastAPICache.init(InMemoryBackend(), prefix='fastapi', key_builder=api_key_builder)

aimfeld avatar Jan 12 '22 13:01 aimfeld

I remove both session and request from the key.

            request: Request = copy_kwargs.pop("request")
            del copy_kwargs['session']

antont avatar Jun 09 '23 05:06 antont

Thank you @aimfeld I solved the issue.

I have an API that uses an SQLite DB, that issue is due to SQLAlchemy generating a different Session object, for instance:

kwargs.items(): dict_items([('db', <sqlalchemy.orm.session.Session object at 0x7f92ad2f1120>), ('skip', 5), ('limit', 3)])
<sqlalchemy.orm.session.Session object at 0x7f92ad3b2ec0>
kwargs.items(): dict_items([('db', <sqlalchemy.orm.session.Session object at 0x7f92ad3b3940>), ('skip', 5), ('limit', 3)])
<sqlalchemy.orm.session.Session object at 0x7f92ad3b3940>

@dveleztx I solved the issue as you mentioned:

def api_key_builder(
    func: Callable,
    namespace: Optional[str] = "",
    request: Optional[Request] = None,
    response: Optional[Response] = None,
    args: Optional[tuple] = None,
    kwargs: Optional[dict] = None,
) -> str:
    from fastapi_cache import FastAPICache

    # SOLUTION: https://github.com/long2ice/fastapi-cache/issues/26
    #print("kwargs.items():", kwargs.items())
    arguments = {}
    for key, value in kwargs.items():
        if key != 'db':
            arguments[key] = value
    #print("request:", request, "request.base_url:", request.base_url, "request.url:", request.url)
    arguments['url'] = request.url
    #print("arguments:", arguments)

    prefix = f"{FastAPICache.get_prefix()}:{namespace}:"
    cache_key = (
        prefix
        + hashlib.md5(  # nosec:B303
            f"{func.__module__}:{func.__name__}:{args}:{arguments}".encode()
        ).hexdigest()
    )
    return cache_key

Then use the new key builder on your FastAPICache init:

redis = aioredis.from_url(get_settings().url_redis)
FastAPICache.init(RedisBackend(redis), prefix="tangara-cache", key_builder=api_key_builder)

Thank you so much, maybe someone still having the same issue o similar using PostgreSQL or any other DB, this solution could help them.

🚲

sebaxtian avatar Jun 17 '23 21:06 sebaxtian