type: source status: seedling title: Pydantic 마스터하기: 데이터 검증 필수 사항 tags: [“python”, “pydantic”, “data-validation”, “fastapi”, “basemodel”] created: 2026-04-26 url: https://velog.io/@jk01019/pydantic harvested: 2026-04-26 site: Velog source_count: 1 source_type: article updated: 2026-04-26 valid_as_of: 2026-04-26

Pydantic: 데이터 검증 마스터하기

학습 목표 매핑

SKALA 3기 Module 1 — Python AI Native (Learning Objective 1-4)

  • Objective: Pydantic을 이용하여 데이터 검증 모델을 정의하고, JSON 입력 데이터에 대해 타입·범위·필수 필드를 자동 검증 (Bloom L2-L3)
  • Evaluation: 스키마 검증 테스트 (유효/무효 데이터 모두 처리)

Pydantic이란?

정의

Pydantic은 Python의 강력한 데이터 유효성 검사 및 파싱 라이브러리입니다.

핵심 역할:

  • ✅ 입력 데이터의 유효성 자동 검사
  • ✅ 타입 불일치 시 자동 변환
  • ✅ 잘못된 데이터 방지 (런타임 에러 감소)
  • ✅ FastAPI와 통합으로 API 요청 검증

특징

from pydantic import BaseModel
 
class User(BaseModel):
    id: int
    name: str
    email: str

자동 기능:

  • 타입 검증: id가 정수가 아니면 오류
  • 타입 변환: 문자열 “123” → 정수 123
  • 필드 검증: 이메일 형식 검증 (추가 설정)
  • 기본값 관리: 선택 필드 처리

선행 개념

이 개념을 배우기 전에 필수로 알아야 할 것:

  • Module 1-1 → python-type-hints-fastapi: Python 타입 힌트 문법
    • 왜?: Pydantic의 BaseModel 클래스 정의 시 각 필드에 타입 힌트를 명시해야 함
    • 예: class User(BaseModel): id: intint가 타입 힌트

후속 개념 (이 개념이 선행)

이 개념을 배운 후 다음 단계:

  1. Module 4-1 → fastapi-docker-official-deployment: FastAPI에서 Pydantic 모델을 request/response 스키마로 사용
    • 예: @app.post("/users/") 엔드포인트에서 Pydantic 모델로 자동 검증 및 Swagger 문서 생성

BaseModel 기초

1. 기본 모델 정의

from pydantic import BaseModel
from datetime import datetime
 
class User(BaseModel):
    id: int
    name: str
    email: str
    is_active: bool = True          # 기본값 설정
    signup_ts: datetime | None = None  # Optional

2. 데이터 검증 (자동)

# ✅ 유효한 데이터
user = User(
    id=1,
    name="John",
    email="john@example.com"
)
print(user)
# User(id=1, name='John', email='john@example.com', is_active=True, signup_ts=None)
 
# ❌ 타입 불일치 → 자동 오류
try:
    user = User(
        id="invalid",  # 문자열이 아니고 정수 기대
        name="John",
        email="john@example.com"
    )
except ValidationError as e:
    print(e)
    # value_error.integer - id는 정수여야 함

3. 자동 타입 변환

# "123" → 123 (자동 변환)
user = User(
    id="123",              # 문자열 입력
    name="John",
    email="john@example.com"
)
print(user.id)  # 123 (정수)
print(type(user.id))  # <class 'int'>

Field로 상세 검증

1. 기본 제약

from pydantic import BaseModel, Field
 
class Product(BaseModel):
    name: str = Field(
        ...,                    # 필수 필드
        min_length=3,          # 최소 3자
        max_length=100,        # 최대 100자
        description="상품명"
    )
    price: float = Field(
        ...,
        gt=0,                  # Greater Than 0
        description="가격 (0보다 커야 함)"
    )
    quantity: int = Field(
        default=0,             # 기본값
        ge=0,                  # Greater or Equal 0
        le=1000,               # Less or Equal 1000
        description="재고 수량"
    )
    description: str = Field(
        default=None,          # Optional
        description="상품 설명"
    )

2. 검증 예시

# ✅ 유효한 데이터
product = Product(
    name="Laptop",
    price=999.99,
    quantity=10
)
 
# ❌ 검증 실패 - 가격이 0
try:
    product = Product(name="TV", price=0, quantity=5)
except ValidationError as e:
    print(e)
    # value_error - price는 0보다 커야 함
 
# ❌ 검증 실패 - 이름이 너무 짧음
try:
    product = Product(name="A", price=100, quantity=5)
except ValidationError as e:
    print(e)
    # string_too_short - name은 최소 3자 이상

Validator로 커스텀 검증

필드 검증

from pydantic import BaseModel, validator
 
class User(BaseModel):
    name: str
    age: int
    email: str
    
    @validator('age')
    def check_age(cls, v):
        if v < 0 or v > 150:
            raise ValueError('Age must be between 0 and 150')
        return v
    
    @validator('email')
    def check_email(cls, v):
        if '@' not in v:
            raise ValueError('Invalid email format')
        return v

