FastAPI 0.67.0 new dataclass related features conflict with some aspects of new SQLAlchemy dataclass-style model mapping
See original GitHub issueFirst 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
Issue Analytics
- State:
- Created 2 years ago
- Reactions:3
- Comments:17 (5 by maintainers)
Top GitHub Comments
@tiangolo , thanks for your great work and FastAPI!
I don’t see a good way of address this issue either. AFAIK SQLAlchemy fully supports
dataclasses
related functionality, however there are two quirks withdataclasses.asdict
which is used internally by FastAPI for serialization:@property
, it don’t get serialized bydataclasses.asdict
lazy="raise"
, it get serialized, however it should not in some cases.I would avoid calling
dataclasses.asdict
for serialization. E.g. ifresponse_model
is Pydantic model, we could doModel.from_orm(obj)
and then convert it todict
. In case ofList[Item]
we could do the same, but within list comprehension.I’m now returning Pydantic models (parsed from SQLA dataclasses) from my endpoints. Based on my example: instead of
return instance
, I’m doingreturn UserOut.from_orm(instance)
. And it works great.Thanks for the comments! Yep, if you solved your use case you can close the issue @AlexanderPodorov 🤓