관계 속성 정의하기

관계 속성 정의하기 (Define Relationship Attributes)

드디어 SQLModel에서 가장 흥미로운 부분 중 하나에 도달했어요. 바로 관계 속성(Relationship Attributes) 이야기예요. ✨ 지금까지는 team_id 같은 외래 키 컬럼으로 테이블을 직접 연결해서 다뤘다면, 이번 장부터는 모델 안에서 관계를 더 편리하게 다루는 방법을 배워볼게요.

출처: 공식문서

지금까지의 상황

우리가 다루는 team 테이블은 이렇게 생겼어요:

id name headquarters
1 Preventers Sharp Tower
2 Z-Force Sister Margaret's Bar

그리고 hero 테이블은 이렇게 생겼죠:

id name secret_name age team_id
1 Deadpond Dive Wilson null 2
2 Rusty-Man Tommy Sharp 48 1
3 Spider-Boy Pedro Parqueador null 1

테이블이 밑에서 어떻게 동작하는지, 모델 클래스가 그 테이블을 어떻게 나타내는지는 이미 배웠어요. 이제 코드에서 많은 작업을 훨씬 간단하게 만들어 줄 작은 편의 기능을 하나 더해볼 차례예요.

관계 속성 선언하기

지금까지는 쿼리에서 select()로 테이블을 연결할 때 team_id 컬럼만 사용했었죠:

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

# Code below omitted 👇

team_id는 다른 필드들과 똑같은 평범한 필드예요. 모두 테이블의 컬럼 하나씩을 나타내죠.

이제 이 모델 클래스들에 몇 가지 새로운 특별한 속성을 추가해 볼게요. 바로 관계(Relationship) 속성입니다.

먼저 sqlmodel에서 Relationship을 가져와요:

from sqlmodel import Field, Relationship, Session, SQLModel, create_engine

# Code below omitted 👇

그다음 이 Relationship으로 모델 클래스 안에 새 속성을 선언합니다:

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

# Code below omitted 👇

이 관계 속성들이 뭘까요?

이 새 속성들은 필드와는 달라요. 데이터베이스의 컬럼 하나를 직접 나타내지 않고, 그 값도 정수 같은 단일 값이 아니에요. 이 속성의 값은 실제로 연결된 전체 객체 그 자체입니다.

즉, Hero 인스턴스에서 hero.team을 호출하면, 이 히어로가 속한 Team 인스턴스 객체 전체를 돌려받아요. ✨

예를 들어 어떤 히어로가 팀에 속해 있는지(.teamNone이 아닌지) 확인하고, 그 팀 이름을 출력할 수도 있겠죠:

if hero.team:
    print(hero.team.name)

관계 속성 또는 None

Hero 클래스에서 team의 타입 어노테이션이 Team | None이라는 점을 눈여겨보세요.

이 말은 이 속성이 None일 수도, 완전한 Team 객체일 수도 있다는 뜻이에요.

관련된 team_id 역시 None(데이터베이스의 NULL)일 수 있기 때문이죠.

만약 Hero가 무조건 Team에 속해야 했다면, team_idint | None이 아니라 int가 되고, 그 FieldField(default=None, foreign_key="team.id")가 아니라 Field(foreign_key="team.id")가 되며, team 속성도 Team | None이 아니라 Team이 되었을 거예요.

리스트를 가진 관계 속성

반대로 Team 클래스에서 heroes 속성은 Hero 객체들의 리스트로 어노테이션되어 있어요. 실제로 그렇게 담기기 때문이죠.

SQLModel(정확히는 SQLAlchemy)은 team_id라는 외래 키가 hero 테이블에서 team 테이블을 가리키는 걸 보고, 관계가 그 team_id로 맺어진다는 걸 알아서 처리해요. 그래서 여기서 그것을 명시적으로 지정할 필요가 없답니다.

💡 팁

list["Hero"]back_populates에 대해서는 다음 몇 개 장에서 다시 확인하게 될 거예요.

지금은 일단 이 관계 속성들을 어떻게 사용하는지부터 살펴볼게요.

다음 단계

다음 장들에서 이 새로운 관계 속성들을 실제로 활용하는 예시들을 하나씩 살펴볼게요. ✨

더 알아보기 (Learn more)