모델 검증

from pydantic import validator, root_validator
 
class Product(BaseModel):
    name: str
    price: float
    discount_price: float | None = None
    
    @root_validator
    def check_discount(cls, values):
        price = values.get('price')
        discount = values.get('discount_price')
        
        if discount and discount >= price:
            raise ValueError('Discount price must be less than regular price')
        return values

중첩 구조 (Nested Models)

1. 중첩 모델 정의

class Address(BaseModel):
    street: str
    city: str
    postal_code: str
 
class User(BaseModel):
    name: str
    age: int
    address: Address  # 중첩 모델

2. 중첩 데이터 자동 변환

# 중첩된 딕셔너리를 자동으로 Address 모델로 변환
user = User(
    name="John",
    age=30,
    address={
        "street": "123 Main St",
        "city": "New York",
        "postal_code": "10001"
    }
)
 
print(user.address.city)  # "New York"
print(type(user.address))  # <class '__main__.Address'>

3. 복잡한 구조

class Tag(BaseModel):
    name: str
 
class Post(BaseModel):
    title: str
    content: str
    author: User              # User 모델
    tags: list[Tag]          # Tag 리스트
    comments: list[str] = [] # 기본값 빈 리스트

데이터 내보내기 & 변환

1. JSON 변환

user = User(name="John", age=30)
 
# JSON 문자열로 변환
user_json = user.json()
print(user_json)
# '{"name": "John", "age": 30}'
 
# 원본 데이터로부터 파싱
user_from_json = User.parse_raw('{"name": "Alice", "age": 25}')

2. 딕셔너리 변환

# 딕셔너리로 변환
user_dict = user.dict()
print(user_dict)
# {'name': 'John', 'age': 30}
 
# None 값 제외
user_partial = user.dict(exclude_none=True)
 
# 특정 필드만 포함
user_subset = user.dict(include={'name'})
# {'name': 'John'}
 
# 특정 필드 제외
user_exclude = user.dict(exclude={'age'})
# {'name': 'John'}

FastAPI와의 연동

1. 요청 검증

from fastapi import FastAPI
from pydantic import BaseModel
 
app = FastAPI()
 
class Item(BaseModel):
    name: str
    price: float
    description: str | None = None
 
@app.post("/items/")
async def create_item(item: Item):
    # item은 자동으로 검증됨
    return item

자동 기능:

  • ✅ JSON 요청 → Item 모델로 자동 변환
  • ✅ 타입 검증 (price가 숫자 아니면 오류)
  • ✅ OpenAPI 문서 자동 생성 (Swagger UI)
  • ✅ 유효하지 않은 요청 시 상세 오류 메시지

2. 응답 검증

@app.get("/items/{item_id}", response_model=Item)
async def get_item(item_id: int):
    # 반환값이 Item 모델 검증됨
    return {"name": "Laptop", "price": 999.99}

주요 특징 & 메서드

기능메서드설명
JSON 변환.json()모델 → JSON 문자열
JSON 파싱.parse_raw()JSON 문자열 → 모델
딕셔너리 변환.dict()모델 → 딕셔너리
딕셔너리 파싱Model(**dict)딕셔너리 → 모델
복사.copy()모델 얕은 복사
검증 무시construct()검증 스킵 (위험)

에러 처리

ValidationError

from pydantic import ValidationError, BaseModel
 
class User(BaseModel):
    id: int
    name: str
    age: int
 
try:
    user = User(id="invalid", name="John", age="thirty")
except ValidationError as e:
    print(e.json())
    # [
    #   {"loc": ["id"], "msg": "value is not a valid integer", "type": "type_error.integer"},
    #   {"loc": ["age"], "msg": "value is not a valid integer", "type": "type_error.integer"}
    # ]

설정 커스터마이징

모델 설정

from pydantic import BaseModel, Extra
 
class User(BaseModel):
    name: str
    age: int
    
    class Config:
        # 추가 필드 거부
        extra = Extra.forbid  # {"name": "John", "age": 30, "email": "..."}  → 오류
        
        # 문자열 자동 trim
        anystr_strip_whitespace = True
        
        # JSON 인코딩
        json_encoders = {
            datetime: lambda v: v.isoformat()
        }

학습 설계 포인트

Cognitive Level (Bloom)

  • L2 (Understand): BaseModel 정의, 기본 검증 이해
  • L3 (Apply): Field, Validator를 이용한 커스텀 검증

권장 실습

  1. 기초: BaseModel 정의 + 자동 타입 검증
  2. 심화: Field 제약 조건 추가 (min_length, gt 등)
  3. 커스텀: Validator로 비즈니스 로직 검증
  4. FastAPI: 요청 모델로 검증 자동화

참고 자료

타 소스와의 연계

python-type-hints-fastapi (타입 힌트 기초) python-venv-poetry-conda-leapcell (Pydantic 라이브러리 설치)