FastAPI + Pydantic V2로 타입 안전성 100% 보장하는 REST API 만들기
Pydantic V2와 FastAPI를 활용해 타입 안전한 REST API를 구축하는 실전 가이드입니다. 새로워진 검증 시스템, 모델 설계 패턴, 커스텀 Validator 활용법부터 프로덕션 베스트 프랙티스까지 다룹니다.
Pydantic V2의 강력한 타입 검증 시스템¶
Pydantic V2는 이전 버전보다 5-50배 빠른 성능을 제공하면서도 더 강력한 타입 안전성을 보장합니다. Rust로 작성된 pydantic-core 덕분에 대규모 데이터 검증에서도 놀라운 속도를 자랑하죠. FastAPI와 결합하면 런타임에서 발생할 수 있는 타입 오류를 컴파일 타임에 잡아낼 수 있어요.
가장 큰 변화는 Field 검증 방식과 ConfigDict의 도입입니다. 이제 더 직관적이고 명확한 방식으로 모델을 정의할 수 있게 되었어요. 특히 model_validate(), model_dump() 같은 새로운 메서드들은 이전의 parse_obj(), dict() 보다 훨씬 명확한 의도를 전달합니다.
실전 모델 설계: BaseModel 활용하기¶
타입 안전한 API를 만들기 위해선 견고한 모델 설계가 필수입니다. Pydantic V2의 새로운 기능들을 활용해 실무에서 바로 쓸 수 있는 유저 관리 API를 만들어볼게요.
python from pydantic import BaseModel, Field, EmailStr, ConfigDict from datetime import datetime from typing import Optional
class UserBase(BaseModel): model_config = ConfigDict(str_strip_whitespace=True)
email: EmailStr = Field(..., description="사용자 이메일")
username: str = Field(..., min_length=3, max_length=20)
age: int = Field(..., ge=0, le=150)
class UserCreate(UserBase): password: str = Field(..., min_length=8, pattern=r'^(?=.[A-Za-z])(?=.\d)')
class UserResponse(UserBase): id: int created_at: datetime is_active: bool = True
model_config = ConfigDict(from_attributes=True)
여기서 핵심은 ConfigDict를 통한 설정 관리입니다. str_strip_whitespace=True는 입력값의 공백을 자동 제거하고, from_attributes=True는 ORM 객체를 자동으로 Pydantic 모델로 변환해줍니다. 이전 버전의 orm_mode=True보다 훨씬 직관적이죠!
FastAPI 엔드포인트에서 타입 안전성 구현하기¶
이제 정의한 모델을 FastAPI 엔드포인트에 적용해볼까요? 타입 힌트만 제대로 사용해도 자동으로 검증과 문서화가 이루어집니다.
python from fastapi import FastAPI, HTTPException, status from typing import List
app = FastAPI(title="타입 안전 API")
users_db: List[UserResponse] = []
@app.post( "/users/", response_model=UserResponse, status_code=status.HTTP_201_CREATED, tags=["users"] ) async def create_user(user: UserCreate) -> UserResponse: # UserCreate 모델이 자동으로 검증을 수행합니다 user_dict = user.model_dump()
# 실제로는 DB 저장 로직
new_user = UserResponse(
id=len(users_db) + 1,
**user_dict,
created_at=datetime.now()
)
users_db.append(new_user)
return new_user
@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=f"User {user_id} not found" ) return user
response_model을 지정하면 FastAPI가 자동으로 응답 데이터를 검증하고 직렬화합니다. 잘못된 데이터가 반환되면 즉시 에러를 발생시켜 클라이언트에게 잘못된 데이터가 전달되는 것을 방지하죠.
고급 검증: Custom Validators와 의존성 주입¶
Pydantic V2에서는 @field_validator와 @model_validator 데코레이터로 커스텀 검증 로직을 추가할 수 있습니다. 실무에서 자주 사용되는 패턴을 살펴볼게요.
python from pydantic import field_validator, model_validator from typing_extensions import Self
class UserCreate(UserBase): password: str = Field(..., min_length=8) password_confirm: str
@field_validator('username')
@classmethod
def username_alphanumeric(cls, v: str) -> str:
if not v.isalnum():
raise ValueError('사용자명은 영문자와 숫자만 가능합니다')
return v.lower()
@model_validator(mode='after')
def check_passwords_match(self) -> Self:
if self.password != self.password_confirm:
raise ValueError('비밀번호가 일치하지 않습니다')
return self
중요한 변경점은 @classmethod 데코레이터가 필수가 되었다는 점입니다. 또한 mode='after'를 사용하면 모든 필드 검증이 끝난 후 모델 전체를 검증할 수 있어요. 이는 여러 필드 간의 관계를 검증할 때 아주 유용합니다.
실용 팁: FastAPI의 의존성 주입과 결합하면 더욱 강력해집니다. 예를 들어 DB 세션, 인증 정보 등을 타입 안전하게 주입받을 수 있죠.
프로덕션 환경을 위한 베스트 프랙티스¶
실제 서비스에 적용할 때는 몇 가지 추가 고려사항이 있습니다. 첫째, **응답 모델과 요청 모델을 명확히 분리**하세요. 비밀번호 같은 민감 정보가 응답에 포함되는 것을 방지할 수 있습니다.
둘째, ConfigDict의 json_schema_extra를 활용해 API 문서를 풍부하게 만드세요. Swagger UI에서 더 나은 예시를 제공할 수 있습니다.
python class UserCreate(UserBase): model_config = ConfigDict( json_schema_extra={ "examples": [{ "email": "user@example.com", "username": "johndoe", "age": 30, "password": "secure123" }] } )
셋째, **환경별 설정을 Pydantic Settings로 관리**하세요. 타입 안전하게 환경 변수를 다룰 수 있어 설정 오류를 사전에 방지할 수 있습니다.
마지막으로, 대용량 데이터를 다룰 때는 model_dump(mode='json')을 사용해 JSON 직렬화 성능을 최적화하세요. Pydantic V2의 Rust 기반 성능을 최대한 활용할 수 있습니다. 이런 작은 최적화들이 모여 서비스 전체의 응답 속도를 크게 개선할 수 있답니다!
이 글은 AI가 자동으로 작성했습니다.