Skip to content

FastAPI와 Pydantic V2를 결합해 타입 안전한 REST API를 구축하는 방법을 다룹니다. 실전 코드 예제와 함께 데이터 검증, 의존성 주입, 에러 핸들링 등 실무 필수 패턴을 소개합니다.

FastAPI와 Pydantic V2의 강력한 조합

FastAPI and Pydantic

안녕하세요! 오늘은 FastAPI와 Pydantic V2를 활용해 타입 안전한 REST API를 만드는 방법을 알아볼게요. Pydantic V2는 2023년에 출시되면서 성능이 5-50배 향상되었고, 더 직관적인 검증 기능을 제공합니다. FastAPI와 결합하면 런타임 에러를 사전에 방지하고, 자동으로 API 문서까지 생성되는 마법 같은 경험을 할 수 있어요.

특히 타입 안전성은 대규모 프로젝트에서 빛을 발합니다. Python의 동적 타이핑 특성상 발생할 수 있는 버그를 컴파일 타임에 잡아낼 수 있고, IDE의 자동완성 기능도 완벽하게 동작하죠. 실무에서 이 조합을 사용하면 코드 리뷰 시간이 크게 줄어들고, 신규 개발자의 온보딩도 훨씬 수월해집니다.

Pydantic V2 모델로 데이터 검증 마스터하기

Data Validation

Pydantic V2의 가장 큰 변화는 Field 함수와 검증 방식이에요. 이전 버전보다 훨씬 직관적이고 강력해졌습니다. 실제 코드로 살펴볼까요?

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

class UserCreate(BaseModel): username: str = Field(min_length=3, max_length=20, pattern="^[a-zA-Z0-9_]+$") email: EmailStr age: int = Field(gt=0, le=150) bio: Optional[str] = Field(None, max_length=500) created_at: datetime = Field(default_factory=datetime.now)

@field_validator('username')
@classmethod
def username_must_not_contain_admin(cls, v: str) -> str:
    if 'admin' in v.lower():
        raise ValueError('username cannot contain "admin"')
    return v

model_config = {
    "json_schema_extra": {
        "examples": [{
            "username": "john_doe",
            "email": "john@example.com",
            "age": 30,
            "bio": "Software developer"
        }]
    }
}

핵심은 Field로 세밀한 검증 규칙을 정의하고, field_validator 데코레이터로 커스텀 검증 로직을 추가하는 거예요. model_config를 통해 API 문서에 표시될 예제까지 정의할 수 있습니다. V2에서는 @validator 대신 @field_validator를 사용하고, Config 클래스 대신 model_config 딕셔너리를 사용한다는 점을 기억하세요!

FastAPI 엔드포인트에서 타입 안전성 활용하기

API Development

이제 Pydantic 모델을 FastAPI 엔드포인트에 적용해봅시다. 타입 힌트만 제대로 작성하면 FastAPI가 모든 마법을 부려줍니다.

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

app = FastAPI()

class UserResponse(BaseModel): id: int username: str email: EmailStr created_at: datetime

model_config = {"from_attributes": True}  # ORM 모델 변환 활성화

임시 데이터베이스

fake_db: List[UserResponse] = [] 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 # user 객체는 이미 검증된 상태입니다! new_user = UserResponse( id=user_id_counter, username=user.username, email=user.email, created_at=user.created_at ) fake_db.append(new_user) user_id_counter += 1 return new_user

@app.get("/users/{user_id}", response_model=UserResponse) async def get_user(user_id: int) -> UserResponse: for user in fake_db: if user.id == user_id: return user raise HTTPException(status_code=404, detail="User not found")

여기서 주목할 점은 response_model 파라미터예요. 이를 통해 응답 데이터도 타입 안전하게 관리할 수 있고, 민감한 필드(예: 비밀번호)를 자동으로 제외할 수 있습니다. FastAPI는 요청과 응답 모두에서 Pydantic 모델을 검증하므로, 예상치 못한 데이터가 API를 통과하는 일이 없어요.

고급 패턴: 의존성 주입과 에러 핸들링

Advanced Programming

실무에서는 더 복잡한 시나리오를 다뤄야 합니다. 의존성 주입과 전역 에러 핸들링을 활용하면 코드가 훨씬 깔끔해져요.

python from fastapi import Depends from pydantic import ValidationError from fastapi.responses import JSONResponse

커스텀 예외

class DuplicateUserError(Exception): def init(self, username: str): self.username = username

전역 예외 핸들러

@app.exception_handler(ValidationError) async def validation_exception_handler(request, exc: ValidationError): return JSONResponse( status_code=422, content={"detail": exc.errors(), "body": exc.json()} )

@app.exception_handler(DuplicateUserError) async def duplicate_user_handler(request, exc: DuplicateUserError): return JSONResponse( status_code=409, content={"detail": f"Username '{exc.username}' already exists"} )

의존성 함수

async def verify_unique_username(user: UserCreate) -> UserCreate: if any(u.username == user.username for u in fake_db): raise DuplicateUserError(user.username) return user

@app.post("/users/safe/", response_model=UserResponse) async def create_user_safe( user: UserCreate = Depends(verify_unique_username) ) -> UserResponse: # 여기서는 이미 중복 검사가 완료된 상태 global user_id_counter new_user = UserResponse( id=user_id_counter, username=user.username, email=user.email, created_at=user.created_at ) fake_db.append(new_user) user_id_counter += 1 return new_user

Depends를 활용하면 검증 로직을 재사용 가능한 컴포넌트로 분리할 수 있어요. 이렇게 하면 각 엔드포인트는 핵심 비즈니스 로직에만 집중할 수 있고, 테스트도 훨씬 쉬워집니다.

실전 팁과 베스트 프랙티스

Best Practices

마지막으로 실무에서 유용한 팁들을 공유할게요.

1. 모델 분리 전략: Request와 Response 모델을 명확히 분리하세요. UserCreate, UserUpdate, UserResponse처럼 목적에 따라 나누면 보안과 유지보수성이 향상됩니다.

2. Alias 활용: 외부 API와 통합할 때는 Field(alias="externalName")을 사용해 내부 네이밍 규칙을 유지하세요.

3. 성능 최적화: Pydantic V2는 기본적으로 빠르지만, model_config = {"validate_assignment": False}로 불필요한 재검증을 비활성화할 수 있습니다.

4. 문서화 강화: Field(description="사용자 고유 식별자")로 각 필드에 설명을 추가하면 자동 생성되는 OpenAPI 문서가 훨씬 이해하기 쉬워집니다.

5. 타입 체커 활용: mypy나 pyright 같은 정적 타입 체커를 CI/CD 파이프라인에 통합하세요. FastAPI와 Pydantic의 타입 안전성을 최대한 활용할 수 있습니다.

이제 여러분도 타입 안전한 REST API를 만들 준비가 되었네요! FastAPI와 Pydantic V2의 조합은 정말 강력하고, 한번 익숙해지면 다른 프레임워크로 돌아가기 힘들 거예요. 작은 프로젝트부터 시작해서 점진적으로 적용해보세요!


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