Python 빌트인 타입 힌트는 정적 — 타입 체커가 보고, 인터프리터가 무시. Pydantic 이 같은 힌트 읽고 런타임에 검증 — 잘못된 데이터로 Pydantic 모델 인스턴스화하면 유용한 메시지 가진 ValidationError raise. FastAPI 가 요청/응답 검증에 사용, config 라이브러리, "parse, don't validate" 중요한 모든 곳.
BaseModel — 기반
BaseModel 서브클래스, 타입 힌트로 필드 선언. kwargs 로 인스턴스화하면 모든 거 검증 — 타입 강제 (필드가 int 면 문자열 "42" 가 int 42 됨), 누락 필수 필드 raise, 추가 필드 무시 또는 거부 (config 따라). 모델 인스턴스가 속성 접근 가진 일반 Python 객체.
디폴트 값과 Field()
직접 디폴트가 Python 처럼 작동. Field() 가 메타데이터 추가 — Field(default=10, ge=0, le=100) 가 필드 0 과 100 사이 검증. 문자열엔 Field(min_length=1, max_length=50). mutable 디폴트엔 Field(default_factory=list) — Python mutable 디폴트와 같은 함정, 같은 해결.
Pydantic v2 vs v1
Pydantic v2 (2023 릴리스) 가 메이저 재작성, ~10x 빠름, v1 에서 breaking 변경. 야생 대부분 코드가 이제 v2. 모양 비슷 — 같은 BaseModel, 비슷한 필드 — 근데 v1 은 .dict(), v2 는 .model_dump(). 항상 어느 버전인지 체크, 섞으면 고통.
Pippa 가 어디 사용
Pippa 의 FastAPI 라우트가 요청/응답 모양에 Pydantic 모델. 모든 chat 메시지, council pick, 설정 업데이트 — Pydantic 모델. 검증이 API 경계에서, 애플리케이션 안에선 파싱 쓰레기 없는 멋진 타입 객체.
Code
Pydantic 기본·python
# pip install pydantic
from pydantic import BaseModel, Field
class User(BaseModel):
name: str
age: int = Field(ge=0, le=150)
email: str | None = None
tags: list[str] = Field(default_factory=list)
# dict 에서 생성
u = User(name="alice", age=30)
print(u) # name='alice' age=30 email=None tags=[]
print(u.name, u.age)
# 생성 시 검증
try:
bad = User(name="alice", age=200) # age 범위 밖
except Exception as e:
print("ValidationError:", str(e)[:200])
# 타입 강제 — '30' 이 30 됨
u2 = User(name="bob", age="30")
print(u2.age, type(u2.age)) # 30 <class 'int'>
JSON round-trip·python
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
tags: list[str] = []
u = User(name="alice", age=30, tags=["admin", "user"])
# Pydantic v2
print(u.model_dump()) # dict
print(u.model_dump_json()) # JSON 문자열
# Round-trip
from_json = User.model_validate_json(u.model_dump_json())
print(from_json == u) # True
# Pydantic v1 은 .dict() 와 .json() — 다른 메서드 이름
검증 메시지 — 진짜 유용·python
from pydantic import BaseModel, ValidationError, Field
class Order(BaseModel):
item: str = Field(min_length=1)
quantity: int = Field(gt=0)
price: float = Field(ge=0.0)
try:
Order(item="", quantity=-1, price=-5)
except ValidationError as e:
for err in e.errors():
print(err["loc"], err["msg"])
# 출력:
# ('item',) String should have at least 1 character
# ('quantity',) Input should be greater than 0
# ('price',) Input should be greater than or equal to 0
중첩 모델 — 합성·python
from pydantic import BaseModel
class Address(BaseModel):
street: str
city: str
zip: str
class User(BaseModel):
name: str
addresses: list[Address] = []
u = User(
name="alice",
addresses=[
{"street": "123 Main", "city": "Seoul", "zip": "01234"},
{"street": "456 Oak", "city": "Busan", "zip": "56789"},
],
)
# 중첩 dict 자동 검증 + Address 인스턴스로 변환
print(u.addresses[0].city) # 'Seoul'
print(type(u.addresses[0])) # <class '...Address'>
FastAPI 라우트 — Pippa 가 실제로 하는 거·python
# Pippa 의 chat 라우트가 Pydantic 어떻게 쓰는지 sketch
from pydantic import BaseModel
class ChatMessage(BaseModel):
role: str # 'user' | 'assistant' (더 빡빡엔 Literal)
content: str
timestamp: float | None = None
class ChatRequest(BaseModel):
conversation_id: str
message: ChatMessage
brain: str = "claude"
extended_thinking: bool = False
class ChatResponse(BaseModel):
conversation_id: str
message_id: str
content: str
brain: str
# FastAPI 에서:
# @router.post('/api/chat', response_model=ChatResponse)
# async def chat(req: ChatRequest) -> ChatResponse:
# ...
# FastAPI 가 요청 body 자동 검증, 응답 자동 직렬화.
Pydantic BaseModelQuest 정의 — name: str (min length 1), tracks: list[str] (디폴트 빈), difficulty: Literal['easy', 'medium', 'hard'], estimated_hours: float = Field(gt=0). 유효 Quest 생성. 무효 거 (예 — 빈 name, 음수 hours, 알 수 없는 difficulty) 만들고 ValidationError 잡아서 각 에러의 loc 와 msg 출력. .model_dump_json() 와 .model_validate_json() 으로 round-trip.
Progress
Progress is local-only — sign in to sync across devices.