Issue 1:
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

Issue 2:
https://github.com/tortoise/tortoise-orm