Skip to content

Pydantic V2와 FastAPI를 활용해 타입 안전한 REST API를 구축하는 방법을 소개합니다. 고급 검증 패턴, 제네릭 활용, 커스텀 타입 정의 등 실전 팁을 포함한 완전한 가이드입니다.

Pydantic V2가 가져온 혁신적인 변화

Pydantic V2 변화

Pydantic V2는 단순한 업데이트가 아닙니다. Rust 기반의 pydantic-core 덕분에 V1 대비 최대 17배 빠른 검증 속도를 자랑하죠. 특히 FastAPI와 결합하면 타입 안전성과 성능을 동시에 잡을 수 있어요.

가장 눈에 띄는 변화는 @validator 대신 @field_validator, Config 클래스 대신 model_config 사용입니다. 처음엔 낯설지만, 더 직관적이고 강력한 기능을 제공합니다.

python from pydantic import BaseModel, Field, field_validator

class UserCreate(BaseModel): username: str = Field(min_length=3, max_length=20) email: str age: int = Field(gt=0, le=120)

@field_validator('email')
@classmethod
def validate_email(cls, v: str) -> str:
    if '@' not in v:
        raise ValueError('유효한 이메일 형식이 아닙니다')
    return v.lower()

FastAPI와 완벽한 조합 만들기

FastAPI 통합

FastAPI는 Pydantic을 핵심 의존성으로 사용하기 때문에 타입 힌트만으로도 자동 검증, 직렬화, API 문서 생성이 가능합니다. 이게 바로 FastAPI의 마법이죠.

중요한 건 요청/응답 모델을 명확히 분리하는 것입니다. UserCreate, UserResponse, UserUpdate처럼 용도별로 모델을 나누면 불필요한 필드 노출을 막고 타입 안전성을 높일 수 있어요.

python from fastapi import FastAPI, HTTPException from pydantic import BaseModel, ConfigDict

app = FastAPI()

class UserResponse(BaseModel): model_config = ConfigDict(from_attributes=True)

id: int
username: str
email: str
# 비밀번호는 응답에서 제외

@app.post("/users/", response_model=UserResponse) async def create_user(user: UserCreate): # DB 저장 로직 new_user = {"id": 1, **user.model_dump()} return new_user

핵심 포인트: response_model을 지정하면 FastAPI가 자동으로 응답 데이터를 검증하고 불필요한 필드를 제거합니다.

고급 검증 패턴으로 한 단계 더

고급 검증 패턴

실전에서는 단순 타입 검증을 넘어 복잡한 비즈니스 로직 검증이 필요합니다. Pydantic V2의 model_validator는 여러 필드를 동시에 검증할 때 강력하죠.

python from pydantic import BaseModel, model_validator from datetime import datetime from typing import Optional

class EventCreate(BaseModel): title: str start_date: datetime end_date: datetime max_participants: Optional[int] = None current_participants: int = 0

@model_validator(mode='after')
def validate_dates_and_capacity(self):
    if self.end_date <= self.start_date:
        raise ValueError('종료일은 시작일보다 늦어야 합니다')

    if self.max_participants and self.current_participants > self.max_participants:
        raise ValueError('현재 참가자가 최대 인원을 초과할 수 없습니다')

    return self

mode='after'를 사용하면 모든 필드가 개별 검증을 통과한 후 모델 전체를 검증합니다. 필드 간 의존성 검증에 완벽하죠.

타입 안전성을 극대화하는 실전 팁

타입 안전성 팁

1. Literal과 Enum 적극 활용하기

문자열 대신 Literal이나 Enum을 사용하면 오타로 인한 버그를 컴파일 타임에 잡을 수 있어요.

python from enum import Enum from typing import Literal

class UserRole(str, Enum): ADMIN = "admin" USER = "user" GUEST = "guest"

class UserUpdate(BaseModel): role: UserRole status: Literal["active", "inactive", "banned"]

2. 제네릭 응답 모델 만들기

페이지네이션이나 표준 응답 구조에는 제네릭을 사용하세요.

python from typing import Generic, TypeVar, List from pydantic import BaseModel

T = TypeVar('T')

class PaginatedResponse(BaseModel, Generic[T]): items: List[T] total: int page: int size: int

사용 예시

@app.get("/users/", response_model=PaginatedResponse[UserResponse]) async def list_users(page: int = 1, size: int = 10): # 로직 구현 pass

3. 커스텀 타입으로 재사용성 높이기

자주 사용하는 검증 로직은 커스텀 타입으로 만들어 재사용하세요.

python from pydantic import field_validator from typing_extensions import Annotated

PositiveInt = Annotated[int, Field(gt=0)] Username = Annotated[str, Field(min_length=3, max_length=20, pattern='^[a-zA-Z0-9_]+$')]

class Product(BaseModel): name: str price: PositiveInt stock: PositiveInt

마치며: 타입 안전성이 곧 생산성

생산성 향상

FastAPI와 Pydantic V2의 조합은 단순히 버그를 줄이는 것을 넘어 개발 속도를 획기적으로 높여줍니다. IDE의 자동완성, 즉각적인 오류 감지, 자동 생성되는 API 문서까지, 모든 것이 타입 안전성에서 비롯되죠.

초기 설정에 약간의 시간이 들더라도, 장기적으로는 디버깅 시간을 크게 줄이고 코드 품질을 높일 수 있습니다. 특히 팀 프로젝트에서 그 진가가 드러나요.

오늘 소개한 패턴들을 프로젝트에 바로 적용해보세요. 타입 안전한 API 개발의 즐거움을 경험하실 수 있을 거예요!


이 글은 AI가 자동으로 작성했습니다.