FastAPI + Pydantic v2 조합 예시 (with Validator & Serializer)

류지수·2025년 6월 11일

이 글에선느 다음 네가지 핵심 기능을 중심으로 FastAPI + Pydantic v2 조합을 정리

  • @field_validator
  • @model_validator
  • @field_serializer
  • @model_serializer

프로젝트 구조

project/
├── main.py
├── schemas.py
├── services.py
└── requirements.txt


Pydantic 스키마 정의 (schemas.py)

from pydantic import BaseModel, Field, model_validator, field_validator, model_serializer

class UserCreate(BaseModel):
    name: str = Field(default="익명", max_length=20)
    password: str = Field(..., min_length=4)

    @model_validator(mode="before")
    @classmethod
    def set_default_name(cls, values):
        values.setdefault("name", "익명")
        return values

    @model_validator(mode="after")
    def check_password(self):
        if len(self.password) < 4:
            raise ValueError("비밀번호는 4자 이상이어야 합니다")
        return self


class UserResponse(BaseModel):
    id: int
    name: str

    @model_serializer
    def serialize(self):
        return {
            "id": self.id,
            "name": self.name.upper()
        }


서비스 로직 (services.py)

from schemas import UserCreate, UserResponse

# 임시 DB 대용
_fake_db = []

def create_user(user_data: UserCreate) -> UserResponse:
    new_id = len(_fake_db) + 1
    new_user = {"id": new_id, "name": user_data.name, "password": user_data.password}
    _fake_db.append(new_user)

    return UserResponse(id=new_id, name=user_data.name)


def get_user_by_id(user_id: int) -> UserResponse:
    user = next((u for u in _fake_db if u["id"] == user_id), None)
    if not user:
        raise ValueError("사용자를 찾을 수 없습니다")
    return UserResponse(id=user["id"], name=user["name"])


FastAPI 라우터 (main.py)

from fastapi import FastAPI, HTTPException
from pydantic import ValidationError
from schemas import UserCreate
from services import create_user, get_user_by_id

app = FastAPI()

@app.post("/users")
def create_user_api(user: UserCreate):
    try:
        result = create_user(user)
        return result
    except ValidationError as e:
        raise HTTPException(status_code=400, detail=e.errors())
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))


@app.get("/users/{user_id}")
def get_user_api(user_id: int):
    try:
        result = get_user_by_id(user_id)
        return result
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))

상세

POST /users → UserCreate로 검증

  1. UserCreate 모델로 입력 값 검증
    model_validator로 기본값 설정 및 비밀번호 검증

  2. 서비스 로직에서 임시 DB 저장

  3. UserResponse 모델로 직렬화
    model_serializer에서 name을 대문자로 변환

  4. model_dump_json()으로 JSON 응답 반환


GET /users/{id}

  1. 임시 DB에서 사용자 정보 조회

  2. UserResponse 모델로 직렬화

  3. model_dump_json()으로 JSON 응답 반환


동작

POST/users

요청

{
  "name": "lee",
  "password": "1234"
}

응답

{
  "id": 1,
  "name": "LEE"
}

GET /users/1

응답

{
  "id": 1,
  "name": "LEE"
}

검증 실패 시
요청

{
  "name": "min",
  "password": "12"
}

응답

{
  "detail": [
    {
      "type": "value_error",
      "msg": "비밀번호는 4자 이상이어야 합니다",
      "loc": ["password"]
    }
  ]
}
profile
끄적끄적

0개의 댓글