question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

FastAPI 0.67.0 new dataclass related features conflict with some aspects of new SQLAlchemy dataclass-style model mapping

See original GitHub issue

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 Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:3
  • Comments:17 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
AlexanderPodorovcommented, Sep 12, 2022

@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 with dataclasses.asdict which is used internally by FastAPI for serialization:

  • if a SQLA dataclass has a @property, it don’t get serialized by dataclasses.asdict
  • if a SQLA dataclass has a relationship attribute declared with lazy="raise", it get serialized, however it should not in some cases.

I would avoid calling dataclasses.asdict for serialization. E.g. if response_model is Pydantic model, we could do Model.from_orm(obj) and then convert it to dict. In case of List[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 doing return UserOut.from_orm(instance). And it works great.

0reactions
tiangolocommented, Nov 14, 2022

Thanks for the comments! Yep, if you solved your use case you can close the issue @AlexanderPodorov 🤓

Read more comments on GitHub >

github_iconTop Results From Across the Web

python - FastAPI SQLAlchemy - ManyToMany Relation Conflict
I have two main models: user and post class User(Base): """ User Model """ __tablename__ = "user" id = Column(Integer, primary_key=True) ...
Read more >
Async SQLAlchemy with FastAPI
There is now support for declarative mapping with dataclasses and attrs. In this post I will use the new async capabilities of the...
Read more >
FastAPI with Async SQLAlchemy, SQLModel, and Alembic
This tutorial looks at how to configure SQLAlchemy, SQLModel, and Alembic to work with FastAPI asynchronously.
Read more >
SqlAlchemy 1.4 async ORM with FastAPI - rogulski.it
The reason to keep it outside the app module is similar to why poetry ... git clone git@github.com:rglsk/fastapi-sqlalchemy-1.4-async.git ...
Read more >
tiangolo/fastapi - Gitter
@tiangolo any chance we could get a new release that uses pydantic 0.30.1? This supposedly includes the fixes for being able to use...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found