3.3장: Pydantic을 사용한 데이터 모델
학습 시간: 50분
1. Pydantic: 우주선의 "디지털 청사진"
우주선을 구축한다고 상상해 보세요. 무작정 금속 조각들을 용접할 수는 없습니다. 다음과 같은 상세한 청사진이 필요합니다:
- 선박명 (타입:
문자열
, 최대 길이: 50 문자) - 발사 연도 (타입:
정수
) - 초광속 엔진 여부 (타입:
부울
)
Pydantic은 Python에서 데이터에 대한 이러한 "디지털 청사진"을 생성할 수 있게 해주는 라이브러리입니다. FastAPI에서는 세 가지 주요 기능을 수행합니다:
- 구조 선언: 데이터가 어떤 필드로 구성되어 있는지 명확하게 설명합니다.
- 데이터 유효성 검사: 들어오는 데이터가 청사진에 일치하는지 자동으로 확인합니다.
- 문서화: FastAPI는 이 청사진들을 사용하여 상세하고 인터랙티브한 문서를 생성합니다.
💡 우주 비유: Pydantic 모델은 객체의 기술 사양서입니다. 스테이션에 도착하는 모든 "화물"(데이터)은 사양서의 사양과 일치해야 합니다. 그렇지 않으면 온보드 컴퓨터(Pydantic)가 이를 거부합니다.
2. 첫 번째 청사진 생성: Spaceship
모델
이제 우리 우주선을 설명할 모델을 만들어 봅시다.
1단계: Pydantic에서 BaseModel
임포트
Pydantic은 fastapi[all]
과 함께 이미 설치되어 있습니다. 우리는 모델의 기본 클래스만 임포트하면 됩니다.
main.py
의 맨 위에 다른 임포트 옆에 다음을 추가하세요:
2단계: Spaceship
모델 설명
BaseModel
로부터 상속받는 클래스를 생성합니다. 클래스 내부에 표준 Python 타입 힌트를 사용하여 필드와 그 타입을 정의합니다.
main.py
에 다음 코드를 추가하세요 (임포트 후에 추가 가능):
class Spaceship(BaseModel):
"""
우주선의 기술 사양서(모델).
"""
name: str
type: str
launch_year: int
status: str
Spaceship
타입의 모든 객체는 지정된 타입의 네 가지 필드를 가져야 한다는 것을 알고 있습니다.
3. 모델 적용: 엔드포인트 개선
이제 새 모델을 사용하여 API를 "더 스마트하게" 만들어 봅시다.
A. 응답 모델로서의 모델 (Response Model)
FastAPI에 우리의 엔드포인트가 Spaceship
모델과 일치하는 데이터를 반환해야 한다고 지정할 수 있습니다. 이는 응답이 항상 올바른 구조를 가질 것이라는 점을 보장합니다.
/spaceships/{ship_id}
엔드포인트를 다음과 같이 변경하세요:
# main.py
# ... db_spaceships 및 Spaceship 모델 코드 ...
# `response_model`을 사용하여 응답의 "청사진"을 지정합니다.
@app.get("/spaceships/{ship_id}", response_model=Spaceship)
def get_spaceship(ship_id: int):
"""
Spaceship 모델과 일치하는 선박 데이터를 반환합니다.
"""
ship = db_spaceships.get(ship_id)
return ship
response_model=Spaceship
: 우리는 FastAPI에 말합니다: "이 함수의 응답은 Spaceship
구조와 일치해야 합니다. 모든 불필요한 필드를 필터링하고 타입이 올바른지 확인해."
이것이 제공하는 것은 무엇입니까?
- 데이터 필터링: 만약
db_spaceships
에 불필요한 필드(예:"secret_code"
)가 있었다면, 최종 JSON에 포함되지 않았을 것입니다. - 구조 보장: API 클라이언트는 항상 예상된 형식으로 응답을 받을 것이라는 점을 확신할 수 있습니다.
- 문서화:
/docs
에서는 이제 정확한 응답 예시(Example Value)가 표시됩니다.
B. 컬렉션을 위한 모델
선박 목록을 반환하는 /spaceships
엔드포인트는 어떻게 될까요? 이를 위해 typing
모듈에서 list
를 사용해야 합니다.
임포트와 /spaceships
엔드포인트를 변경하세요:
# main.py 상단
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List # <-- List 임포트
# ... 코드 ...
# 응답이 Spaceship 타입 객체의 목록(List)임을 지정합니다.
@app.get("/spaceships", response_model=List[Spaceship])
def get_spaceships():
"""
선박 목록을 반환합니다. 목록의 각 요소는
Spaceship 모델에 따라 검사됩니다.
"""
# Pydantic은 키가 ID인 딕셔너리와 함께 작동할 수 없습니다.
# 우리의 딕셔너리를 간단한 목록으로 변환합니다.
return list(db_spaceships.values())
response_model=List[Spaceship]
: 우리는 응답이Spaceship
모델과 일치하는 객체로 구성된 목록임을 지정합니다.return list(db_spaceships.values())
: 중요한 변경 사항입니다! Pydantic은 키가 ID인 딕셔너리가 아닌, 반복 가능한 객체(목록)를 예상합니다. 우리는 "DB 시뮬레이터"의 값들을 목록으로 변환합니다.
4. 개선된 API 확인
uvicorn
서버가 --reload
옵션으로 실행 중인지 확인하세요.
http://127.0.0.1:8000/spaceships
확인하세요: 이제 응답은 객체가 아닌 JSON 배열입니다. 이는 컬렉션에 대해 더 올바르고 표준적인 구조입니다.http://127.0.0.1:8000/spaceships/1
확인하세요: 응답은 변경되지 않았지만, 이제 모델에 보장된 일치를 보여줍니다./docs
확인하세요: 페이지 하단의 "Schemas" 섹션에Spaceship
모델이 나타났습니다. 그리고 엔드포인트의 응답 예시에는 이제 아름답고 구조화된 데이터 스키마가 표시됩니다.
5. 고급 유효성 검사: "온보드 컴퓨터" 작동 중
Pydantic은 단순히 타입을 확인하는 것보다 훨씬 더 많은 것을 할 수 있습니다.
Spaceship
모델에 유효성 검사를 추가해 봅시다:
from pydantic import BaseModel, Field
class Spaceship(BaseModel):
name: str = Field(..., min_length=3, max_length=50, description="선박명")
type: str
launch_year: int = Field(..., gt=1950, description="발사 연도는 1950년 이후여야 합니다.")
status: str
Field(...)
: 추가 유효성 검사 규칙을 추가하는 데 사용됩니다....
(Ellipsis): 필드가 필수임을 의미합니다.min_length
,max_length
: 문자열에 대한 제한입니다.gt
: "Greater Than" (초과).
아직 새 선박을 생성하지는 않지만 (이것은 다음 장에서 다룰 것입니다), 이러한 규칙은 이미 문서에 반영될 것이고 우리가 POST
요청을 구현할 때 작동할 것입니다.
복습 퀴즈
🚀 장 요약:
API 데이터에 대한 "디지털 청사진"을 설계했습니다. 이제 단순히 작동하는 것을 넘어, 예측 가능하고 안정적으로 작동합니다.
- 📝 Pydantic을 사용하여
Spaceship
모델을 생성했습니다. - 🛡️ 이제 API는
response_model
을 사용하여 나가는 데이터를 검증하고 필터링합니다. - 📊 문서는 정확한 데이터 스키마를 보여주면서 훨씬 더 유익해졌습니다.
청사진이 준비되고 승인되었습니다! 다음 장에서는 데이터 읽기에서 데이터 생성으로 넘어갈 것입니다. 우리 함대를 위한 완전한 CRUD 작업을 구현할 것입니다.
📌 확인:
/docs
에Spaceship
모델 스키마가 나타났는지 확인하십시오./spaceships
엔드포인트가 이제 객체 ({...}
)가 아닌 JSON 배열 ([...]
)을 반환하는지 확인하십시오.- 모델 추가 후 코드에 구문 오류가 없는지 확인하십시오.
⚠️ 오류 발생 시:
NameError: name 'BaseModel' is not defined
:BaseModel
을pydantic
에서 임포트했는지 확인하십시오.NameError: name 'List' is not defined
:List
를typing
에서 임포트했는지 확인하십시오./spaceships
에 대한 응답이 비어 있음 ([]
):return db_spaceships
를return list(db_spaceships.values())
로 변경했는지 확인하십시오. ```