long2ice / fastapi-cache

fastapi-cache is a tool to cache fastapi response and function result, with backends support redis and memcached.
https://github.com/long2ice/fastapi-cache
Apache License 2.0
1.34k stars 162 forks source link

Error when using FastAPI request state variable AND also body parameter #183

Open john-tipper opened 1 year ago

john-tipper commented 1 year ago

I have a FastAPI POST endpoint which receives a parameter in the request body. A global FastAPI Depends sets a Request state value which I want to retrieve in my method.

The following works fine:

from pydantic import BaseModel
from fastapi import APIRouter, Request

class MyPydanticValue(BaseModel):
    # some properties here

router = APIRouter()

@router.post(
  "/foo"
)
def perform_action(
  my_val: MyPydanticValue
  request: Request
):
  # do something with my_val
  ...

  # and also reference a state value that was set globally elsewhere
  print(request.state.x_some_value) 

FastAPI sees the my_val parameter, recognises that it is a Pedantic model and therefore parses it from the request body.

I now want to cache the function, so I use @cache:

@router.post(
  "/foo"
)
@cache(expire=300)
def perform_action(
  my_val: MyPydanticValue
  request: Request
):
  # do something with my_val
  ...

  #and also reference a state value that was set globally elsewhere
  print(request.state.x_some_value) 

Now I get an exception:

TypeError: perform_action() got multiple values for argument 'my_val'

The fastapi-cache documentation states:

The cache decorator injects dependencies for the Request and Response objects, so that it can add cache control headers to the outgoing response, and return a 304 Not Modified response when the incoming request has a matching If-Non-Match header. This only happens if the decorated endpoint doesn't already list these dependencies already.

My reading of that is that the injection shouldn't happen because I declare a parameter referring to the Request already. I think that's what is happening in the source code for the @cache decorator: https://github.com/long2ice/fastapi-cache/blob/v0.2.1/fastapi_cache/decorator.py#L41-L46. However, I think that my function is indeed actually getting a request parameter being injected by fastapi-cache and then subsequently by FastAPI. Alternatively, something that the cache wrapper is doing is messing up the parsing of the body.

What am I doing wrong - is this a bug? If so, is there a way of working around this? How do I access the state value in the endpoint function whilst also parsing the Pydantic model from the request body?

vvanglro commented 1 year ago

You can try install main.

pip install git+https://github.com/long2ice/fastapi-cache.git         

But if the post method you use will not cache, if you want to test, you can modify it to return false. https://github.com/long2ice/fastapi-cache/blob/8945ac7a3a7d1533388a81460afdc2b5f0b1dfc2/fastapi_cache/decorator.py#L82-L83

john-tipper commented 1 year ago

I've traced the error to this line: https://github.com/long2ice/fastapi-cache/blob/v0.2.1/fastapi_cache/decorator.py#L152

return await ensure_async_func(request, *args, **kwargs)

Is there a reason why this particular invocation of the function adds a request argument, whereas this does not happen anywhere else in the code? I don't think that request argument belongs in that call. It results in too many arguments being passed to the underlying function.

plv commented 1 year ago

@john-tipper Did you ever find a workaround for this? Running into the same thing.

Ninefiveblade commented 1 year ago

Same thing, can someone answer?

golankopi commented 10 months ago

hi any update? can we please open a PR and fix the relevant line?

admo1 commented 8 months ago

I also have the same issue

ricardordb commented 7 months ago

Try changing your method to GET instead of POST. It worked for me.