Skip to content

Cyclic import error with Multiple files #1358

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
1 task done
sebastianfym opened this issue Apr 29, 2025 · 7 comments
Open
1 task done

Cyclic import error with Multiple files #1358

sebastianfym opened this issue Apr 29, 2025 · 7 comments

Comments

@sebastianfym
Copy link

sebastianfym commented Apr 29, 2025

Privileged issue

  • I'm @tiangolo or he asked me directly to create an issue here.

Issue Content

At 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 if TYPE_CHECKING, they are considered strings, and in this case, the Relationship does not work.

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):
joinedload(Team.hero)

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:
sqlalchemy.exc.InvalidRequestError: When initializing mapper Mapper[Hero(hero)] the expression "relationship("list['Team']")" appears to use a common class as an argument for relationship(); please specify a common argument using an annotation, such as "team: Mapped[list['Team']] = communication()"

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
)

###############################################################

@sebastianfym sebastianfym changed the title the problem with cyclic import in SQLmodel Cyclic import error with Multiple files Apr 30, 2025
@bentoluizv
Copy link

Hi @sebastianfym

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

Image

Image

@sebastianfym
Copy link
Author

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.
Here is my service file:

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()
_*_*_*_*_*_*_*_**
Here is my qualifications file:

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()
_*_*_*_*_*_*_*_**
This is my link_model_file

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:

sqlalchemy.exc.InvalidRequestError: When initializing mapper Mapper[Qualification(qualifications)], expression "relationship("list['Service']")" seems to be using a generic class as the argument to relationship(); please state the generic argument using an annotation, e.g. "service: Mapped[list['Service']] = relationship()"

If this field is not commented out, then the error is already like this:

sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Triggering mapper: 'Mapper[Qualification(qualifications)]'. Original exception was: When initializing mapper Mapper[Qualification(qualifications)], expression "relationship("list['Service']")" seems to be using a generic class as the argument to relationship(); please state the generic argument using an annotation, e.g. "service: Mapped[list['Service']] = relationship()"

Here are the versions of the libraries that I use:
SQLAlchemy = 2.0.40
sqlmodel = 0.0.24
Python 3.13.1

@Kahacho
Copy link

Kahacho commented May 6, 2025

Hi @sebastianfym

As you rightly mentioned:

"it did not produce results, because when accessing models imported under if TYPE_CHECKING, they are considered strings, and in this case, the Relationship does not work."

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)

@sebastianfym
Copy link
Author

Hi @Kahacho, thank you for your help. I used your code examples, but I got the following error:

Traceback (most recent call last):
File "/Users/bogdanvelikorodov/projects/test_1/main.py", line 4, in
from hero import Hero
File "/Users/bogdanvelikorodov/projects/test_1/hero.py", line 10, in
class Hero(SQLModel, table=True):
...<2 lines>...
teams: list["Team"] = Relationship(back_populates="hero", link_model="HeroTeamLink")
File "/Users/bogdanvelikorodov/projects/test_1/.venv/lib/python3.13/site-packages/sqlmodel/main.py", line 623, in init
ins = inspect(rel_info.link_model)
File "/Users/bogdanvelikorodov/projects/test_1/.venv/lib/python3.13/site-packages/sqlalchemy/inspection.py", line 147, in inspect
raise exc.NoInspectionAvailable(
...<2 lines>...
)
sqlalchemy.exc.NoInspectionAvailable: No inspection system is available for object of type <class 'str'>

Here are the code examples:
*_*_*_*_*__*
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")

*_*_*_*_*__*

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?

@Kahacho
Copy link

Kahacho commented May 6, 2025

Hi @sebastianfym

In your main.py, use TYPE_CHECKING as well and import as follows:

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from team import Team
    from hero import Hero

@sebastianfym
Copy link
Author

@Kahacho, thanks a lot, it solved my problem.

@Kahacho
Copy link

Kahacho commented May 6, 2025

@sebastianfym Glad it worked out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants