파이썬 Pydantic 데이터 검증 가이드: API 입출력 타입과 스키마 설계
파이썬 Pydantic v2로 API 요청·응답 모델을 만들고 타입 변환, Field 제약조건, 중첩 모델, 커스텀 검증, 오류 처리와 JSON Schema 생성을 구현하는 방법을 설명합니다.

API가 받은 JSON을 그대로 사용하면 누락된 필드, 잘못된 자료형, 허용 범위를 벗어난 값 때문에 오류가 늦게 발견될 수 있습니다. Pydantic은 파이썬 타입 힌트로 데이터 구조를 선언하고 입력값을 검증·변환하며, 오류 위치와 원인을 구조화해 보여주는 도구입니다. 이 글에서는 Pydantic v2 기준으로 요청·응답 스키마를 만드는 핵심 흐름을 설명합니다.
핵심 요약
BaseModel을 상속하고 타입 힌트로 필드를 선언하면 검증 스키마가 됩니다.- 기본 모드에서는 가능한 값을 지정한 타입으로 변환하지만, 엄격한 검증이 필요하면 strict mode를 사용합니다.
Field()로 길이·범위·기본값·별칭 같은 제약을 설정합니다.- 검증 실패는
ValidationError로 발생하며errors()에서 필드 위치와 사유를 확인할 수 있습니다. model_dump(),model_dump_json(),model_json_schema()로 결과와 JSON Schema를 생성합니다.
API 데이터 검증에 Pydantic이 필요한 이유
파이썬의 타입 힌트는 코드의 의도를 표현하고 정적 분석을 돕지만, 일반적으로 외부에서 들어온 값을 실행 시점에 자동 검증하지는 않습니다. Pydantic은 타입 힌트를 실제 검증 규칙으로 사용해 결과 모델이 선언된 타입과 제약조건을 따르도록 만듭니다.
| 문제 상황 | 단순 딕셔너리 | Pydantic 모델 |
|---|---|---|
| 필수값 누락 | 사용 지점에서 오류 | 모델 생성 시 즉시 확인 |
| 자료형 불일치 | 직접 변환 코드 필요 | 허용 범위에서 변환 또는 거부 |
| 값 범위 | 조건문을 반복 작성 | Field() 제약으로 선언 |
| 오류 응답 | 형식을 직접 설계 | 필드 위치와 오류 유형 제공 |
| API 문서 | 스키마 별도 관리 | JSON Schema 생성 가능 |
여기서 검증은 입력이 원래부터 올바른 타입인지 확인하는 것만 뜻하지 않습니다. 기본 설정에서는 문자열 "123"을 정수 123으로 바꾸는 것처럼, 허용 가능한 입력을 목표 타입에 맞게 변환할 수 있습니다. 원본 입력의 타입까지 엄격하게 제한해야 한다면 strict mode를 선택해야 합니다.
BaseModel로 첫 데이터 모델 만들기
설치는 pip install pydantic으로 진행합니다. 모델은 BaseModel을 상속한 클래스에 필드를 타입 힌트로 선언합니다. 기본값이 없는 필드는 필수이며, 기본값이 있는 필드는 생략할 수 있습니다.
from datetime import datetime
from pydantic import BaseModel
class UserCreate(BaseModel):
username: str
age: int
email: str | None = None
joined_at: datetime | None = None
payload = {
"username": "jin",
"age": "52",
"email": "jin@example.com"
}
user = UserCreate.model_validate(payload)
print(user.age) # 52
print(type(user.age)) # int
print(user.model_dump())
model_validate()는 딕셔너리나 객체를 모델에 맞게 검증합니다. JSON 문자열을 바로 검증하려면 model_validate_json()을 사용할 수 있습니다. 모델을 직접 UserCreate(**payload) 형태로 생성해도 검증이 수행됩니다.
Field로 길이와 숫자 범위 제한하기
자료형만으로 부족한 규칙은 Field()에 선언합니다. 문자열 최소·최대 길이, 숫자의 최소·최대값, 별칭, 설명 등을 모델 정의 가까이에 모을 수 있습니다.
from typing import Annotated
from pydantic import BaseModel, Field
class ProductCreate(BaseModel):
name: Annotated[str, Field(min_length=2, max_length=80)]
price: Annotated[int, Field(gt=0, le=10_000_000)]
quantity: Annotated[int, Field(ge=0, le=100_000)] = 0
sku: Annotated[str, Field(pattern=r"^[A-Z0-9-]+$")]
product = ProductCreate(
name="무선 키보드",
price=59000,
sku="KEY-100"
)
gt는 초과, ge는 이상, lt는 미만, le는 이하를 의미합니다. Annotated를 사용하면 타입과 부가 제약조건을 함께 표현하기 좋습니다. 업무 규칙이 바뀌면 여러 조건문을 찾는 대신 모델 정의를 중심으로 수정할 수 있습니다.
중첩 모델과 리스트 검증하기
API 데이터는 주문 안에 고객과 상품 목록이 들어가는 중첩 구조가 많습니다. 각 구조를 별도 모델로 나눈 뒤 상위 모델의 필드 타입으로 사용하면, 중첩된 위치까지 재귀적으로 검증됩니다.
from pydantic import BaseModel, Field
class OrderItem(BaseModel):
product_id: int
quantity: int = Field(ge=1)
unit_price: int = Field(gt=0)
class OrderCreate(BaseModel):
customer_id: int
items: list[OrderItem] = Field(min_length=1)
memo: str | None = None
order = OrderCreate.model_validate({
"customer_id": 10,
"items": [
{"product_id": 101, "quantity": 2, "unit_price": 15000}
]
})
검증 오류가 두 번째 상품의 수량에서 발생하면 오류 위치에 items, 인덱스, quantity가 함께 표시될 수 있습니다. 클라이언트에 오류를 전달할 때 이 위치 정보를 활용하면 어떤 입력을 고쳐야 하는지 명확하게 안내할 수 있습니다.
field_validator로 업무 규칙 추가하기
필드 길이와 범위만으로 표현하기 어려운 규칙은 검증기를 사용합니다. Pydantic v2에서는 field_validator로 특정 필드의 값을 검사할 수 있습니다. 검증기는 성공 시 값을 반환하고, 조건에 맞지 않으면 ValueError 등을 발생시킵니다.
from pydantic import BaseModel, field_validator
class SignupRequest(BaseModel):
username: str
referral_code: str | None = None
@field_validator("username")
@classmethod
def username_must_be_clean(cls, value: str) -> str:
cleaned = value.strip()
if " " in cleaned:
raise ValueError("사용자 이름에는 공백을 넣을 수 없습니다.")
if len(cleaned) < 3:
raise ValueError("사용자 이름은 3자 이상이어야 합니다.")
return cleaned
여러 필드의 관계를 확인해야 한다면 모델 단위 검증기를 검토합니다. 예를 들어 종료일이 시작일보다 늦어야 한다거나 비밀번호 확인값이 일치해야 하는 규칙입니다. 다만 단순 제약조건까지 모두 커스텀 검증기로 만들면 재사용성과 JSON Schema 표현력이 떨어질 수 있으므로 먼저 기본 타입과 Field()로 해결할 수 있는지 확인하세요.
자동 타입 변환과 strict mode 차이
Pydantic 기본 모드는 실용적인 데이터 변환을 지원합니다. 숫자 형태의 문자열을 정수로 바꾸는 동작은 HTML 폼이나 쿼리 문자열을 처리할 때 편리하지만, 변환이 예상하지 못한 입력을 통과시킬 수도 있습니다.
| 검증 방식 | 입력 "123"을 int로 선언 |
적합한 상황 |
|---|---|---|
| 기본 모드 | 가능하면 123으로 변환 | 폼·쿼리·일반 API 입력 |
| 엄격 모드 | 문자열이므로 거부 | 내부 계약, 금융 수치, 형식 보존 |
from pydantic import BaseModel, ConfigDict
class StrictPayload(BaseModel):
model_config = ConfigDict(strict=True)
count: int
# StrictPayload(count="3") # ValidationError
StrictPayload(count=3) # 정상
무조건 엄격 모드가 더 좋은 것은 아닙니다. 입력 채널과 데이터 계약을 기준으로 정해야 합니다. 외부 요청을 편리하게 받되 특정 필드만 엄격하게 제한하거나, 모델 전체를 엄격하게 설정하는 방식을 구분하세요.
ValidationError를 안전하게 처리하기
검증에 실패하면 ValidationError가 발생합니다. 오류 문자열만 저장하기보다 errors()로 구조화된 내용을 확인하면 위치, 오류 유형, 메시지와 문제 입력을 분리해 처리할 수 있습니다.
from pydantic import ValidationError
bad_payload = {
"name": "A",
"price": -100,
"quantity": "많음",
"sku": "invalid sku"
}
try:
ProductCreate.model_validate(bad_payload)
except ValidationError as error:
for item in error.errors():
print(item["loc"], item["type"], item["msg"])
오류 응답에 원본 입력을 그대로 포함하면 비밀번호, 토큰, 주민등록번호 같은 민감정보가 노출될 수 있습니다. 로그와 클라이언트 응답에 넣을 필드를 구분하고, 외부에는 사용자가 수정하는 데 필요한 내용만 전달해야 합니다.
model_dump와 JSON Schema 생성
검증된 모델은 model_dump()로 딕셔너리, model_dump_json()으로 JSON 문자열로 변환할 수 있습니다. 기본값이 설정되지 않은 필드만 보내거나 None을 제외하는 등 직렬화 옵션도 지정할 수 있습니다.
data = product.model_dump(exclude_none=True)
json_text = product.model_dump_json(exclude_none=True)
schema = ProductCreate.model_json_schema()
print(data)
print(schema["properties"].keys())
model_json_schema()는 모델의 필드와 제약조건을 JSON Schema로 표현합니다. API 문서 생성이나 다른 시스템과 데이터 계약을 공유할 때 유용합니다. 다만 스키마가 생성된다고 실제 업무 의미까지 자동 검증되는 것은 아니므로 커스텀 규칙과 데이터베이스 제약도 함께 설계해야 합니다.
실수 방지 체크리스트
- Pydantic v1 예제와 v2 문법을 섞지 않았는지 확인합니다.
- 기본값이 없는 필드는 필수라는 점을 확인합니다.
Optional또는| None과 기본값= None을 구분합니다.- 자동 타입 변환을 허용할지 strict mode를 사용할지 정합니다.
- 단순 제약은 커스텀 검증기보다
Field()를 우선 검토합니다. - 중첩 목록에서 오류 위치를 클라이언트가 이해할 수 있게 변환합니다.
- 오류 로그에 민감한 원본 입력이 남지 않도록 필터링합니다.
- 직렬화 시
None, 기본값, 별칭 포함 여부를 확인합니다. - 모델 변경 시 API 소비자와 JSON Schema 호환성을 점검합니다.
자주 묻는 질문
1. Pydantic은 타입 검사기인가요?
타입 힌트를 이용하지만 mypy 같은 정적 타입 검사기와 역할이 다릅니다. Pydantic은 실행 시점의 입력을 검증하고 변환해 모델을 만듭니다.
2. 문자열 숫자가 정수로 바뀌는 것이 정상인가요?
기본 모드에서는 가능한 경우 타입 변환이 수행됩니다. 변환을 허용하지 않으려면 strict mode를 설정해야 합니다.
3. 선택 필드는 어떻게 선언하나요?
str | None = None처럼 None을 허용하고 기본값을 지정합니다. None을 허용하지만 기본값이 없으면 필수 필드가 될 수 있으므로 구분해야 합니다.
4. model_validate와 생성자 호출의 차이는 무엇인가요?
둘 다 검증을 수행합니다. model_validate()는 주어진 객체를 모델로 검증한다는 의도를 명확히 표현하고 관련 옵션을 사용할 수 있습니다.
5. dict() 대신 무엇을 사용하나요?
Pydantic v2에서는 일반적으로 model_dump()를 사용합니다. 오래된 v1 예제의 dict()와 문법을 혼동하지 않는 것이 좋습니다.
6. JSON 문자열은 먼저 json.loads 해야 하나요?
반드시 그런 것은 아닙니다. model_validate_json()으로 JSON 문자열이나 바이트 데이터를 바로 검증할 수 있습니다.
7. 검증 없이 모델을 만들 수 있나요?
model_construct()가 있지만 신뢰할 수 없는 입력에는 사용하면 안 됩니다. 검증 비용과 필요성을 충분히 이해한 제한된 상황에서만 검토하세요.
8. 이메일 형식도 검증할 수 있나요?
Pydantic의 네트워크 관련 타입을 사용할 수 있으며 일부 기능은 추가 의존성이 필요할 수 있습니다. 프로젝트 설치 옵션과 공식 문서를 확인하세요.
9. API 응답 모델에도 Pydantic을 써야 하나요?
응답 구조를 일정하게 유지하고 직렬화·문서화를 연결하려면 유용합니다. 내부 객체의 모든 필드를 외부 응답에 그대로 노출하지 않도록 별도 응답 모델을 두는 편이 안전합니다.
10. 데이터베이스 검증을 Pydantic으로 대체할 수 있나요?
대체할 수 없습니다. Pydantic은 애플리케이션 입력 검증을 담당하며, 고유키·외래키·트랜잭션 같은 데이터 무결성은 데이터베이스에서도 보장해야 합니다.
공식 출처
공식 문서 확인일: 2026년 8월 29일