Skip to content

Use FastAPI dependencies when rendering response models #5112

@olivier-thatch

Description

@olivier-thatch

First Check

  • I added a very descriptive title to this issue.
  • I used the GitHub search to find a similar issue and didn't find it.
  • I searched the FastAPI documentation, with the integrated search.
  • I already searched in Google "How to X in FastAPI" and didn't find any information.
  • I already read and followed all the tutorial in the docs and didn't find an answer.
  • I already checked if it is not related to FastAPI but to Pydantic.
  • I already checked if it is not related to FastAPI but to Swagger UI.
  • I already checked if it is not related to FastAPI but to ReDoc.

Commit to Help

  • I commit to help with one of those options 👆

Example Code

from fastapi import FastAPI
from pydantic import BaseModel

from .database import Base
from .deps import get_db

# DB models

class ItemDB(Base):
    __tablename__ = "items"

    id: int = Column(Integer, primary_key=True, index=True)
    title: str = Column(String, nullable=False)
    
    def times_ordered(self, db: Session) -> int:
        return db.query(func.count(OrderDB.id)).filter(Order.item_id == self.id).scalar()


class OrderDB(Base):
    __tablename__ = "orders"

    id: int = Column(Integer, primary_key=True, index=True)
    item_id: int = Column(Integer, ForeignKey("items.id"))


# Pydantic model

class Item(BaseModel):
    title: str
    times_ordered: int
 

app = FastAPI()

@app.get("/items/{item_id}", response_model=Item)
async def get_item(db: Session = Depends(get_db), item_id: int) -> Item:
    item_db = db.query(ItemDB).filter(ItemDB.id == item_id).first()
    # assume item_db exists
    item = Item(
        title=item_db.title,
        times_ordered=item_db.times_ordered(db),
    )
    return item

Description

(Assuming the DB is already populated with 1 item with id 1, and 3 orders pointing to the item.)

  • Send a request to /items/1
  • The API replies with a payload similar to {"title": "Item title", "times_ordered": 3}

Because the ItemDB.times_ordered() instance method needs the db dependency, I was not able to simply return the ItemDB model and let FastAPI map the DB model to the Pydantic model. Instead, I had to manually instantiate the Item Pydantic schema in the route handler.

Wanted Solution

It would be great if there was some way I could tell FastAPI how to instantiate the Pydantic model from the DB model while using FastAPI's dependency injection.

Below is an example of what it could look like. I don't know how FastAPI's DI actually works so this example may not necessary be realistic -- maybe FastAPI would need more information in the route decorator or something. But the gist of it is that the route handler can still return the DB model and not have to worry about mapping the DB model to the Pydantic model.

Wanted Code

from fastapi import FastAPI
from pydantic import BaseModel

from .database import Base
from .deps import get_db

# DB models

class ItemDB(Base):
    __tablename__ = "items"

    id: int = Column(Integer, primary_key=True, index=True)
    title: str = Column(String, nullable=False)
    
    def times_ordered(self, db: Session = Depends(get_db)) -> int:
        return db.query(func.count(OrderDB.id)).filter(Order.item_id == self.id).scalar()


class OrderDB(Base):
    __tablename__ = "orders"

    id: int = Column(Integer, primary_key=True, index=True)
    item_id: int = Column(Integer, ForeignKey("items.id"))


# Pydantic model

class Item(BaseModel):
    title: str
    times_ordered: int
 

app = FastAPI()

@app.get("/items/{item_id}", response_model=Item)
async def get_item(db: Session = Depends(get_db), item_id: int) -> ItemDB:
    item_db = db.query(ItemDB).filter(ItemDB.id == item_id).first()
    # assume item_db exists
    return item_db

Alternatives

The alternative would be to simply do what's in the first example code. For clarity, the mapping of DB model to Pydantic model could be moved into a helper method on the Pydantic model class, but the route handler still has to call the helper method and have its return type set to the Pydantic model rather than the DB model.

Operating System

Linux

Operating System Details

No response

FastAPI Version

0.78.0

Python Version

3.10.4

Additional Context

This example is a bit contrived -- if you had this setup in practice you would also define an orders relationship on the ItemDB DB model class, then you could change times_ordered to be a property that returns len(orders) and Pydantic would be able to fill the times_ordered field with the property's return value. However this would be suboptimal, because it would load all orders from the DB rather than using COUNT(order.id).

More importantly, this would only work for this specific use case. In practice, you may have more complex SQL queries that would not map so cleanly to SQLAlchemy's relationships, or you may need to use other dependencies entirely. For instance, I have a model that has an s3_key column, and I need to inject an S3 client to compute a presigned URL and return the URL in the Pydantic model.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions