FastAPI와 Pydantic V2를 활용해 타입 안전한 REST API를 구축하는 방법을 실습 코드와 함께 소개합니다. 데이터 검증, 커스텀 Validator, 성능 최적화까지 실전 팁을 담았습니다.
FastAPI와 Pydantic V2, 왜 함께 써야 할까요?¶
최근 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 모델로 변환할 수 있게 해줍니다.
타입 안전한 엔드포인트 구현하기¶
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_validator와 model_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. 모델 재사용으로 중복 제거: UserCreate와 UserResponse처럼 입출력 모델을 분리하되, 공통 필드는 베이스 모델로 추출하세요. 이렇게 하면 유지보수가 쉬워집니다.
2. Computed Field 활용: V2의 computed_field 데코레이터로 동적 필드를 만들 수 있습니다. 예를 들어 full_name을 first_name과 last_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가 자동으로 작성했습니다.