관계 생성과 업데이트
관계 생성과 업데이트 (Create and Update Relationships)
이제 이 새로운 관계 속성들을 사용해서 데이터를 생성하는 방법을 살펴볼게요. ✨
출처: 공식문서
필드로 인스턴스 생성하기
먼저 예전에 우리가 히어로와 팀을 만들 때 썼던 코드를 확인해 볼게요:
# Code above omitted 👆
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")
session.add(team_preventers)
session.add(team_z_force)
session.commit()
hero_deadpond = Hero(
name="Deadpond", secret_name="Dive Wilson", team_id=team_z_force.id
)
hero_rusty_man = Hero(
name="Rusty-Man",
secret_name="Tommy Sharp",
age=48,
team_id=team_preventers.id,
)
hero_spider_boy = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
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("Created hero:", hero_deadpond)
print("Created hero:", hero_rusty_man)
print("Created hero:", hero_spider_boy)
# Code below omitted 👇
👀 전체 파일 미리보기 (Full file preview)
from sqlmodel import Field, Session, SQLModel, create_engine
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)
team_id: int | None = Field(default=None, foreign_key="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")
session.add(team_preventers)
session.add(team_z_force)
session.commit()
hero_deadpond = Hero(
name="Deadpond", secret_name="Dive Wilson", team_id=team_z_force.id
)
hero_rusty_man = Hero(
name="Rusty-Man",
secret_name="Tommy Sharp",
age=48,
team_id=team_preventers.id,
)
hero_spider_boy = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
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("Created hero:", hero_deadpond)
print("Created hero:", hero_rusty_man)
print("Created hero:", hero_spider_boy)
def main():
create_db_and_tables()
create_heroes()
if __name__ == "__main__":
main()
여기서 눈여겨볼 것들이 몇 가지 있어요.
첫째, 우리는 Team 인스턴스 객체들을 몇 개 만드는데, Hero 인스턴스들을 만들 때 team_id 필드에 이 팀들의 ID를 사용하고 싶어요.
하지만 모델 인스턴스는 세션에 추가하고 커밋하기 전까지는 데이터베이스가 생성한 ID를 갖고 있지 않아요. 그 전에는 그냥 None이죠. 그리고 우리는 실제 ID를 사용하고 싶어요.
그래서 Hero 인스턴스들을 만들기 시작하기 전에 먼저 팀들을 추가하고 세션을 커밋해야, 그들의 ID를 사용할 수 있어요.
그런 다음 그 ID들을 Hero 인스턴스를 만들 때 사용해요. 새 히어로들을 세션에 추가하고 나서 커밋하죠.
그래서 결국 두 번 커밋하게 되는 거예요. 그리고 어떤 것들을 먼저 추가한 뒤 커밋하고, 그 모든 걸 올바른 순서로 해야 해요. 그렇지 않으면 아직 저장되지 않아서 현재 None인 team.id를 사용하게 될 수도 있거든요.
이 관계 속성들이 처음으로 도움이 되는 부분이 바로 여기예요. 🤓
관계 속성으로 인스턴스 생성하기
이제 그 모든 걸 하지만, 이번에는 새롭고 반짝이는 관계(Relationship) 속성을 사용해서 해볼게요.
# Code above omitted 👆
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", team=team_z_force
)
hero_rusty_man = Hero(
name="Rusty-Man", secret_name="Tommy Sharp", age=48, team=team_preventers
)
hero_spider_boy = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
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("Created hero:", hero_deadpond)
print("Created hero:", hero_rusty_man)
print("Created hero:", hero_spider_boy)
# Code below omitted 👇
👀 전체 파일 미리보기 (Full file preview)
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine
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="team")
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)
team_id: int | None = Field(default=None, foreign_key="team.id")
team: Team | None = Relationship(back_populates="heroes")
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", team=team_z_force
)
hero_rusty_man = Hero(
name="Rusty-Man", secret_name="Tommy Sharp", age=48, team=team_preventers
)
hero_spider_boy = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
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("Created hero:", hero_deadpond)
print("Created hero:", hero_rusty_man)
print("Created hero:", hero_spider_boy)
hero_spider_boy.team = team_preventers
session.add(hero_spider_boy)
session.commit()
def main():
create_db_and_tables()
create_heroes()
if __name__ == "__main__":
main()
이제 우리는 Team 인스턴스들을 만들고, Hero 인스턴스를 만들 때 그 새 team 인자에 직접 전달할 수 있어요. team_id=team_preventers.id 대신 team=team_preventers처럼요.
그리고 내부에서 SQLAlchemy가 어떻게 동작하는지 덕분에, 이 팀들은 아직 ID를 갖고 있지 않아도 돼요. 우리가 전체 객체를 각 히어로에 할당하니까, 그 팀들은 데이터베이스에 자동으로 생성되고, 자동 ID가 만들어져서 각각의 대응하는 히어로 행의 team_id 컬럼에 설정될 거예요.
사실 이제는 session.add(team)으로 팀들을 명시적으로 세션에 넣을 필요조차 없어요. 왜냐하면 이 Team 인스턴스들은 이미 우리가 세션에 추가하는 히어로들과 연결되어 있기 때문이죠.
SQLAlchemy는 히어로들을 올바르게 저장하려면 그 팀들도 다음 커밋에 포함해야 한다는 걸 알고 있어요.
그리고 보시다시피 이제는 commit() 하나만 하면 돼요.
관계 할당하기
우리가 hero.team_id에 team.id라는 정수를 할당할 수 있었던 것과 같은 방식으로, hero.team에도 Team 인스턴스를 할당할 수 있어요:
# Code above omitted 👆
def create_heroes():
with Session(engine) as session:
# Code here omitted 👈
hero_spider_boy.team = team_preventers
session.add(hero_spider_boy)
session.commit()
session.refresh(hero_spider_boy)
print("Updated hero:", hero_spider_boy)
# Code below omitted 👇
히어로가 있는 팀 생성하기
앞에서는 Team 인스턴스들을 먼저 만든 뒤 Hero 인스턴스를 만들 때 team= 인자에 전달했어요.
반대로 Hero 인스턴스들을 먼저 만든 뒤, Team 인스턴스를 만들 때 리스트를 받는 heroes= 인자에 그들을 전달할 수도 있어요:
# Code above omitted 👆
def create_heroes():
with Session(engine) as session:
# Code here omitted 👈
hero_black_lion = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
hero_sure_e = Hero(name="Princess Sure-E", secret_name="Sure-E")
team_wakaland = Team(
name="Wakaland",
headquarters="Wakaland Capital City",
heroes=[hero_black_lion, hero_sure_e],
)
session.add(team_wakaland)
session.commit()
session.refresh(team_wakaland)
print("Team Wakaland:", team_wakaland)
# Code below omitted 👇
여기서는 두 히어로, Black Lion과 Princess Sure-E를 먼저 만들고, 그 다음 heroes 인자에 전달했어요.
앞과 마찬가지로 Team 인스턴스만 세션에 추가하면 되고, 히어로들이 그와 연결되어 있으므로 커밋할 때 그들도 자동으로 저장된다는 점을 눈여겨보세요.
다(Many) 쪽에서 관계 객체 포함하기
앞에서 이것이 다대일(many-to-one) 관계라고 했죠. 한 팀에 여러 히어로가 속할 수 있으니까요.
이 관계 속성들을 다(多) 쪽에서도 데이터를 연결하는 데 사용할 수 있어요.
team.heroes 속성은 리스트처럼 동작하므로, 그냥 append하면 돼요.
히어로들을 몇 개 더 만들고 team_preventers.heroes 리스트 속성에 추가해 볼게요:
# Code above omitted 👆
def create_heroes():
with Session(engine) as session:
# Code here omitted 👈
hero_tarantula = Hero(name="Tarantula", secret_name="Natalia Roman-on", age=32)
hero_dr_weird = Hero(name="Dr. Weird", secret_name="Steve Weird", age=36)
hero_cap = Hero(
name="Captain North America", secret_name="Esteban Rogelios", age=93
)
team_preventers.heroes.append(hero_tarantula)
team_preventers.heroes.append(hero_dr_weird)
team_preventers.heroes.append(hero_cap)
session.add(team_preventers)
session.commit()
session.refresh(hero_tarantula)
session.refresh(hero_dr_weird)
session.refresh(hero_cap)
print("Preventers new hero:", hero_tarantula)
print("Preventers new hero:", hero_dr_weird)
print("Preventers new hero:", hero_cap)
# Code below omitted 👇
team_preventers.heroes 속성은 리스트처럼 동작해요. 하지만 특별한 종류의 리스트인데, 히어로들을 추가해서 수정하면 SQLModel(정확히는 SQLAlchemy)이 데이터베이스에서 해야 할 필요한 변경 사항을 추적하기 때문이에요.
그런 다음 팀을 add()하고 commit() 해요.
그리고 앞과 똑같이, 히어로들은 팀에 연결되어 있으므로 개별 히어로들을 세션에 add() 할 필요조차 없어요.
정리
이 관계 속성들을 사용하면 평범한 Python 객체와 속성으로 데이터 연결을 만들고 업데이트할 수 있어요. 😎
다음에는 이 관계 속성들로 연결된 데이터를 읽는 방법을 살펴볼게요. 🤝