Search code examples
python-3.xpostgresqlfastapistarlettedepends

How to return multiple data/objects from a dependency function in FastAPI?


I am using FastAPI framework and writing some data into PostgreSQL database. The issue I am encountering is that I have a function get_current_user() that returns user credentials (including email), but I am not sure how to access this data from another function s3_create_upload_files().

The reason I want to do this is because I want to write the email address and the filename that I am going to upload to the same line in the database after the file is successfully uploaded.

get_current_user()

async def get_current_user(
    request: Request,
    db: Session = Depends(get_db),
):
    token = request.cookies.get("Authorization")

    if token is None:
        raise HTTPException(status_code=302, headers={"Location": '/auth/login'})

    token = token.split("Bearer ")[-1]

    try:
        payload = jwt.decode(token, os.getenv('SECRET_KEY'), algorithms=["HS256"])
        email: str = payload.get("sub")
        if email is None:
            raise HTTPException(status_code=302, headers={"Location": '/auth/login'})
    except jwt.exceptions.ExpiredSignatureError:
        raise HTTPException(status_code=302, headers={"Location": '/auth/login'})

    user = db.query(User).filter(User.email == email).first()
    if user is None:
        raise HTTPException(status_code=302, headers={"Location": '/auth/login'})
    
    print(f'Email is {user.email}')

    return user  # Ensure 'user' is a User object

s3_create_upload_files()

async def s3_create_upload_files(
    file: UploadFile = File(...),
    current_user: User = Depends(get_current_user), 
    db: Session = Depends(get_db)
):
....CODE HERE,,,,

        # Insert data into the database after successful upload
    try:
        db = SessionLocal()
        file_data = User(filename=sanitized_filename, email=current_user.email)
        db.add(file_data)
        db.commit()
    except Exception as e:
        db.rollback()
        raise HTTPException(status_code=500, detail=str(e))
    finally:
        db.close()
    

I have tried a few different ways to receive the data in s3_create_upload_files() but all of them cause an error on the HTML side. Same error has come about again "{"detail":"'Depends' object has no attribute 'email'"}"

The error in the backend says INFO: 192.168.65.1:62133 - "POST /dashboard HTTP/1.1" 500 Internal Server Error


Solution

  • You could do that using one of the approaches described below.

    Option 1 - Return a list or dict of data

    Return a list of data from the dependency function, and define a proper parameter in the endpoint to expect these data in the appropriate form.

    from fastapi import FastAPI, Depends
    from pydantic import BaseModel
    
    app = FastAPI()
    
    class User(BaseModel):
        name: str
        age: int
        
        
    async def get_user_data():
        user = User(name='john', age=33)
        email = '[email protected]'
        return [user, email]
    
    
    @app.post('/upload')
    async def upload(data: list = Depends(get_user_data)):
        return {'user': data[0], 'email': data[1]}
    

    To use a dict instead, apply the following changes to the example above:

    # ...
    
    async def get_user_data():
        user = User(name='john', age=33)
        email = '[email protected]'
        return {'user': user, 'email': email}
    
    
    @app.post('/upload')
    async def upload(data: dict = Depends(get_user_data)):
        return {'user': data['user'], 'email': data['email']}
    

    Option 1 - Return an object of a custom Python class

    Another way to approach this issue would be to create a custom Python class with the relevant data, instantiate the class inside the dependency function to create an object, and finally, return the object to the endppoint. For simplicity, one could use Python's dataclass—as shown in this answer— where special methods, such as such as __init__() and __repr__() can be automatically added.

    from fastapi import FastAPI, Depends
    from pydantic import BaseModel
    from dataclasses import dataclass
    
    app = FastAPI()
    
    class User(BaseModel):
        name: str
        age: int
        
    
    @dataclass
    class UserData:
        user: User
        email: str
    
        
    async def get_user_data():
        user = User(name='john', age=33)
        email = '[email protected]'
        return UserData(user, email)
    
    
    @app.post('/upload')
    async def upload(data: UserData = Depends(get_user_data)):
        return {'user': data.user, 'email': data.email}
    

    Option 3 - Use request.state to store arbitrary data

    A further, and maybe simpler, way to store such arbitrary data would be to use request.state, which would allow you to retrieve these data later in the endpoint, using the Request object. For more details and examples on this approach (as well as on global dependencies and request.state in FastAPI), I would highly suggest having a look at this answer, this answer, as well as this and this.

    from fastapi import FastAPI, Depends, Request
    from pydantic import BaseModel
    
    app = FastAPI()
    
    class User(BaseModel):
        name: str
        age: int
        
        
    async def get_current_user(request: Request):
        user = User(name='john', age=33)
        request.state.email = '[email protected]'
        return user
    
    @app.post('/upload')
    async def upload(request: Request, user: User = Depends(get_current_user)):
        return {'user': user, 'email': request.state.email}