sqlmodel
sqlmodel copied to clipboard
from __future__ import annotation and many to many example
First Check
- [X] I added a very descriptive title to this issue.
- [X] I used the GitHub search to find a similar issue and didn't find it.
- [X] I searched the SQLModel documentation, with the integrated search.
- [X] I already searched in Google "How to X in SQLModel" and didn't find any information.
- [X] I already read and followed all the tutorial in the docs and didn't find an answer.
- [X] I already checked if it is not related to SQLModel but to Pydantic.
- [X] I already checked if it is not related to SQLModel but to SQLAlchemy.
Commit to Help
- [X] I commit to help with one of those options 👆
Example Code
from __future__ import annotations
from typing import List, Optional
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine
class HeroTeamLink(SQLModel, table=True):
team_id: Optional[int] = Field(
default=None, foreign_key="team.id", primary_key=True
)
hero_id: Optional[int] = Field(
default=None, foreign_key="hero.id", primary_key=True
)
class Team(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
headquarters: str
heroes: List[Hero] = Relationship(back_populates="teams", link_model=HeroTeamLink)
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
secret_name: str
age: Optional[int] = None
teams: List[Team] = Relationship(back_populates="heroes", link_model=HeroTeamLink)
sqlite_url = f"sqlite://"
engine = create_engine(sqlite_url, echo=True)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
def create_heroes():
with Session(engine) as session:
team_preventers = Team(name="Preventers", headquarters="Sharp Tower")
team_z_force = Team(name="Z-Force", headquarters="Sister Margaret’s Bar")
hero_deadpond = Hero(
name="Deadpond",
secret_name="Dive Wilson",
teams=[team_z_force, team_preventers],
)
hero_rusty_man = Hero(
name="Rusty-Man",
secret_name="Tommy Sharp",
age=48,
teams=[team_preventers],
)
hero_spider_boy = Hero(
name="Spider-Boy", secret_name="Pedro Parqueador", teams=[team_preventers]
)
session.add(hero_deadpond)
session.add(hero_rusty_man)
session.add(hero_spider_boy)
session.commit()
session.refresh(hero_deadpond)
session.refresh(hero_rusty_man)
session.refresh(hero_spider_boy)
print("Deadpond:", hero_deadpond)
print("Deadpond teams:", hero_deadpond.teams)
print("Rusty-Man:", hero_rusty_man)
print("Rusty-Man Teams:", hero_rusty_man.teams)
print("Spider-Boy:", hero_spider_boy)
print("Spider-Boy Teams:", hero_spider_boy.teams)
def main():
create_db_and_tables()
create_heroes()
if __name__ == "__main__":
main()
Description
all what i have done is use the example from the docs about many to many relationships and tried to fit them with from __future__ import annotation
by adding the import in the first line and changing List["Hero"]
to List[Hero]
. and i get this error
sqlalchemy.exc.InvalidRequestError: When initializing mapper mapped class Team->team, expression 'List[Hero]' failed to locate a name ('List[Hero]'). If this is a class name, consider adding this relationship() to the <class '__main__.Team'> class after both dependent classes have been defined.
also update_forward_refs()
did not help for both classes.
Operating System
Linux
Operating System Details
No response
SQLModel Version
0.0.4
Python Version
Python 3.9.9
Additional Context
No response
Hi, I have the same issue with sqlmodel 0.0.6 and python 3.9.9.
I have created a similar virtual env with python 3.8.10 and this piece of code works as expected.
I had a similar issue with 1-M relationship, where the models are also separated into their own files.
sqlmodel == 0.0.6
python == 3.9
Not sure if that helps, but I was able to workaround this issue by removing
from __future__ import annotations
from the file of the "parent" model and returning back to quoting the type hints:
# other imports omitted
from typing import TYPE_CHECKING:
from .child import ChildModel
class ParentModel(SQLModel, table=True):
# children: List[ChildModel] = Relationship(back_populates="parent") # does not work
children: List["ChildModel"] = Relationship(back_populates="parent") # OK
# other column definitions...
I had a similar issue with 1-M relationship, where the models are also separated into their own files.
sqlmodel == 0.0.6 python == 3.9
@l1b3r Do you updated to lastest versions?
I am having some problems with the models separated in different files. The example model of Heroes and Teams, works fine with separate files until I add the model that allows to get a Team with its Heroes. https://sqlmodel.tiangolo.com/tutorial/fastapi/relationships/#update-the-path-operations
Are you using this option, does it work for you?
Thanks
It looks like the forward reference is not evaluated at all, but passed to SQLAlchemy as-is.
I experienced the same issue on version sqlmodel == 0.0.8
This is a pretty confusing problem to have given the selling point of this library.
Postponed annotations are still an issue with 0.0.14
, hence the ruff setting.
I guess it has to do with all the very dark black magic
that SQLAlchemy
does 😅
I also hit this issue (again) with a new project, But removing
from __future__ import annotations
fixed it (again). Just in case someone else hits this issue
Thank you @s-weigand, I removed the annotations
import and SQLAlchemy stopped complaining that classes weren't mapped!
Would have saved me time if I was told that future annotations cannot be used, and that forward type references in SQLModel classes should be enclosed in double quotes to play nice with SA. I guess I should have gleaned this from the docs, on the page about type annotation strings? Considering this issue thread though, I think it's worth addressing explicitly somewhere. I'll look into adding this caveat to the docs if contributions are accepted.
yeah i lost loads of time to this, definitely think 'don't import annotations from future' should be stated somewhere in the docs. unless i missed it?
While documentation is important, is there a plan or a route to fix this? Strings don't work for nice things like Optional[A | B | C]
...
While documentation is important, is there a plan or a route to fix this? Strings don't work for nice things like
Optional[A | B | C]
...
I think you can do something like this Optional["A" | "B" | "C"]
@DirkReiners Forward referenced type annotations are a planned future feature for Python, so I wouldn't expect this library to implement a fix.
I explain the situation and state of the feature in a bit more detail in my PR
Just to double check - this isn't something that would be fixed by adding typing.get_type_hints(...)
somewhere, would it?
Seems reasonable to me that it would fix the problem.