FastAPI와 Pydantic을 활용해 타입 안전한 REST API를 구축하는 실전 가이드입니다. Pydantic 모델 정의부터 고급 검증, 에러 핸들링까지 실무에 바로 적용 가능한 코드 예제와 팁을 제공합니다.
FastAPI와 Pydantic이 환상의 조합인 이유¶
Python으로 API를 개발하다 보면 타입 관련 버그로 고생한 경험, 다들 있으시죠? FastAPI와 Pydantic의 조합은 이런 문제를 근본적으로 해결해줍니다. FastAPI는 Python의 타입 힌트를 기반으로 자동 검증과 문서화를 제공하는 현대적인 웹 프레임워크이고, Pydantic은 데이터 검증 라이브러리입니다. 이 둘의 시너지는 정말 강력합니다.
특히 런타임에서 타입을 자동으로 검증하고, 잘못된 데이터가 들어오면 명확한 에러 메시지를 반환해줍니다. 더 놀라운 건 이 모든 게 자동으로 API 문서(Swagger UI)에 반영된다는 점이죠. 개발 생산성이 2배 이상 올라가는 경험을 하게 되실 겁니다.
Pydantic 모델로 데이터 구조 정의하기¶
Pydantic의 핵심은 BaseModel을 상속받은 클래스로 데이터 구조를 정의하는 것입니다. 실제 예제를 보시죠:
python from pydantic import BaseModel, EmailStr, Field, validator from typing import Optional from datetime import datetime
class UserCreate(BaseModel): username: str = Field(..., min_length=3, max_length=50) email: EmailStr age: int = Field(..., gt=0, lt=150) bio: Optional[str] = None
@validator('username')
def username_alphanumeric(cls, v):
assert v.isalnum(), '사용자명은 영문자와 숫자만 가능합니다'
return v
class UserResponse(BaseModel): id: int username: str email: EmailStr created_at: datetime
class Config:
orm_mode = True # ORM 객체를 자동으로 Pydantic 모델로 변환
여기서 Field는 추가 검증 규칙을 정의하고, validator는 커스텀 검증 로직을 만들 수 있게 해줍니다. EmailStr처럼 Pydantic이 제공하는 특수 타입도 활용하면 더욱 강력한 검증이 가능합니다. 요청과 응답에 각각 다른 모델을 사용하는 것도 중요한 포인트입니다!
FastAPI 엔드포인트에 타입 적용하기¶
이제 정의한 모델을 FastAPI 엔드포인트에 적용해봅시다. 타입 힌트만 추가하면 모든 마법이 자동으로 일어납니다:
python from fastapi import FastAPI, HTTPException, status from typing import List
app = FastAPI(title="타입 안전한 사용자 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): global user_id_counter
# user는 이미 검증된 UserCreate 객체입니다
user_dict = user.dict()
user_dict['id'] = user_id_counter
user_dict['created_at'] = datetime.now()
users_db[user_id_counter] = user_dict
user_id_counter += 1
return user_dict
@app.get("/users/{user_id}", response_model=UserResponse) async def get_user(user_id: int): if user_id not in users_db: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="사용자를 찾을 수 없습니다" ) return users_db[user_id]
@app.get("/users/", response_model=List[UserResponse]) async def list_users(skip: int = 0, limit: int = 10): users = list(users_db.values()) return users[skip:skip + limit]
response_model 파라미터를 지정하면 응답 데이터도 자동으로 검증되고 직렬화됩니다. 민감한 정보(비밀번호 등)를 응답에서 자동으로 제외할 수도 있죠. 쿼리 파라미터인 skip과 limit도 타입 힌트만으로 자동 검증됩니다!
고급 검증과 에러 핸들링¶
실무에서는 더 복잡한 검증이 필요합니다. Pydantic의 고급 기능들을 활용해봅시다:
python from pydantic import BaseModel, root_validator from typing import Optional
class UserUpdate(BaseModel): username: Optional[str] = Field(None, min_length=3, max_length=50) email: Optional[EmailStr] = None bio: Optional[str] = None password: Optional[str] = Field(None, min_length=8) password_confirm: Optional[str] = None
@root_validator
def check_passwords_match(cls, values):
pw = values.get('password')
pw_confirm = values.get('password_confirm')
if pw is not None and pw != pw_confirm:
raise ValueError('비밀번호가 일치하지 않습니다')
return values
class Config:
# 업데이트 시 None 값은 무시
exclude_none = True
root_validator는 여러 필드를 동시에 검증할 때 사용합니다. FastAPI는 검증 실패 시 자동으로 422 상태 코드와 함께 상세한 에러 정보를 JSON으로 반환합니다. 클라이언트에서 어떤 필드가 왜 잘못되었는지 정확히 알 수 있죠.
커스텀 에러 응답이 필요하다면 @app.exception_handler를 활용하세요. Pydantic의 ValidationError를 잡아서 원하는 형식으로 변환할 수 있습니다.
실전 팁: 타입 안전성을 극대화하는 방법¶
mypy로 정적 타입 검사하기: FastAPI 코드에 mypy를 적용하면 개발 단계에서 타입 오류를 미리 잡을 수 있습니다. mypy --strict main.py 명령으로 엄격한 타입 검사를 수행하세요.
제네릭 응답 모델 활용하기: 페이지네이션이나 표준 응답 형식을 제네릭으로 만들면 재사용성이 높아집니다. from typing import Generic, TypeVar를 활용하세요.
환경 변수도 Pydantic으로: pydantic.BaseSettings를 상속받으면 환경 변수도 타입 안전하게 관리할 수 있습니다. 데이터베이스 URL이나 API 키 같은 설정값을 검증하는 데 완벽합니다.
의존성 주입 활용하기: FastAPI의 Depends를 사용하면 데이터베이스 세션, 인증, 권한 검사 등을 타입 안전하게 주입할 수 있습니다. 테스트 코드 작성도 훨씬 쉬워지죠.
자동 생성되는 /docs 엔드포인트를 확인하면 모든 타입 정보가 완벽하게 문서화되어 있습니다. 프론트엔드 개발자와 협업할 때 이보다 좋은 게 없습니다!
이 글은 AI가 자동으로 작성했습니다.