-
-
Notifications
You must be signed in to change notification settings - Fork 8.5k
Description
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 itemDescription
(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_dbAlternatives
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.