Skip to content

FastAPI와 Pydantic V2를 활용해 타입 안전한 REST API를 구축하는 방법을 실습 코드와 함께 소개합니다. 데이터 검증, 커스텀 Validator, 성능 최적화까지 실전 팁을 담았습니다.

FastAPI와 Pydantic V2, 왜 함께 써야 할까요?

FastAPI와 Pydantic

최근 Python 웹 개발에서 FastAPI의 인기가 급상승하고 있습니다. 그 중심에는 Pydantic이 있죠. 특히 2023년에 출시된 Pydantic V2는 성능이 기존 대비 5-50배 향상되었고, 타입 안전성도 크게 강화되었습니다. FastAPI와 Pydantic V2를 함께 사용하면 런타임 에러를 컴파일 타임에 잡아낼 수 있고, API 문서가 자동으로 생성되며, 데이터 검증 로직을 선언적으로 작성할 수 있습니다. 마치 TypeScript를 Python에서 사용하는 것과 비슷한 경험이라고 할 수 있어요.

기본 설정과 모델 정의하기

데이터 모델링

먼저 필요한 패키지를 설치해볼까요?

bash pip install fastapi[all] pydantic>=2.0

Pydantic V2에서는 BaseModel을 상속받아 데이터 모델을 정의합니다. V2의 가장 큰 변화는 Field의 강력한 검증 기능과 ConfigDict를 통한 설정 방식입니다.

python from pydantic import BaseModel, Field, ConfigDict, EmailStr from typing import Optional from datetime import datetime

class UserCreate(BaseModel): model_config = ConfigDict(str_strip_whitespace=True)

username: str = Field(min_length=3, max_length=20, pattern="^[a-zA-Z0-9_]+$")
email: EmailStr
age: int = Field(gt=0, le=120)
bio: Optional[str] = Field(None, max_length=500)

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

id: int
username: str
email: EmailStr
created_at: datetime

여기서 주목할 점은 ConfigDict입니다. V1의 Config 클래스 방식에서 딕셔너리 기반으로 변경되어 더 직관적이에요. str_strip_whitespace=True는 문자열 양끝 공백을 자동 제거하고, from_attributes=True는 ORM 객체를 직접 Pydantic 모델로 변환할 수 있게 해줍니다.

타입 안전한 엔드포인트 구현하기

API 개발

FastAPI는 Pydantic 모델을 통해 요청/응답의 타입을 자동으로 검증합니다. 이제 실제 API 엔드포인트를 만들어볼게요.

python from fastapi import FastAPI, HTTPException, status from typing import List

app = FastAPI(title="Type-Safe API")

임시 데이터베이스

users_db = [] user_id_counter = 1

@app.post("/users", response_model=UserResponse, status_code=status.HTTP_201_CREATED) async def create_user(user: UserCreate) -> UserResponse: global user_id_counter

# 중복 이메일 체크
if any(u["email"] == user.email for u in users_db):
    raise HTTPException(
        status_code=status.HTTP_400_BAD_REQUEST,
        detail="Email already registered"
    )

new_user = {
    "id": user_id_counter,
    "username": user.username,
    "email": user.email,
    "created_at": datetime.now()
}
users_db.append(new_user)
user_id_counter += 1

return UserResponse(**new_user)

@app.get("/users", response_model=List[UserResponse]) async def get_users() -> List[UserResponse]: return [UserResponse(**user) for user in users_db]

@app.get("/users/{user_id}", response_model=UserResponse) async def get_user(user_id: int) -> UserResponse: user = next((u for u in users_db if u["id"] == user_id), None) if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found" ) return UserResponse(**user)

핵심은 response_model 파라미터입니다. 이를 지정하면 FastAPI가 자동으로 응답 데이터를 검증하고, 불필요한 필드는 제거하며, API 문서에도 정확한 스키마가 표시됩니다. 타입 힌트(-> UserResponse)와 함께 사용하면 IDE의 자동완성도 완벽하게 작동하죠.

고급 검증과 커스텀 Validator

데이터 검증

Pydantic V2는 field_validatormodel_validator 데코레이터로 복잡한 검증 로직을 구현할 수 있습니다. V1의 validator에서 업그레이드된 버전이에요.

python from pydantic import field_validator, model_validator import re

class SecureUserCreate(BaseModel): username: str password: str password_confirm: str email: EmailStr

@field_validator('password')
@classmethod
def validate_password_strength(cls, v: str) -> str:
    if len(v) < 8:
        raise ValueError('비밀번호는 최소 8자 이상이어야 합니다')
    if not re.search(r'[A-Z]', v):
        raise ValueError('대문자를 최소 1개 포함해야 합니다')
    if not re.search(r'[0-9]', v):
        raise ValueError('숫자를 최소 1개 포함해야 합니다')
    if not re.search(r'[!@#$%^&*]', v):
        raise ValueError('특수문자를 최소 1개 포함해야 합니다')
    return v

@model_validator(mode='after')
def validate_passwords_match(self) -> 'SecureUserCreate':
    if self.password != self.password_confirm:
        raise ValueError('비밀번호가 일치하지 않습니다')
    return self

field_validator는 개별 필드를 검증하고, model_validator는 여러 필드를 동시에 검증할 때 사용합니다. mode='after'는 모든 필드 검증이 끝난 후 실행된다는 의미예요. 이렇게 하면 비즈니스 로직을 모델 자체에 캡슐화할 수 있어 코드가 훨씬 깔끔해집니다.

실전 팁: 성능 최적화와 베스트 프랙티스

성능 최적화

Pydantic V2를 실전에서 사용할 때 알아두면 좋은 팁들을 정리했습니다.

1. 모델 재사용으로 중복 제거: UserCreateUserResponse처럼 입출력 모델을 분리하되, 공통 필드는 베이스 모델로 추출하세요. 이렇게 하면 유지보수가 쉬워집니다.

2. Computed Field 활용: V2의 computed_field 데코레이터로 동적 필드를 만들 수 있습니다. 예를 들어 full_namefirst_namelast_name으로 조합할 수 있죠.

3. Strict Mode 고려: model_config = ConfigDict(strict=True)로 설정하면 타입 강제 변환을 비활성화합니다. 문자열 "123"을 정수 123으로 자동 변환하지 않고 에러를 발생시켜 더 엄격한 검증이 가능합니다.

4. Alias 사용: 외부 API와 통합할 때 Field(alias="external_name")으로 필드명을 매핑할 수 있습니다. Python의 snake_case와 JSON의 camelCase를 자연스럽게 연결할 수 있어요.

5. 문서화 강화: Field(description="...")로 각 필드에 설명을 추가하면 자동 생성되는 Swagger 문서가 훨씬 풍부해집니다.

FastAPI와 Pydantic V2의 조합은 Python으로 엔터프라이즈급 API를 개발할 때 최고의 선택입니다. 타입 안전성을 확보하면서도 빠른 개발 속도를 유지할 수 있으니, 다음 프로젝트에 꼭 적용해보세요!


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