FastAPI 0.67.0 new dataclass related features conflict with some aspects of new SQLAlchemy dataclass-style model mapping #6524
-
First Check
Commit to Help
Example Codefrom __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) DescriptionExample code works if Expected behaviour: User is created and displayed successfully. 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 Here are a few links: Operating SystemWindows Operating System DetailsNo response FastAPI Version0.67.0 Python Version3.9.6 Additional ContextNo response |
Beta Was this translation helpful? Give feedback.
Replies: 17 comments
-
I think this is caused by the same dataclass serialization issue as #3608 . |
Beta Was this translation helpful? Give feedback.
-
Could you check if my patch in PR #3607 fixes the issue for you? |
Beta Was this translation helpful? Give feedback.
-
I have also experienced this issue this week. It caused a huge slowdown in generating the openapi schema in 0.64 but broke when I updated to 0.67. The slowdown in schema generation seemed to come from A pydantic field has a However I've been meaning to report this as a bug for the last day or two. A longer term fix is of course needed, but whether this is in FastAPI or Pydantic is maybe a matter for discussion. |
Beta Was this translation helpful? Give feedback.
-
@himbeles , thank you! Unfortunately your fix won't work since in my case such a statement is problematic: obj_dict = dataclasses.asdict(obj) It grabs all |
Beta Was this translation helpful? Give feedback.
-
I have no hope that this is going to be fixed someday, especially for reason that it could affect new |
Beta Was this translation helpful? Give feedback.
-
I've run into the same issue, and have created a pull request that addresses it for me (PR #4094) - I'm not 100% sure about the approach though, let me know what you think. |
Beta Was this translation helpful? Give feedback.
-
@Lunrtick , I think a bool flag like that is not a good solution. It would be nicer to call |
Beta Was this translation helpful? Give feedback.
-
@AlexanderPodorov I agree - it's definitely not the cleanest solution. I thought it'd make sense because we'd be opting in to a "legacy" feature, but it'd also mean FastAPI would need to support that going forward. Perhaps we could emulate the There may be better ways to allow specifying this option though... Is it Pythonic to have a custom dunder property? In my codebase, this change only requires me to update my base class, from which all my SQLAlchemy models inherit. |
Beta Was this translation helpful? Give feedback.
-
@Lunrtick yes, I think it's a bit better. And yes, I think it's fine to have a custom dunder class attribute. SQLAlchemy relies on them too ( |
Beta Was this translation helpful? Give feedback.
-
Is this ever going to get fixed? A rather big issue I would say |
Beta Was this translation helpful? Give feedback.
-
hi guys, this has been affecting us as well |
Beta Was this translation helpful? Give feedback.
-
Interesting, I would think that the main problem here would be that SQLAlchemy supports a bit of dataclasses, but it doesn't support So, I would think that the problem is not with dataclasses, and not necessarily even with FastAPI, but with the quirk that SQLAlchemy is doing its best to support dataclasses while not being made to do so, so it can only do so much, while still modifying the class and attributes in several ways. I see there are some possible approaches, like the I'm not sure what's the best approach here. I hope to provide a way to configure how things are serialized and converted internally at some point, and that would allow customizing these things. I also made SQLModel to solve this kind of problems and avoid code duplication: https://sqlmodel.tiangolo.com/ , you can also check if that works for you. About just parsing directly from the |
Beta Was this translation helpful? Give feedback.
-
@tiangolo , thanks for your great work and FastAPI! I don't see a good way of address this issue either.
I would avoid calling I'm now returning Pydantic models (parsed from SQLA dataclasses) from my endpoints. |
Beta Was this translation helpful? Give feedback.
-
Thanks for the input @AlexanderPodorov! Yep, it seems SQLAlchemy supports several parts of dataclasses, but it seems it specifically doesn't support (at least not fully)
The problem with this is that it would work only for the specific case where the I hope to, in some future release, allow customizing how to serialize return values. This would probably allow overriding this functionality to accommodate this particular use case, for example. On the other hand, Pydantic V2 will have first class support for other types apart from models. Maybe that could help with these use cases too. I guess it would also be nice to have support in SQLAlchemy for |
Beta Was this translation helpful? Give feedback.
-
Thanks for the comments @tiangolo ! SQLAlchemy actually supports Yes, I understand there could be some complications with other use cases of I'm doubtful about having some config class specifying what to serialize. Technically we could have the same object, but we want to serialize it differently in different situations. If you want, I can close this issue or keep it open until we get better solution. Thanks! |
Beta Was this translation helpful? Give feedback.
-
I'm fairly new to SQLAlchemy, but I've been testing SQLAlchemy 2.0, and I've noticed that the new declarative model definitions result in model instances where So it seems this issue may affect all models in SQLAlchemy 2.0, not just dataclass models. I've had to comment out the code responsible for dict conversion so that my models can be properly parsed as Pydantic models with Edit: I was using the |
Beta Was this translation helpful? Give feedback.
-
Thanks for the comments! Yep, if you solved your use case you can close the issue @AlexanderPodorov 🤓 |
Beta Was this translation helpful? Give feedback.
Interesting, I would think that the main problem here would be that SQLAlchemy supports a bit of dataclasses, but it doesn't support
dataclasses.asdict()
and that's where it breaks.So, I would think that the problem is not with dataclasses, and not necessarily even with FastAPI, but with the quirk that SQLAlchemy is doing its best to support dataclasses while not being made to do so, so it can only do so much, while still modifying the class and attributes in several ways.
I see there are some possible approaches, like the
__read_with_orm_mode__
attribute, maybe aClassVar
. I don't see a predefined "standard" way of modifying dataclasses with additional metadata. But the problem is that …