Skip to content
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

from __future__ import annotation and many to many example #196

Open
8 tasks done
5cat opened this issue Dec 18, 2021 · 14 comments
Open
8 tasks done

from __future__ import annotation and many to many example #196

5cat opened this issue Dec 18, 2021 · 14 comments
Labels
investigate question Further information is requested

Comments

@5cat
Copy link

5cat commented Dec 18, 2021

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

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

@5cat 5cat added the question Further information is requested label Dec 18, 2021
@Batalex
Copy link
Contributor

Batalex commented Jan 13, 2022

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.

@l1b3r
Copy link

l1b3r commented Jan 27, 2022

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...

@xavipolo
Copy link

xavipolo commented Sep 11, 2022

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

@agronholm
Copy link

It looks like the forward reference is not evaluated at all, but passed to SQLAlchemy as-is.

@NunchakusLei
Copy link

I experienced the same issue on version sqlmodel == 0.0.8

@JamesHutchison
Copy link

This is a pretty confusing problem to have given the selling point of this library.

@s-weigand
Copy link

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

@aholten
Copy link

aholten commented Jan 8, 2024

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.

@pawrequest
Copy link

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?

@DirkReiners
Copy link

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]...

@NunchakusLei
Copy link

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"]

@aholten
Copy link

aholten commented Feb 1, 2024

@JamesHutchison
Copy link

Just to double check - this isn't something that would be fixed by adding typing.get_type_hints(...) somewhere, would it?

@agronholm
Copy link

Seems reasonable to me that it would fix the problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
investigate question Further information is requested
Projects
None yet
Development

No branches or pull requests