Cyclic import error with Multiple files #1525
-
Privileged issue
Issue ContentAt the moment, if you split the model file into different folders, the cyclic import error will appear. Decision: from __future__ import annotations
if TYPE_CHECKING:
from models.general_name_models import Model1, Model2 it did not produce results, because when accessing models imported under Therefore, in order for all the imports to work adequately and there were no problems with requests, we had to remove all the Relationship's. If all the Relationships are removed, then the project starts and even functions (partially), but in this case we will have to rewrite most of the functions from db , since in many places we join as follows (example):
This query won't work because the Team model won't see the hero attribute. Otherwise, if you don't choose a Relationship (for example, a team.hero), you can get them yourself: Below are some examples of my code. link.py: class HeroTeamLink(SQLModel, table=True):
hero_id: int = Field(foreign_key="hero.id", primary_key=True)
team_id: int = Field(foreign_key="team.id", primary_key=True) team.py: from __future__ import annotations
from typing import TYPE_CHECKING
from sqlmodel import (
Relationship,
SQLModel,
)
if TYPE_CHECKING:
from models.hero import Hero
class Team(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str
heroes: list["Hero"] = Relationship(
# secondary="hero",
back_populates="team",
link_model=HeroTeamLink,
) hero.py: from __future__ import annotations
from typing import TYPE_CHECKING
from sqlmodel import (
Relationship,
SQLModel,
)
if TYPE_CHECKING:
from models.team import Team
from models.link import HeroTeamLink
class Hero(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str
teams: list["Team"] = Relationship(
back_populates="hero", link_model=HeroTeamLink
) |
Beta Was this translation helpful? Give feedback.
Replies: 12 comments
-
I solved this cyclic import problem by moving the import to the end of the file and calling pydantic's model_rebuild method. You can read more in the Pydantic docs |
Beta Was this translation helpful? Give feedback.
-
Hi @bentoluizv, thank you, your solution works, but in my case it is used many-to-many, and at this point an error occurs. I have 3 files, each of which has models. from models.link_models import ServiceQualificationLink
class Service(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
qualification: list["Qualification"] = Relationship(
back_populates="service",
# link_model=ServiceQualificationLink,
)
from models.qualifications import Qualification
Service.model_rebuild() _*_*_*_*_*_*_*_** from models.link_models import ServiceQualificationLink
class Qualification(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
service: list["Service"] = Relationship(
back_populates="qualification" ,
# link_model=ServiceQualificationLink
)
from models.services import Service
Qualification.model_rebuild() _*_*_*_*_*_*_*_** from sqlmodel import Field, SQLModel
class ServiceQualificationLink(SQLModel, table=True):
service_id: int | None = Field(default=None, foreign_key="services.id", primary_key=True)
qualification_id: int | None = Field(
default=None, foreign_key="qualifications.id", primary_key=True
) _*_*_*_*_*_*_*_** If I have a commented out link_model string, then I get the following error:
If this field is not commented out, then the error is already like this:
Here are the versions of the libraries that I use: |
Beta Was this translation helpful? Give feedback.
-
As you rightly mentioned: And that is why while annotating qualification here: qualification: list["Qualification"] You have defined Qualification as a literal string by adding quotation marks "Qualification" However, when accessing the link_model - ServiceQualificationLink - in Relationship, you have not defined it as a literal string. To resolve your issue, have it as: qualification: list["Qualification"] = Relationship(
back_populates="service",
link_model="ServiceQualificationLink"
) and that should solve your issue. I reproduced your previous code hero example as follows and it worked perfectly: team.py from typing import TYPE_CHECKING
from sqlmodel import Relationship, SQLModel, Field
if TYPE_CHECKING:
from .hero import Hero
from .link import HeroTeamLink
class Team(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str
heroes: list["Hero"] = Relationship(
# secondary="hero",
back_populates="team",
link_model="HeroTeamLink",
) hero.py from typing import TYPE_CHECKING
from sqlmodel import Relationship, SQLModel, Field
if TYPE_CHECKING:
from .team import Team
from .link import HeroTeamLink
class Hero(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str
teams: list["Team"] = Relationship(back_populates="hero", link_model="HeroTeamLink") link.py from sqlmodel import SQLModel, Field
class HeroTeamLink(SQLModel, table=True):
hero_id: int = Field(foreign_key="hero.id", primary_key=True)
team_id: int = Field(foreign_key="team.id", primary_key=True) |
Beta Was this translation helpful? Give feedback.
-
Hi @Kahacho, thank you for your help. I used your code examples, but I got the following error:
Here are the code examples: from typing import TYPE_CHECKING
from sqlmodel import Relationship, SQLModel, Field
if TYPE_CHECKING:
from .team import Team
from .link import HeroTeamLink
class Hero(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str
teams: list["Team"] = Relationship(back_populates="hero", link_model="HeroTeamLink") *_*_*_*_*__* team.py from typing import TYPE_CHECKING
from sqlmodel import Relationship, SQLModel, Field
if TYPE_CHECKING:
from .hero import Hero
from .link import HeroTeamLink
class Team(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str
heroes: list["Hero"] = Relationship(
# secondary="hero",
back_populates="team",
link_model="HeroTeamLink",
) *_*_*_*_*__* link.py from sqlmodel import SQLModel, Field
class HeroTeamLink(SQLModel, table=True):
hero_id: int = Field(foreign_key="hero.id", primary_key=True)
team_id: int = Field(foreign_key="team.id", primary_key=True) Can you tell me your library versions and what could I have reproduced wrong? |
Beta Was this translation helpful? Give feedback.
-
In your from typing import TYPE_CHECKING
if TYPE_CHECKING:
from team import Team
from hero import Hero |
Beta Was this translation helpful? Give feedback.
-
@Kahacho, thanks a lot, it solved my problem. |
Beta Was this translation helpful? Give feedback.
-
@sebastianfym Glad it worked out. |
Beta Was this translation helpful? Give feedback.
-
Hello, I was having the same issue (defining the link model of a many-to-many relationship into a different file leads to circular imports), but I don't understand how the proposed solution solves the issue. Taking the example from above:
For future reference, the solution is actually straightforward: import the EXCEPT if you define naming conventions ( Solutions to avoid this:
|
Beta Was this translation helpful? Give feedback.
-
But then you would have to use a forward annotation everywhere you use the models right? |
Beta Was this translation helpful? Give feedback.
-
Hi @lucasfelber, yes. For additional context, I recommend reading the-future-of-fastapi-and-pydantic-is-bright by @tiangolo. It provides valuable insight into the reasoning behind the changes and the approach I previously suggested. |
Beta Was this translation helpful? Give feedback.
-
@Kahacho thanks for the interesting read. I need to instantiate the Team and Hero objects in my main.py file, so I need to import the actual classes at runtime, thus my comment. But maybe in your answer you only need annotations? |
Beta Was this translation helpful? Give feedback.
-
@sebastianfym, @thjungers, See code in details: main.py from sqlmodel import Session, SQLModel, create_engine, select
from .hero import Hero
from .team import Team
def test_():
h1 = Hero(name="me")
t1 = Team(name="team 1")
h1.teams.append(t1)
engine = create_engine("sqlite://")
SQLModel.metadata.create_all(engine)
with Session(engine) as session:
session.add(h1)
session.add(t1)
session.commit()
with Session(engine) as session:
query_hero = session.scalar(
select(Hero).limit(1)
)
assert query_hero
assert query_hero.id == 1
assert query_hero.name == "me"
assert query_hero.teams[0].name == "team 1" hero.py from typing import TYPE_CHECKING
from sqlmodel import Field, Relationship, SQLModel
from .link import HeroTeamLink
if TYPE_CHECKING:
from team import Team
class Hero(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str
teams: list["Team"] = Relationship(back_populates="heroes", link_model=HeroTeamLink) team.py from typing import TYPE_CHECKING
from sqlmodel import Field, Relationship, SQLModel
from .link import HeroTeamLink
if TYPE_CHECKING:
from .hero import Hero
class Team(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str
heroes: list["Hero"] = Relationship(
back_populates="teams",
link_model=HeroTeamLink,
) link.py from sqlmodel import Field, SQLModel
class HeroTeamLink(SQLModel, table=True):
hero_id: int = Field(foreign_key="hero.id", primary_key=True)
team_id: int = Field(foreign_key="team.id", primary_key=True) All files in the archive: files.zip |
Beta Was this translation helpful? Give feedback.
@sebastianfym, @thjungers,
Could you please check if this works for you?
See code in details:
main.py