pythonpython-3.xrequestfastapidepends

"AttributeError: 'property' object has no attribute 'get'" when using Depends in FastAPI


To implement authentication for a FastAPI endpoint, I want to make use of the Depends functionality offered in the latest versions (>=0.95) of FastAPI. I have tried several ways to implement it but I am facing an error "AttributeError: 'property' object has no attribute 'get'". This implies that the starlette Request object is not recognised as such when using Depends.

Endpoint implementation:

@app.get("/")
async def home(user: Annotated[User, Depends(authenticate_request(Request))]):
    """
    status _summary_
    Returns status message if service is running
    """
    if isinstance(user, User):
        return {"Welcome, service is running ok!"}
    raise HTTPException(
        status_code=401,
        detail="User not authorized!",
    )

Function authenticate_request checks validity of cookies based on provided Request:

def authenticate_request(request: Request) -> User:
    cookie1 = request.cookies.get("x")
    cookie2 = request.cookies.get("y")
    if cookie1 is not None and cookie2 is not None:
        tenant = get_tenant(request)
        if user := authenticate_cookies(cookie1, cookie2, tenant):
            if not tenant.startswith(user.tenant):
                print("Unauthorized: Token hostname mismatch")
                raise Exception("Token hostname mismatch")
            return user
    print("Unauthorized: please login")
    raise HTTPException(status_code=401, detail="Unauthorized: please login")

This results in error "AttributeError: 'property' object has no attribute 'get'". This implies that the Request object is not passed correctly or is not recognised as such.

I then tried to solve this without using Depends:

@app.get("/")
async def home(request: Request):
    """
    status _summary_
    Returns status message if service is running
    """
    user = authenticate_request(request=request)
    if isinstance(user, User):
        return {"Welcome, service is running ok!"}
    raise HTTPException(
        status_code=401,
        detail="User not authorized!",
    )

This is working correctly. If no valid cookie is provided a 401 is returned. Based on this I come to the conclusion that my authenticate_request() function is working correctly.

Does anybody know why there is a difference in behaviour between Depends() and the alternative solution?

Help is much appreciated!

Regards, Laurens


Solution

  • When using Depends(), you just pass the name of the dependency function within the brackets (i.e., don't call it directly, just pass it as a parameter to Depends()). As described in the documentation:

    Let's first focus on the dependency.

    It is just a function that can take all the same parameters that a path operation function can take.

    And it has the same shape and structure that all your path operation functions have.

    You can think of it as a path operation function without the "decorator" (without the @app.get("/some-path")).

    And it can return anything you want.

    Thus, any other parameters that your dependency function takes, you simply need to declare them in that function and not passing them through Depends(). Same applies to the Request object. FastAPI will analyze the parameters for the dependency, and process them in the same way as the parameters for a path operation function (also known as endpoint), including sub-dependencies.

    Hence, your endpoint should look like this:

    @app.get("/")
    async def home(user: Annotated[User, Depends(authenticate_request)]):
        pass
    

    and your authenticate_request dependency function should be as follows:

    def authenticate_request(request: Request) -> User:
        pass
    

    Additional Information

    Instead of a "callable" function, one could also use a Python class instead as a dependency, as Python classes are also callable.

    One not only can they use a "callable" dependency class, but also a "callable" instance of that dependency class, which would allow them to initially pass fixed content when creating the instance of the class, as well as pass additional parameters and sub-dependencies when the endpoint is called.

    The __init__ method in the example below is used to declare the parameters of the instance that we can use to "parameterize" the dependency (the fixed content). FastAPI won't ever touch or care about __init__, it is used directly in the code when creating the instance of the class.

    The __call__ method, on the other hand, is what FastAPI will use to check for additional parameters and sub-dependencies, and this is what will be called to pass a value to the parameter in your endpoint, when it will later be called.

    Hence, in the example below, fixed_content is the attribute used to "parameterize" the dependency, while q is the additional query parameter to pass a value for, when a client is calling the endpoint.

    Example

    class FixedContentQueryChecker:
        def __init__(self, fixed_content: str):
            self.fixed_content = fixed_content
    
        def __call__(self, q: str = ""):
            if q:
                return self.fixed_content in q
            return False
    
    
    checker = FixedContentQueryChecker("bar")
    
    
    @app.get("/query-checker/")
    async def read_query_check(fixed_content_included: bool = Depends(checker)):
        return {"fixed_content_in_query": fixed_content_included}