-
-
Notifications
You must be signed in to change notification settings - Fork 781
Tests and example for how to use relationship alias. #815
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
Changes from 1 commit
bd2d61a
d5f78f2
870dc4c
fe0187c
10b79b9
bf737bf
f419d74
dfdaf23
d514df2
1f80fcb
a6e1492
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,113 @@ | ||||||||||||||||||||||||||||||||||
| # Aliased Relationships | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| ## Multiple Relationships to the Same Model | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| We've seen how tables are related to each other via a single relationship attribute but what if more than | ||||||||||||||||||||||||||||||||||
| one attribute links to the same table? | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| What if you have a `User` model and an `Address` model and would like | ||||||||||||||||||||||||||||||||||
| to have `User.home_address` and `User.work_address` relationships to the same | ||||||||||||||||||||||||||||||||||
| `Address` model? In SQL you do this by creating a table alias using `AS` like this: | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||
| SELECT * | ||||||||||||||||||||||||||||||||||
| FROM user | ||||||||||||||||||||||||||||||||||
| JOIN address AS home_address_alias | ||||||||||||||||||||||||||||||||||
| ON user.home_address_id == home_address_alias.id | ||||||||||||||||||||||||||||||||||
| JOIN address AS work_address_alias | ||||||||||||||||||||||||||||||||||
| ON user.work_address_id == work_address_alias.id | ||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| The aliases we create are `home_address_alias` and `work_address_alias`. You can think of them | ||||||||||||||||||||||||||||||||||
| as a view to the same underlying `address` table. | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| We can do this with **SQLModel** and **SQLAlchemy** using `sqlalchemy.orm.aliased` | ||||||||||||||||||||||||||||||||||
| and a couple of extra bits of info in our **SQLModel** relationship definition and join statements. | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| ## The Relationships | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Let's define a `winter_team` and `summer_team` relationship for our heros. They can be on different | ||||||||||||||||||||||||||||||||||
| winter and summer teams or on the same team for both seasons. | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| ```Python hl_lines="11 15" | ||||||||||||||||||||||||||||||||||
| # Code above omitted 👆 | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| {!./docs_src/tutorial/relationship_attributes/aliased_relationship/tutorial001.py[ln:13-26]!} | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| # Code below omitted 👇 | ||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /// details | 👀 Full file preview | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| ```Python | ||||||||||||||||||||||||||||||||||
| {!./docs_src/tutorial/relationship_attributes/aliased_relationship/tutorial001.py!} | ||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
| ```Python hl_lines="11 15" | |
| # Code above omitted 👆 | |
| {!./docs_src/tutorial/relationship_attributes/aliased_relationship/tutorial001.py[ln:13-26]!} | |
| # Code below omitted 👇 | |
| ``` | |
| /// details | 👀 Full file preview | |
| ```Python | |
| {!./docs_src/tutorial/relationship_attributes/aliased_relationship/tutorial001.py!} | |
| ``` | |
| /// | |
| {* ./docs_src/tutorial/relationship_attributes/aliased_relationship/tutorial001_py310.py ln[13:26] hl[11,15] *} |
The format of includes has been changed and now it's much simpler
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about adding a "Get hero's teams" section and show how easy it is to access Hero.winter_team and Hero.summer_team?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this what you were thinking about? https://github.com/fastapi/sqlmodel/pull/815/files#diff-80f9757360022c326b802fa59508223afc314174233caa81da4aa1091c56a643R90
This comment was marked as duplicate.
This comment was marked as duplicate.
Sorry, something went wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant something like
hero = session.get(Hero, hero_id)
print(f"Hero: {hero.name}, Winter Team: {hero.winter_team.name} Summer Team: {hero.summer_team.name}")
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| from typing import Optional | ||
|
|
||
| from sqlalchemy.orm import aliased | ||
| from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select | ||
|
|
||
|
|
||
| class Team(SQLModel, table=True): | ||
| id: Optional[int] = Field(default=None, primary_key=True) | ||
| name: str = Field(index=True) | ||
| headquarters: str | ||
|
|
||
|
|
||
| class Hero(SQLModel, table=True): | ||
| id: Optional[int] = Field(default=None, primary_key=True) | ||
| name: str = Field(index=True) | ||
| secret_name: str | ||
| age: Optional[int] = Field(default=None, index=True) | ||
|
|
||
| winter_team_id: Optional[int] = Field(default=None, foreign_key="team.id") | ||
| winter_team: Optional[Team] = Relationship( | ||
| sa_relationship_kwargs={"primaryjoin": "Hero.winter_team_id == Team.id"} | ||
| ) | ||
| summer_team_id: Optional[int] = Field(default=None, foreign_key="team.id") | ||
| summer_team: Optional[Team] = Relationship( | ||
| sa_relationship_kwargs={"primaryjoin": "Hero.summer_team_id == Team.id"} | ||
| ) | ||
|
|
||
|
|
||
| sqlite_file_name = "database.db" | ||
| sqlite_url = f"sqlite:///{sqlite_file_name}" | ||
|
|
||
| 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", | ||
| winter_team=team_preventers, | ||
| summer_team=team_z_force, | ||
| ) | ||
| hero_rusty_man = Hero( | ||
| name="Rusty-Man", | ||
| secret_name="Tommy Sharp", | ||
| age=48, | ||
| winter_team=team_preventers, | ||
| summer_team=team_preventers, | ||
| ) | ||
| session.add(hero_deadpond) | ||
| session.add(hero_rusty_man) | ||
| session.commit() | ||
|
|
||
| session.refresh(hero_deadpond) | ||
| session.refresh(hero_rusty_man) | ||
|
|
||
| print("Created hero:", hero_deadpond) | ||
| print("Created hero:", hero_rusty_man) | ||
|
|
||
|
|
||
| def select_heroes(): | ||
| with Session(engine) as session: | ||
| winter_alias = aliased(Team) | ||
|
|
||
| # Heros with winter team as the Preventers | ||
| result = session.exec( | ||
| select(Hero) | ||
| .join(winter_alias, onclause=Hero.winter_team_id == winter_alias.id) | ||
| .where(winter_alias.name == "Preventers") | ||
| ) | ||
| heros = result.all() | ||
| print("Heros with Preventers as their winter team:", heros) | ||
| assert len(heros) == 2 | ||
|
|
||
| summer_alias = aliased(Team) | ||
| # Heros with Preventers as their winter team and Z-Force as their summer team | ||
| result = session.exec( | ||
| select(Hero) | ||
| .join(winter_alias, onclause=Hero.winter_team_id == winter_alias.id) | ||
| .where(winter_alias.name == "Preventers") | ||
| .join(summer_alias, onclause=Hero.summer_team_id == summer_alias.id) | ||
| .where(summer_alias.name == "Z-Force") | ||
| ) | ||
| heros = result.all() | ||
| print( | ||
| "Heros with Preventers as their winter and Z-Force as their summer team:", | ||
| heros, | ||
| ) | ||
| assert len(heros) == 1 | ||
| assert heros[0].name == "Deadpond" | ||
|
|
||
|
|
||
| def main(): | ||
| create_db_and_tables() | ||
| create_heroes() | ||
| select_heroes() | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| from sqlalchemy.orm import aliased | ||
| from sqlmodel import Field, Relationship, Session, SQLModel, create_engine, select | ||
|
|
||
|
|
||
| class Team(SQLModel, table=True): | ||
| id: int | None = Field(default=None, primary_key=True) | ||
| name: str = Field(index=True) | ||
| headquarters: str | ||
|
|
||
|
|
||
| class Hero(SQLModel, table=True): | ||
| id: int | None = Field(default=None, primary_key=True) | ||
| name: str = Field(index=True) | ||
| secret_name: str | ||
| age: int | None = Field(default=None, index=True) | ||
|
|
||
| winter_team_id: int | None = Field(default=None, foreign_key="team.id") | ||
| winter_team: Team | None = Relationship( | ||
| sa_relationship_kwargs={"primaryjoin": "Hero.winter_team_id == Team.id"} | ||
| ) | ||
| summer_team_id: int | None = Field(default=None, foreign_key="team.id") | ||
| summer_team: Team | None = Relationship( | ||
| sa_relationship_kwargs={"primaryjoin": "Hero.summer_team_id == Team.id"} | ||
|
||
| ) | ||
|
|
||
|
|
||
| sqlite_file_name = "database.db" | ||
| sqlite_url = f"sqlite:///{sqlite_file_name}" | ||
|
|
||
| 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", | ||
| winter_team=team_preventers, | ||
| summer_team=team_z_force, | ||
| ) | ||
| hero_rusty_man = Hero( | ||
| name="Rusty-Man", | ||
| secret_name="Tommy Sharp", | ||
| age=48, | ||
| winter_team=team_preventers, | ||
| summer_team=team_preventers, | ||
| ) | ||
| session.add(hero_deadpond) | ||
| session.add(hero_rusty_man) | ||
| session.commit() | ||
|
|
||
| session.refresh(hero_deadpond) | ||
| session.refresh(hero_rusty_man) | ||
|
|
||
| print("Created hero:", hero_deadpond) | ||
| print("Created hero:", hero_rusty_man) | ||
|
|
||
|
|
||
| def select_heroes(): | ||
| with Session(engine) as session: | ||
| winter_alias = aliased(Team) | ||
|
|
||
| # Heros with winter team as the Preventers | ||
| result = session.exec( | ||
| select(Hero) | ||
| .join(winter_alias, onclause=Hero.winter_team_id == winter_alias.id) | ||
| .where(winter_alias.name == "Preventers") | ||
| ) | ||
| heros = result.all() | ||
| print("Heros with Preventers as their winter team:", heros) | ||
| assert len(heros) == 2 | ||
|
|
||
| summer_alias = aliased(Team) | ||
|
|
||
| # Heros with Preventers as their winter team and Z-Force as their summer team | ||
| result = session.exec( | ||
| select(Hero) | ||
| .join(winter_alias, onclause=Hero.winter_team_id == winter_alias.id) | ||
| .where(winter_alias.name == "Preventers") | ||
| .join(summer_alias, onclause=Hero.summer_team_id == summer_alias.id) | ||
| .where(summer_alias.name == "Z-Force") | ||
| ) | ||
| heros = result.all() | ||
| print( | ||
| "Heros with Preventers as their winter and Z-Force as their summer team:", | ||
| heros, | ||
| ) | ||
| assert len(heros) == 1 | ||
| assert heros[0].name == "Deadpond" | ||
|
|
||
|
|
||
| def main(): | ||
| create_db_and_tables() | ||
| create_heroes() | ||
| select_heroes() | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
Uh oh!
There was an error while loading. Please reload this page.