-
-
Notifications
You must be signed in to change notification settings - Fork 8.4k
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 __future__ import annotations
from dataclasses import dataclass, field
from fastapi import Depends, FastAPI
from pydantic import BaseModel
from sqlalchemy import Column, ForeignKey, Integer, String, create_engine
from sqlalchemy.orm import Session, registry, relationship, sessionmaker
from uvicorn import run
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
mapper = registry()
@mapper.mapped
@dataclass
class User:
__tablename__ = "users"
__sa_dataclass_metadata_key__ = "sa"
id: int = field(
metadata={
"sa": Column(Integer, primary_key=True, index=True)
}
)
email: str = field(
metadata={
"sa": Column(String, unique=True, index=True)
}
)
items: list[Item] = field(
init=False,
metadata={
"sa": relationship("Item", lazy="raise")
}
)
@property
def greeting(self):
return f"Hello {self.email}"
@mapper.mapped
@dataclass
class Item:
__tablename__ = "items"
__sa_dataclass_metadata_key__ = "sa"
id: int = field(
metadata={
"sa": Column(Integer, primary_key=True, index=True)
}
)
title: str = field(
metadata={
"sa": Column(String, index=True)
}
)
owner_id: int = field(
init=False,
metadata={
"sa": Column(Integer, ForeignKey("users.id"))
}
)
class UserIn(BaseModel):
id: int
email: str
class UserOut(BaseModel):
id: int
email: str
greeting: str
class Config:
orm_mode = True
mapper.metadata.create_all(bind=engine)
app = FastAPI()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.post("/users/", response_model=UserOut)
def create_user(user: UserIn, db: Session = Depends(get_db)):
instance = User(
id=user.id,
email=user.email
)
db.add(instance)
db.commit()
return instance
if __name__ == '__main__':
run(app)Description
Example code works if fastapi==0.66.1 and it does not work if fastapi==0.67.0.
Open a browser, hit POST /users/ endpoint and create a user
Expected behaviour: User is created and displayed successfully.
Current behavior: User is created successfully (check in DB), but is not displayed successfully due to serialization errors.
Most likely the reason is in the new dataclass related features of FastAPI 0.67.0. Since the new-style database models are dataclasses as well, therefore they are affected too. As far as I can see if an instance is the dataclass, then FastAPI makes a dict (dataclasses.asdict(res)) out of instance before doing serialization.
It has two issues: first, if a dataclass has a property, it won't be serialized; second, if a dataclass has a relationship with lazy="raise" (means we should load this relationship explicitly), it is actually accessed and caused SQLAlchemy's exception.
Technically, if we let pydantic to serialize this dataclass, we won't get any of those issues. I assume it was the case for previous versions of FastAPI.
Here are a few links:
https://docs.sqlalchemy.org/en/14/orm/mapping_styles.html#orm-declarative-dataclasses-declarative-table
https://docs.sqlalchemy.org/en/14/orm/loading_relationships.html#prevent-lazy-with-raiseload
Operating System
Windows
Operating System Details
No response
FastAPI Version
0.67.0
Python Version
3.9.6
Additional Context
No response