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.

Selectin for the SQLModels relations

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 SQLModel documentation, with the integrated search.
  • I already searched in Google “How to X in SQLModel” 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 SQLModel but to Pydantic.
  • I already checked if it is not related to SQLModel but to SQLAlchemy.

Commit to Help

  • I commit to help with one of those options 👆

Example Code

class Team(SQLModel):
    id: int = Field(default=None, primary_key=True)
    heroes: List["Hero"] = Relationship(back_populates="team", sa_relationship_kwargs={"lazy": "selectin"})

class Hero(SQLModel):
    name: str

    team_id: Optional[int] = Field(default=None, foreign_key="team.id")
    team: Optional[Team] = Relationship(back_populates="incidents")

app = FastAPI()

@app.get("/teams/", response_model=List[Team])
def get_teams() -> List[Team]:
    with Session(engine) as session:
        return session.exec(select(Team)).all()

desired_output = [
    {
        "id": 1,
        "heroes": [
            {"name": "Batman"},
            {"name": "Superman"}
        ]
    }
]

Description

When returning a list of Teams the Relationship fields are not covered in the output. I believe the response is using pydantic.main.BaseModel.json() to get the data (and there it will only cover non-Relationship fields).

It would be great if there was an option to also return the Relationship fields especially if {"lazy": "selectin"} was enabled for the relation. It’s an easy workaround to walk the objects and return the complete data as json in the endpoint, but since we already have the models that is not very satisfying (also because the response_model for openapi will not be easily achievable).

Did I miss an option to enable having the Relationship attributes in the response, or is that something that could be worked on? I would also be happy to contribute but would need a pointer on where to start.

Operating System

Linux

Operating System Details

No response

SQLModel Version

0.0.7

Python Version

3.10.4

Additional Context

No response

Issue Analytics

  • State:open
  • Created a year ago
  • Comments:6

github_iconTop GitHub Comments

2reactions
xavipolocommented, Sep 11, 2022

Does anyone have the models separated in different files? When I separate the Hero from the Team in two different files I get an error when open “/docs/”

File "/home/xpolo/dev/python/fastapi-test/.env/lib/python3.10/site-packages/fastapi/utils.py", line 35, in get_model_definitions
    m_schema, m_definitions, m_nested_models = model_process_schema(
  File "pydantic/schema.py", line 580, in pydantic.schema.model_process_schema
  File "pydantic/schema.py", line 621, in pydantic.schema.model_type_schema
  File "pydantic/schema.py", line 254, in pydantic.schema.field_schema
  File "pydantic/schema.py", line 461, in pydantic.schema.field_type_schema
  File "pydantic/schema.py", line 847, in pydantic.schema.field_singleton_schema
  File "pydantic/schema.py", line 698, in pydantic.schema.field_singleton_sub_fields_schema
  File "pydantic/schema.py", line 526, in pydantic.schema.field_type_schema
  File "pydantic/schema.py", line 921, in pydantic.schema.field_singleton_schema
  File "/usr/lib/python3.10/abc.py", line 123, in __subclasscheck__
    return _abc_subclasscheck(cls, subclass)
TypeError: issubclass() arg 1 must be a class

In one file all is running ok, but not with separated files. All is running OK with separated files, without the HeroReadWithTeam / TeamReadWithHeroes

Note: I’m using TYPE_CHECKING with imports.

2reactions
daniil-bergcommented, Sep 9, 2022

First off, if you haven’t already, I suggest reading this section of the docs. It addresses the issue of infinite recursion (among other things) that can easily happen, if you don’t think carefully about what data you actually want to get.

Second, I assume you forgot to copy the table=True in your example models. Without those, you can’t use the models for any database queries.

The cyclic dilemma

There are already quite a few issues around this topic of including relationships in such a way. I haven’t looked deeply into the code for this yet. But as I understand it, it is not at all clear, how exactly you should tackle the problem of cyclical relationship graphs. Not that there aren’t any theoretical solutions. It seems to be more of a question of what a “sensible” approach may look like. In the end, it would have to be up to the user to decide, when exactly to stop walking along the relationship graph.

The most straightforward solution, and the one used right now is to simply avoid the issue altogether and force the user to e.g. set up his own response models (not tables) with the desired nesting of sub-models. (See the section “What Data to Include” on the aforementioned docs page.)

Be explicit yourself

So I would suggest closely following the advice about model inheritance from the docs for now. That means for your desired output:

from typing import List, Optional

from fastapi import FastAPI, Depends
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select


class TeamBase(SQLModel):
    name: str = Field(index=True)
    ...


class Team(TeamBase, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    heroes: List["Hero"] = Relationship(back_populates="team")


class TeamRead(TeamBase):
    id: int


class HeroBase(SQLModel):
    name: str = Field(index=True)
    ...
    team_id: Optional[int] = Field(default=None, foreign_key="team.id")


class Hero(HeroBase, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    team: Optional[Team] = Relationship(back_populates="heroes")


class HeroRead(HeroBase):
    id: int


class TeamReadWithHeroes(TeamRead):
    heroes: List[HeroRead] = []


app = FastAPI()

sqlite_url = "sqlite:///test.db"
engine = create_engine(sqlite_url, echo=True)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


def get_session():
    with Session(engine) as session:
        yield session


@app.on_event("startup")
def on_startup():
    create_db_and_tables()


@app.get("/teams/", response_model=List[TeamReadWithHeroes])
def get_teams(*, session: Session = Depends(get_session)) -> List[Team]:
    return session.exec(select(Team)).all()

Under the hood, FastAPI will basically call the TeamReadWithHeroes.from_orm() method on each Team instance returned by the database query. This will populate the heroes field on that model and give you the desired result:

[
  {
    "name": "theteam",
    "id": 1,
    "heroes": [
      {
        "name": "foo",
        "team_id": 1,
        "id": 1
      },
      {
        "name": "bar",
        "team_id": 1,
        "id": 2
      }
    ]
  }
]
Read more comments on GitHub >

github_iconTop Results From Across the Web

What does 'sa_relationship_kwargs={"lazy": "selectin"}' means ...
It chooses the relationship loading technique that SQLAlchemy should use. The loading of relationships falls into three categories; ...
Read more >
Read Data - SELECT - SQLModel
We are creating a SQLModel Hero class model and creating some records. ... Here we are only selecting the id and name columns....
Read more >
FastAPI - SQLModel Relationships and Alpine.js integration
This video will demonstrate how to define relationships using SQLModel, and apply these in a FastAPI project.We'll also look at integrating ...
Read more >
SQL models - dbt Developer Hub
A SQL model is a select statement. Models are defined in .sql files (typically in your models directory):. Each .sql file contains one...
Read more >
Relationship Loading Techniques
Subquery eager loading is detailed at Subquery Eager Loading. select IN loading - available via lazy='selectin' or the selectinload() option, ...
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