[Python] Circular Dependencies with Response Model

Discussão em 'Python' iniciado por Stack, Março 10, 2025.

  1. Stack

    Stack Membro Participativo

    I'm trying to leverage SQLModel "Relationships" in a FastAPI app. Specifically, I want to break my model classes out into separate files.

    I found the official SQLModel documentation describing how to deal with circular dependencies which describes how to use TYPE_CHECKING to sort of bypass the typing issues; and by itself, that allows the API to build.

    The problem arises when I try to declare a response_model on my API endpoint with one of the classes from one of those files. The API request raises:


    pydantic.errors.PydanticUserError: TypeAdapter[typing.Annotated[team.TeamResponse, FieldInfo(annotation=TeamResponse, required=True)]] is not fully defined; you should define typing.Annotated[team.TeamResponse, FieldInfo(annotation=TeamResponse, required=True)] and all referenced types, then call .rebuild() on the instance.
    ​

    And I think that makes sense that I'm getting an error (although the particular error is cryptic); after all, I used the TYPE_CHECKING voodoo on the model classes, but now I'm explicitly referencing TeamResponse, and expecting to get all the type information for the API request.

    So assuming I do NOT want to store all my models in one big class, how can I deal with circular dependencies in model classes AND specify a response_model with related data being returned?

    hero.py

    from typing import TYPE_CHECKING
    from sqlmodel import SQLModel, Field, Relationship
    if TYPE_CHECKING: from .team import Team

    class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    team_id: int | None = Field(default=None, foreign_key="team.id")
    team: "Team" = Relationship(back_populates="heroes")

    class HeroResponse(BaseModel):
    name: str
    team: "Team"


    team.py

    from typing import TYPE_CHECKING
    from pydantic import BaseModel
    from sqlmodel import SQLModel, Field, Relationship
    if TYPE_CHECKING: from .hero import Hero, HeroResponse

    class Team(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    heroes: list["Hero"] = Relationship(back_populates="team")

    class TeamResponse(BaseModel):
    """What I want returned from the API"""
    name: str
    heroes: list["HeroResponse"]


    app.py

    from fastapi import FastAPI, Depends
    from sqlmodel import Session, create_engine, select, SQLModel
    from team import TeamResponse, Team
    from hero import Hero
    engine = create_engine("sqlite:///db.db")
    app = FastAPI()

    async def get_session():
    session = Session(engine)
    try:
    yield session
    finally:
    session.close()

    @app.get("/teams", response_model=list[TeamResponse])
    def get_teams(session = Depends(get_session)):
    stmt = select(Team)
    heroes = session.exec(stmt).all()
    return heroes

    # If you want to stage some data
    if __name__ == "__main__":
    SQLModel.metadata.create_all(engine)
    preservers = Team(name="Preservers")
    hero1 = Hero(name="The Flash", team=preservers)
    hero2 = Hero(name="Superman", team=preservers)
    with Session(engine) as session:
    session.add_all([preservers,hero1, hero2])
    session.refresh(preservers)
    session.refresh(hero1)
    session.refresh(hero2)
    session.commit()

    Continue reading...

Compartilhe esta Página