다대다 관계로 데이터 만들기
다대다 관계로 데이터 만들기 (Create Data with Many-to-Many Relationships)
멈춘 지점에서 이어서 데이터를 만들어 볼게요.
바로 이 다대다 관계에 링크 테이블로 데이터를 만들 거예요:
이전 코드에서 멈춘 지점부터 이어갈게요.
👀 전체 파일 미리 보기
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine
class HeroTeamLink(SQLModel, table=True):
team_id: int | None = Field(default=None, foreign_key="team.id", primary_key=True)
hero_id: int | None = Field(default=None, foreign_key="hero.id", primary_key=True)
class Team(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True)
headquarters: str
heroes: list["Hero"] = Relationship(back_populates="teams", link_model=HeroTeamLink)
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)
teams: list[Team] = Relationship(back_populates="heroes", link_model=HeroTeamLink)
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",
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()
출처: 공식문서
히어로 만들기
전처럼 create_heroes() 함수를 만들고, 그 안에서 팀과 히어로를 만들게요:
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]
)
이건 지금까지 해온 것과 아주 비슷해요.
팀 두 개를 만들고, 히어로 세 명을 만들어요.
유일하게 새로운 세부사항은 team 인자 대신 teams를 쓴다는 거예요. 그게 새 관계 속성의 이름이니까요. 그리고 더 중요하게, 팀 리스트를 넘겨요(단일 팀이라 하더라도).
Deadpond가 이제 두 팀에 속하는 게 보이죠?
커밋, 새로고침, 프린트
이제 전처럼 해 볼게요. 세션을 commit하고, 데이터를 refresh하고, 프린트해요:
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)
main에 추가하기
전처럼, 이 프로그램을 커맨드 라인에서 실행할 때 호출되도록 create_heroes() 함수를 main() 함수에 추가해요:
def main():
create_db_and_tables()
create_heroes()
프로그램 실행하기
커맨드 라인에서 프로그램을 실행하면 이렇게 출력돼요:
$ uv run python app.py
// 이전 출력은 생략했어요 🙈
// 자동으로 새 트랜잭션 시작
INFO Engine BEGIN (implicit)
// 먼저 히어로 데이터를 삽입
INFO Engine INSERT INTO hero (name, secret_name, age) VALUES (?, ?, ?)
INFO Engine [generated in 0.00041s] ('Deadpond', 'Dive Wilson', None)
INFO Engine INSERT INTO hero (name, secret_name, age) VALUES (?, ?, ?)
INFO Engine [cached since 0.001942s ago] ('Rusty-Man', 'Tommy Sharp', 48)
INFO Engine INSERT INTO hero (name, secret_name, age) VALUES (?, ?, ?)
INFO Engine [cached since 0.002541s ago] ('Spider-Boy', 'Pedro Parqueador', None)
// 두 번째로 팀 데이터를 삽입
INFO Engine INSERT INTO team (name, headquarters) VALUES (?, ?)
INFO Engine [generated in 0.00037s] ('Z-Force', 'Sister Margaret's Bar')
INFO Engine INSERT INTO team (name, headquarters) VALUES (?, ?)
INFO Engine [cached since 0.001239s ago] ('Preventers', 'Sharp Tower')
// 생성된 ID를 재사용할 수 있도록 링크 데이터를 마지막에 삽입
INFO Engine INSERT INTO heroteamlink (team_id, hero_id) VALUES (?, ?)
INFO Engine [generated in 0.00026s] ((2, 3), (1, 1), (2, 1), (2, 2))
// 커밋하고 데이터를 데이터베이스에 저장
INFO Engine COMMIT
// 자동으로 새 트랜잭션 시작
INFO Engine BEGIN (implicit)
// 데이터 새로고침
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [generated in 0.00019s] (1,)
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.001959s ago] (2,)
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
INFO Engine [cached since 0.003215s ago] (3,)
// Deadpond 프린트
Deadpond: name='Deadpond' age=None id=1 secret_name='Dive Wilson'
// .team 속성에 접근하면 새로고침이 일어남
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team, heroteamlink
WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id
INFO Engine [generated in 0.00025s] (1,)
// Deadpond의 팀을 프린트, 팀 2개! 🎉
Deadpond teams: [Team(id=1, name='Z-Force', headquarters='Sister Margaret's Bar'), Team(id=2, name='Preventers', headquarters='Sharp Tower')]
// Rusty-Man 프린트
Rusty-Man: name='Rusty-Man' age=48 id=2 secret_name='Tommy Sharp'
// .team 속성에 접근하면 새로고침이 일어남
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team, heroteamlink
WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id
INFO Engine [cached since 0.001716s ago] (2,)
// Rusty-Man 팀을 프린트, 하나지만 여전히 리스트
Rusty-Man Teams: [Team(id=2, name='Preventers', headquarters='Sharp Tower')]
// Spider-Boy 프린트
Spider-Boy: name='Spider-Boy' age=None id=3 secret_name='Pedro Parqueador'
// .team 속성에 접근하면 새로고침이 일어남
INFO Engine SELECT team.id AS team_id, team.name AS team_name, team.headquarters AS team_headquarters
FROM team, heroteamlink
WHERE ? = heroteamlink.hero_id AND team.id = heroteamlink.team_id
INFO Engine [cached since 0.002739s ago] (3,)
// Spider-Boy의 팀을 프린트, 하나지만 여전히 리스트
Spider-Boy Teams: [Team(id=2, name='Preventers', headquarters='Sharp Tower')]
// with 블록이 끝날 때 이전 자동 트랜잭션을 자동으로 롤백
INFO Engine ROLLBACK
요약
모델 링크를 설정하고 나면, 관계 속성으로 사용하는 건 꽤 간단해요. 그냥 파이썬 객체일 뿐이니까요. ✨