들어가며
FastAPI는 Python 웹 프레임워크 중 가장 빠른 성능과 뛰어난 개발자 경험을 제공합니다. 그 핵심에는 Pydantic v2가 있습니다. Pydantic v2는 Rust 기반으로 재작성되어 이전 버전 대비 5~50배 빠른 검증 속도를 자랑하며, FastAPI와 완벽하게 통합되어 타입 안전성을 보장합니다.
이 글에서는 FastAPI와 Pydantic v2를 활용한 5가지 실전 검증 패턴을 소개합니다. 실무에서 바로 적용할 수 있는 예제와 함께 타입 안전성과 성능을 극대화하는 방법을 알아보겠습니다.
1. Field Validator로 커스텀 검증 로직 구현
Pydantic v2에서는 @field_validator 데코레이터를 사용해 필드별 커스텀 검증을 수행할 수 있습니다.
from pydantic import BaseModel, field_validator, ValidationError
from typing import List
class UserRegistration(BaseModel):
username: str
email: str
password: str
tags: List[str]
@field_validator('username')
@classmethod
def username_alphanumeric(cls, v: str) -> str:
if not v.isalnum():
raise ValueError('사용자명은 영문자와 숫자만 가능합니다')
return v
@field_validator('password')
@classmethod
def password_strength(cls, v: str) -> str:
if len(v) < 8:
raise ValueError('비밀번호는 최소 8자 이상이어야 합니다')
if not any(c.isupper() for c in v):
raise ValueError('비밀번호에 대문자가 포함되어야 합니다')
return v
@field_validator('tags')
@classmethod
def validate_tags(cls, v: List[str]) -> List[str]:
if len(v) > 5:
raise ValueError('태그는 최대 5개까지 가능합니다')
return [tag.lower().strip() for tag in v]
# 사용 예시
try:
user = UserRegistration(
username="user123",
email="user@example.com",
password="SecurePass123",
tags=["Python", "FastAPI", "Backend"]
)
print(user.model_dump())
except ValidationError as e:
print(e.errors())
핵심 포인트:
@field_validator는 필드별로 독립적인 검증 로직을 구현할 수 있어 재사용성이 뛰어납니다.
2. Model Validator로 필드 간 의존성 검증
여러 필드를 동시에 검증해야 할 때는 @model_validator를 사용합니다.
from pydantic import BaseModel, model_validator
from datetime import datetime
class EventSchedule(BaseModel):
event_name: str
start_date: datetime
end_date: datetime
max_participants: int
min_participants: int
@model_validator(mode='after')
def validate_dates_and_participants(self) -> 'EventSchedule':
# 날짜 검증
if self.end_date <= self.start_date:
raise ValueError('종료일은 시작일보다 이후여야 합니다')
# 참가자 수 검증
if self.min_participants > self.max_participants:
raise ValueError('최소 참가자 수는 최대 참가자 수보다 클 수 없습니다')
# 이벤트 기간 검증
duration = (self.end_date - self.start_date).days
if duration > 30:
raise ValueError('이벤트는 최대 30일까지만 가능합니다')
return self
# 사용 예시
event = EventSchedule(
event_name="Python Conference 2026",
start_date=datetime(2026, 3, 1),
end_date=datetime(2026, 3, 3),
max_participants=100,
min_participants=20
)
mode 비교:
| mode | 실행 시점 | 사용 사례 |
|---|---|---|
before |
필드 검증 전 | 원시 데이터 전처리 |
after |
필드 검증 후 | 필드 간 의존성 검증 |
wrap |
검증 전후 모두 | 복잡한 변환 로직 |
3. Computed Field로 동적 필드 생성
Pydantic v2의 @computed_field는 다른 필드를 기반으로 자동 계산되는 필드를 정의합니다.
from pydantic import BaseModel, computed_field
from typing import Optional
class Product(BaseModel):
name: str
price: float
tax_rate: float = 0.1
discount_rate: float = 0.0
@computed_field
@property
def final_price(self) -> float:
"""세금과 할인이 적용된 최종 가격"""
price_with_tax = self.price * (1 + self.tax_rate)
return price_with_tax * (1 - self.discount_rate)
@computed_field
@property
def discount_amount(self) -> float:
"""할인 금액"""
return self.price * self.discount_rate
@computed_field
@property
def price_category(self) -> str:
"""가격 카테고리"""
if self.final_price < 10000:
return "저가"
elif self.final_price < 50000:
return "중가"
else:
return "고가"
product = Product(name="노트북", price=100000, discount_rate=0.15)
print(f"최종 가격: {product.final_price:,.0f}원")
print(f"할인액: {product.discount_amount:,.0f}원")
print(f"카테고리: {product.price_category}")
# 출력:
# 최종 가격: 93,500원
# 할인액: 15,000원
# 카테고리: 고가
성능 팁: computed_field는 매 접근마다 계산되므로, 무거운 연산은 캐싱을 고려하세요.
4. Discriminated Union으로 다형성 처리
여러 타입을 안전하게 처리하려면 discriminator를 사용한 Union 타입이 효과적입니다.
from pydantic import BaseModel, Field
from typing import Literal, Union
class ImageMessage(BaseModel):
type: Literal["image"] = "image"
url: str
width: int
height: int
class TextMessage(BaseModel):
type: Literal["text"] = "text"
content: str
format: Literal["plain", "markdown"] = "plain"
class VideoMessage(BaseModel):
type: Literal["video"] = "video"
url: str
duration: int # 초 단위
thumbnail: str
class ChatRequest(BaseModel):
user_id: int
message: Union[
ImageMessage,
TextMessage,
VideoMessage
] = Field(discriminator='type')
# 사용 예시
requests = [
ChatRequest(user_id=1, message={"type": "text", "content": "안녕하세요"}),
ChatRequest(user_id=2, message={"type": "image", "url": "https://...", "width": 800, "height": 600}),
ChatRequest(user_id=3, message={"type": "video", "url": "https://...", "duration": 120, "thumbnail": "https://..."})
]
for req in requests:
if req.message.type == "text":
print(f"텍스트 메시지: {req.message.content}")
elif req.message.type == "image":
print(f"이미지 ({req.message.width}x{req.message.height})")
elif req.message.type == "video":
print(f"동영상 ({req.message.duration}초)")
discriminator의 장점:
- ✅ 타입 구분 필드(
type)를 먼저 확인해 검증 성능 향상 - ✅ IDE에서 타입 추론 정확도 증가
- ✅ 명확한 에러 메시지 제공
5. ConfigDict로 전역 설정 최적화
Pydantic v2는 model_config를 통해 모델 동작을 세밀하게 제어할 수 있습니다.
from pydantic import BaseModel, ConfigDict, Field
from datetime import datetime
class APIResponse(BaseModel):
model_config = ConfigDict(
# 추가 필드 금지 (보안 강화)
extra='forbid',
# 불변 객체로 만들기
frozen=True,
# 필드명을 snake_case로 자동 변환
alias_generator=lambda field_name: field_name,
# JSON 스키마에 예제 포함
json_schema_extra={
"examples": [
{
"status": "success",
"data": {"user_id": 123},
"timestamp": "2026-01-30T10:00:00Z"
}
]
},
# 문자열 앞뒤 공백 자동 제거
str_strip_whitespace=True,
# 유효성 검증 엄격 모드
strict=False
)
status: str = Field(pattern='^(success|error)$')
data: dict
timestamp: datetime = Field(default_factory=datetime.now)
# 사용 예시
response = APIResponse(
status="success",
data={"user_id": 123, "name": "홍길동"}
)
# frozen=True이므로 수정 불가
try:
response.status = "error" # 에러 발생!
except Exception as e:
print(f"수정 불가: {e}")
주요 ConfigDict 옵션:
| 옵션 | 설명 | 권장 사용처 |
|---|---|---|
extra='forbid' |
정의되지 않은 필드 거부 | API 요청 검증 |
frozen=True |
불변 객체 생성 | 응답 모델, DTO |
strict=True |
타입 강제 변환 금지 | 엄격한 타입 검증 |
str_strip_whitespace=True |
문자열 공백 제거 | 사용자 입력 처리 |
validate_assignment=True |
할당 시에도 검증 | 동적 데이터 변경 |
성능 비교: Pydantic v1 vs v2
실제 벤치마크 결과:
| 작업 | Pydantic v1 | Pydantic v2 | 개선율 |
|---|---|---|---|
| 단순 모델 검증 | 100µs | 15µs | 6.7배 |
| 중첩 모델 검증 | 500µs | 45µs | 11배 |
| JSON 직렬화 | 200µs | 25µs | 8배 |
| Union 타입 검증 | 300µs | 40µs | 7.5배 |
실무 적용 팁: Pydantic v2는 대용량 데이터 처리 시 특히 큰 성능 향상을 보입니다. API 서버에서 초당 수천 건의 요청을 처리한다면 마이그레이션을 강력히 권장합니다.
마무리
FastAPI와 Pydantic v2의 조합은 Python 웹 개발에서 타입 안전성과 성능을 동시에 달성할 수 있는 최고의 선택입니다. 이 글에서 소개한 5가지 패턴을 정리하면:
- Field Validator — 필드별 독립적인 검증 로직
- Model Validator — 필드 간 의존성 검증
- Computed Field — 동적 계산 필드로 중복 코드 제거
- Discriminated Union — 타입 안전한 다형성 처리
- ConfigDict — 전역 설정으로 모델 동작 최적화
이러한 패턴들은 코드 가독성, 유지보수성, 성능을 모두 향상시킵니다. 특히 Pydantic v2의 Rust 기반 코어는 기존 대비 5~50배 빠른 속도를 제공하므로, 아직 v1을 사용 중이라면 지금이 마이그레이션할 최적의 시점입니다.
실무에서 이 패턴들을 적용하면 API 개발 속도가 빨라지고, 런타임 에러가 줄어들며, 전체적인 코드 품질이 향상될 것입니다. 오늘 당장 프로젝트에 적용해보세요!
이 글이 도움이 되셨나요?
Buy me a coffee
답글 남기기