Alert
์ด ๊ธ์ Claude Code์ ๋์์ ๋ฐ์ ์์ฑ๋์์ต๋๋ค
TL;DR
- pydantic์ Python์ ํ์ ํํธ๋ฅผ ๋ฐํ์์ ๊ฐ์ ํ๋ ๋ฐ์ดํฐ ๊ฒ์ฆ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- dataclass๋ ํ์ ํํธ๊ฐ ํํธ์ผ ๋ฟ์ด์ง๋ง, pydantic์ ์ค์ ๋ก ๊ฒ์ฆํ๊ณ ๋ณํํ๋ค
- FastAPI์ ์์ฒญ/์๋ต ์ฒ๋ฆฌ, ์ค์ ๊ด๋ฆฌ, ์ธ๋ถ ๋ฐ์ดํฐ ํ์ฑ ๋ฑ์์ ํต์ฌ์ ์ผ๋ก ์ฌ์ฉ๋๋ค
Sources
1. pydantic์ด๋
pydantic์ Python์ ํ์ ํํธ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ฐ์ดํฐ ๊ฒ์ฆ๊ณผ ์๋ ๋ณํ์ ํด์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค. ํต์ฌ ์ฐจ์ด๋ฅผ ์ฝ๋๋ก ๋ณด๋ฉด ๋ฐ๋ก ์ดํด๋๋ค.
dataclass โ ํ์ ํํธ๋ โํํธโ์ผ ๋ฟ
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
u = User(name="alice", age="30")
print(u.age) # "30" โ ๋ฌธ์์ด ๊ทธ๋๋ก
print(type(u.age)) # <class 'str'>age: int๋ผ๊ณ ์ ์์ง๋ง ๋ฌธ์์ด "30"์ด ๊ทธ๋๋ก ๋ค์ด๊ฐ๋ค. ๋ฐํ์์์ ํ์
์ ํ์ธํ์ง ์๋๋ค.
pydantic โ ํ์ ์ โ๊ฐ์ โํ๋ค
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
u = User(name="alice", age="30")
print(u.age) # 30 โ int๋ก ๋ณํ๋จ
print(type(u.age)) # <class 'int'>๋ฌธ์์ด "30"์ ๋ฃ์ด๋ int๋ก ์๋ ๋ณํ๋๋ค. ๋ณํ ๋ถ๊ฐ๋ฅํ๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
User(name="alice", age="abc")
# ValidationError: 1 validation error for User
# age
# Input should be a valid integer, unable to parse string as an integer์ค์น
pydantic์ ์๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค. ๋ณ๋ ์ค์น๊ฐ ํ์ํ๋ค.
pip install pydantic
2. ๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
๋ชจ๋ธ ์ ์
BaseModel์ ์์ํ๊ณ ํ๋๋ฅผ ํ์
์ด๋
ธํ
์ด์
์ผ๋ก ์ ์ธํ๋ค.
from pydantic import BaseModel
class Product(BaseModel):
name: str
price: float
quantity: int = 0 # ๊ธฐ๋ณธ๊ฐ
tags: list[str] = [] # mutable ๊ธฐ๋ณธ๊ฐ๋ ์์ (dataclass์ ๋ฌ๋ฆฌ field() ๋ถํ์)p = Product(name="ํค๋ณด๋", price="89000") # ๋ฌธ์์ด โ float ์๋ ๋ณํ
print(p)
# name='ํค๋ณด๋' price=89000.0 quantity=0 tags=[]์๋ ๋ณํ ๊ท์น
pydantic์ ๊ฐ๋ฅํ ํ ์ ์ธ๋ ํ์ ์ผ๋ก ๋ณํ์ ์๋ํ๋ค.
class Example(BaseModel):
a: int
b: float
c: str
d: bool
e = Example(a="42", b="3.14", c=123, d="yes")
print(e)
# a=42 b=3.14 c='123' d=True| ์ ๋ ฅ | ์ ์ธ ํ์ | ๊ฒฐ๊ณผ |
|---|---|---|
"42" | int | 42 |
"3.14" | float | 3.14 |
123 | str | "123" |
"yes" | bool | True |
"abc" | int | ValidationError |
ValidationError
๊ฒ์ฆ ์คํจ ์ ValidationError๊ฐ ๋ฐ์ํ๋ค. ์ด๋ค ํ๋์์ ์ด๋ค ์ด์ ๋ก ์คํจํ๋์ง ์์ธ ์ ๋ณด๋ฅผ ์ ๊ณตํ๋ค.
from pydantic import ValidationError
class User(BaseModel):
name: str
age: int
email: str
try:
User(name=123, age="abc", email="alice@example.com")
except ValidationError as e:
print(e.error_count()) # 1 โ age๋ง ์คํจ (name์ "123"์ผ๋ก ๋ณํ ๊ฐ๋ฅ)
print(e.errors())
# [{'type': 'int_parsing', 'loc': ('age',), 'msg': 'Input should be a valid integer...'}]3. Field()
Field()๋ฅผ ์ฌ์ฉํ๋ฉด ๊ธฐ๋ณธ๊ฐ, ๋ณ์นญ, ๊ฒ์ฆ ์ ์ฝ ์กฐ๊ฑด ๋ฑ์ ํ๋ ๋จ์๋ก ์ค์ ํ ์ ์๋ค.
๊ธฐ๋ณธ ์ฌ์ฉ
from pydantic import BaseModel, Field
class User(BaseModel):
name: str = Field(min_length=1, max_length=50, description="์ฌ์ฉ์ ์ด๋ฆ")
age: int = Field(ge=0, le=150, description="๋์ด")
email: str = Field(pattern=r"^[\w.-]+@[\w.-]+\.\w+$")User(name="", age=30, email="alice@example.com")
# ValidationError โ name์ด ๋น ๋ฌธ์์ด (min_length=1 ์๋ฐ)
User(name="alice", age=-1, email="alice@example.com")
# ValidationError โ age๊ฐ ์์ (ge=0 ์๋ฐ)
User(name="alice", age=30, email="not-an-email")
# ValidationError โ pattern ๋ถ์ผ์นField() ์ฃผ์ ์ต์
| ์ต์ | ์ฉ๋ | ์์ |
|---|---|---|
default | ๊ธฐ๋ณธ๊ฐ | Field(default=0) |
default_factory | mutable ๊ธฐ๋ณธ๊ฐ | Field(default_factory=list) |
alias | JSON ํค ์ด๋ฆ ๋งคํ | Field(alias="userName") |
min_length / max_length | ๋ฌธ์์ด ๊ธธ์ด ์ ํ | Field(min_length=1) |
ge / gt / le / lt | ์ซ์ ๋ฒ์ | Field(ge=0, le=100) |
pattern | ์ ๊ท์ ๊ฒ์ฆ | Field(pattern=r"^\d{3}-\d{4}$") |
description | JSON Schema ์ค๋ช | Field(description="์ฌ์ฉ์ ID") |
exclude | ์ง๋ ฌํ ์ ์ ์ธ | Field(exclude=True) |
alias โ ์ธ๋ถ JSON ํค์ Python ํ๋๋ช ๋งคํ
์ธ๋ถ API์ JSON ํค๊ฐ Python ๋ค์ด๋ฐ ์ปจ๋ฒค์ ๊ณผ ๋ค๋ฅผ ๋ ์ ์ฉํ๋ค.
class User(BaseModel):
user_name: str = Field(alias="userName")
created_at: str = Field(alias="createdAt")
# JSON์์๋ camelCase๋ก ๋ฐ๊ณ
data = {"userName": "alice", "createdAt": "2026-04-12"}
u = User(**data)
# Python์์๋ snake_case๋ก ์ ๊ทผ
print(u.user_name) # alice
print(u.created_at) # 2026-04-124. ๊ฒ์ฆ์ (Validator)
Field()์ ์ ์ฝ ์กฐ๊ฑด๋ง์ผ๋ก ๋ถ์กฑํ ๋, ์ปค์คํ
๊ฒ์ฆ ๋ก์ง์ ๋ฐ์ฝ๋ ์ดํฐ๋ก ์ ์ํ ์ ์๋ค.
@field_validator โ ํ๋ ๋จ์ ๊ฒ์ฆ
from pydantic import BaseModel, field_validator
class User(BaseModel):
username: str
password: str
@field_validator("username")
@classmethod
def username_must_be_alphanumeric(cls, v: str) -> str:
if not v.isalnum():
raise ValueError("์๋ฌธ๊ณผ ์ซ์๋ง ํ์ฉ๋ฉ๋๋ค")
return v
@field_validator("password")
@classmethod
def password_must_be_strong(cls, v: str) -> str:
if len(v) < 8:
raise ValueError("๋น๋ฐ๋ฒํธ๋ 8์ ์ด์์ด์ด์ผ ํฉ๋๋ค")
if not any(c.isupper() for c in v):
raise ValueError("๋๋ฌธ์๊ฐ ์ต์ 1๊ฐ ํฌํจ๋์ด์ผ ํฉ๋๋ค")
return vUser(username="alice123", password="MyPass123") # OK
User(username="alice!@#", password="MyPass123")
# ValidationError โ ์๋ฌธ๊ณผ ์ซ์๋ง ํ์ฉ๋ฉ๋๋ค
User(username="alice", password="short")
# ValidationError โ ๋น๋ฐ๋ฒํธ๋ 8์ ์ด์์ด์ด์ผ ํฉ๋๋ค@field_validator์ ๊ฐ ๋ณํ
validator์์ ๊ฐ์ ๋ณํํด์ ๋ฐํํ ์๋ ์๋ค. ๊ฒ์ฆ๊ณผ ์ ๊ทํ๋ฅผ ๋์์ ์ฒ๋ฆฌํ๋ค.
class Tag(BaseModel):
name: str
@field_validator("name")
@classmethod
def normalize(cls, v: str) -> str:
return v.strip().lower()
print(Tag(name=" Python ")) # name='python'@model_validator โ ๋ชจ๋ธ ๋จ์ ๊ฒ์ฆ (์ฌ๋ฌ ํ๋ ์กฐํฉ)
ํ๋ ๊ฐ ๊ด๊ณ๋ฅผ ๊ฒ์ฆํ ๋ ์ฌ์ฉํ๋ค.
from pydantic import BaseModel, model_validator
class DateRange(BaseModel):
start: str
end: str
@model_validator(mode="after")
def check_date_order(self):
if self.start >= self.end:
raise ValueError("start๋ end๋ณด๋ค ์ด์ ์ด์ด์ผ ํฉ๋๋ค")
return selfDateRange(start="2026-01-01", end="2026-12-31") # OK
DateRange(start="2026-12-31", end="2026-01-01") # ValidationErrormode=โbeforeโ vs mode=โafterโ
class User(BaseModel):
name: str
age: int
# before: ํ์
๋ณํ ์ ์ ์คํ (raw ์
๋ ฅ๊ฐ์ ๋ฐ์)
@model_validator(mode="before")
@classmethod
def preprocess(cls, data):
if isinstance(data, dict) and "full_name" in data:
data["name"] = data.pop("full_name")
return data
# after: ํ์
๋ณํ ํ์ ์คํ (๊ฒ์ฆ๋ ๋ชจ๋ธ ์ธ์คํด์ค๋ฅผ ๋ฐ์)
@model_validator(mode="after")
def postprocess(self):
self.name = self.name.title()
return selfu = User(**{"full_name": "alice", "age": 30})
print(u.name) # Alice โ before์์ ํค ๋ณํ, after์์ title() ์ ์ฉ5. ์ง๋ ฌํ/์ญ์ง๋ ฌํ
pydantic์ ์ง๋ ฌํ ๋ฉ์๋๋ฅผ ๋ด์ฅํ๊ณ ์๋ค. asdict + json.dumps ์กฐํฉ์ด ํ์ํ dataclass์ ๋ฌ๋ฆฌ ํ ์ค๋ก ์ฒ๋ฆฌ๋๋ค.
Python ๊ฐ์ฒด โ dict / JSON
class Address(BaseModel):
city: str
zipcode: str
class User(BaseModel):
name: str
age: int
address: Address
u = User(name="alice", age=30, address=Address(city="์์ธ", zipcode="06000"))
# dict๋ก ๋ณํ
print(u.model_dump())
# {'name': 'alice', 'age': 30, 'address': {'city': '์์ธ', 'zipcode': '06000'}}
# JSON ๋ฌธ์์ด๋ก ๋ณํ
print(u.model_dump_json(indent=2))
# {
# "name": "alice",
# "age": 30,
# "address": {
# "city": "์์ธ",
# "zipcode": "06000"
# }
# }dict / JSON โ Python ๊ฐ์ฒด
# dict์์ ์์ฑ
data = {"name": "bob", "age": 25, "address": {"city": "๋ถ์ฐ", "zipcode": "48000"}}
u = User.model_validate(data)
# JSON ๋ฌธ์์ด์์ ์์ฑ
json_str = '{"name": "bob", "age": 25, "address": {"city": "๋ถ์ฐ", "zipcode": "48000"}}'
u = User.model_validate_json(json_str)์ค์ฒฉ๋ ๋ชจ๋ธ๋ ์๋์ผ๋ก ํ์ฑ๋๋ค. dict ์์ {"city": "๋ถ์ฐ", "zipcode": "48000"}์ด Address ๊ฐ์ฒด๋ก ๋ณํ๋๋ค.
์ง๋ ฌํ ์ต์
class User(BaseModel):
name: str
password: str = Field(exclude=True) # ์ง๋ ฌํ ์ ์ ์ธ
age: int
u = User(name="alice", password="secret123", age=30)
print(u.model_dump())
# {'name': 'alice', 'age': 30} โ password ์ ์ธ๋จ# ํน์ ํ๋๋ง ํฌํจ/์ ์ธ
u.model_dump(include={"name", "age"}) # {'name': 'alice', 'age': 30}
u.model_dump(exclude={"age"}) # {'name': 'alice'}dataclass์ ๋น๊ต
| ์์ | dataclass | pydantic |
|---|---|---|
| dict ๋ณํ | asdict(obj) | obj.model_dump() |
| JSON ๋ณํ | json.dumps(asdict(obj)) | obj.model_dump_json() |
| dict โ ๊ฐ์ฒด | ์๋ ๋งคํ ํ์ | Model.model_validate(dict) |
| JSON โ ๊ฐ์ฒด | json.loads() + ์๋ ๋งคํ | Model.model_validate_json(str) |
| ์ค์ฒฉ ๊ฐ์ฒด ํ์ฑ | ์๋ | ์๋ |
6. ๋ชจ๋ธ ์ค์ (model_config)
model_config๋ฅผ ํตํด ๋ชจ๋ธ ์ ์ฒด์ ๋์์ ์ ์ดํ ์ ์๋ค.
from pydantic import ConfigDictstrict โ ์๋ ๋ณํ ๋๊ธฐ
๊ธฐ๋ณธ์ ์ผ๋ก pydantic์ "30" โ 30์ฒ๋ผ ๋ณํ์ ์๋ํ๋ค. strict=True๋ก ์ค์ ํ๋ฉด ์ ํํ ํ์
๋ง ํ์ฉํ๋ค.
class StrictUser(BaseModel):
model_config = ConfigDict(strict=True)
name: str
age: int
StrictUser(name="alice", age=30) # OK
StrictUser(name="alice", age="30") # ValidationError โ str์ int๊ฐ ์๋frozen โ ๋ถ๋ณ ๋ชจ๋ธ
dataclass์ frozen=True์ ๋์ผํ๋ค.
class Config(BaseModel):
model_config = ConfigDict(frozen=True)
host: str
port: int
c = Config(host="localhost", port=8080)
c.host = "0.0.0.0" # ValidationError โ frozen์ด๋ผ ๋ณ๊ฒฝ ๋ถ๊ฐextra โ ์ ์ํ์ง ์์ ํ๋ ์ฒ๋ฆฌ
# forbid: ์ ์ํ์ง ์์ ํ๋๊ฐ ์ค๋ฉด ์๋ฌ (๊ธฐ๋ณธ๊ฐ์ ignore)
class StrictModel(BaseModel):
model_config = ConfigDict(extra="forbid")
name: str
StrictModel(name="alice", unknown_field="?")
# ValidationError โ extra inputs are not permitted# allow: ์ ์ํ์ง ์์ ํ๋๋ ์ ์ฅ
class FlexModel(BaseModel):
model_config = ConfigDict(extra="allow")
name: str
m = FlexModel(name="alice", custom="value")
print(m.custom) # valuepopulate_by_name โ alias์ ํ๋๋ช ๋ ๋ค ํ์ฉ
class User(BaseModel):
model_config = ConfigDict(populate_by_name=True)
user_name: str = Field(alias="userName")
# ๋ ๋ค ๊ฐ๋ฅ
User(userName="alice") # OK (alias)
User(user_name="alice") # OK (ํ๋๋ช
)7. ์ค์ฒฉ ๋ชจ๋ธ๊ณผ JSON Schema
์ค์ฒฉ ๋ชจ๋ธ ์๋ ํ์ฑ
pydantic์ ๊ฐ์ฅ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ ์ค ํ๋๋ค. JSON์ ์ค์ฒฉ ๊ตฌ์กฐ๋ฅผ ๋ชจ๋ธ ์ ์๋ง์ผ๋ก ์๋ ํ์ฑํ๋ค.
class Address(BaseModel):
city: str
zipcode: str
class Company(BaseModel):
name: str
address: Address
class User(BaseModel):
name: str
age: int
company: Company
hobbies: list[str] = []# ๊น๊ฒ ์ค์ฒฉ๋ dict๋ฅผ ๋ฃ์ด๋ ์๋์ผ๋ก ๊ฐ ๋ชจ๋ธ๋ก ๋ณํ๋๋ค
data = {
"name": "alice",
"age": 30,
"company": {
"name": "Acme",
"address": {
"city": "์์ธ",
"zipcode": "06000"
}
},
"hobbies": ["python", "coffee"]
}
u = User(**data)
print(type(u.company)) # <class 'Company'>
print(type(u.company.address)) # <class 'Address'>
print(u.company.address.city) # ์์ธdataclass์์ ๊ฐ์ ์ผ์ ํ๋ ค๋ฉด ์ค์ฒฉ๋ dict๋ฅผ ์๋์ผ๋ก ๋งคํํด์ผ ํ๋ค.
JSON Schema ์๋ ์์ฑ
pydantic ๋ชจ๋ธ์์ JSON Schema๋ฅผ ์๋์ผ๋ก ์์ฑํ ์ ์๋ค. FastAPI๋ ์ด ๊ธฐ๋ฅ์ ์ด์ฉํด API ๋ฌธ์(Swagger UI)๋ฅผ ์๋ ์์ฑํ๋ค.
import json
schema = User.model_json_schema()
print(json.dumps(schema, indent=2))
# {
# "properties": {
# "name": { "type": "string", "title": "Name" },
# "age": { "type": "integer", "title": "Age" },
# "company": { "$ref": "#/$defs/Company" },
# "hobbies": {
# "items": { "type": "string" },
# "type": "array",
# "default": [],
# "title": "Hobbies"
# }
# },
# "required": ["name", "age", "company"],
# ...
# }8. FastAPI์์ ์ฐ๋
FastAPI๋ ๋ด๋ถ์ ์ผ๋ก pydantic์ ์ฌ์ฉํ๋ค. ์์ฒญ body๋ฅผ pydantic ๋ชจ๋ธ๋ก ์ ์ธํ๋ฉด ๊ฒ์ฆ, ๋ณํ, ๋ฌธ์ํ๊ฐ ์๋์ผ๋ก ์ฒ๋ฆฌ๋๋ค.
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class CreateUserRequest(BaseModel):
name: str = Field(min_length=1, max_length=50)
age: int = Field(ge=0, le=150)
email: str
class UserResponse(BaseModel):
id: int
name: str
email: str
@app.post("/users", response_model=UserResponse)
async def create_user(req: CreateUserRequest):
# req๋ ์ด๋ฏธ ๊ฒ์ฆ/๋ณํ์ด ์๋ฃ๋ pydantic ๋ชจ๋ธ
# name์ด ๋น ๋ฌธ์์ด์ด๊ฑฐ๋ age๊ฐ ์์๋ฉด ์ฌ๊ธฐ๊น์ง ์ค์ง ์๋๋ค (422 ์๋ฌ)
return UserResponse(id=1, name=req.name, email=req.email)์ด ์ฝ๋๋ง์ผ๋ก ๋ค์์ด ์๋์ผ๋ก ๋์ํ๋ค:
- ์์ฒญ JSON โ
CreateUserRequest๊ฒ์ฆ + ๋ณํ - ๊ฒ์ฆ ์คํจ ์ 422 Unprocessable Entity ์๋ต (์๋ฌ ๋ฉ์์ง ํฌํจ)
- ์๋ต์
UserResponse์คํค๋ง๋ก ์ง๋ ฌํ - Swagger UI(
/docs)์ ์์ฒญ/์๋ต ์คํค๋ง ์๋ ํ์
Query Parameter ๊ฒ์ฆ
body๋ฟ ์๋๋ผ query parameter์๋ pydantic ๊ฒ์ฆ์ ์ ์ฉํ ์ ์๋ค.
from fastapi import Query
@app.get("/users")
async def list_users(
page: int = Query(ge=1, default=1),
size: int = Query(ge=1, le=100, default=20),
sort: str = Query(pattern=r"^(name|age|created)$", default="name"),
):
return {"page": page, "size": size, "sort": sort}9. dataclass vs pydantic ์ ๋ฆฌ
| ๊ธฐ์ค | dataclass | pydantic |
|---|---|---|
| ์ค์น | ๋ถํ์ (ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ) | pip install pydantic |
| ํ์ ๊ฒ์ฆ | X (ํํธ๋ง) | O (๋ฐํ์ ๊ฒ์ฆ + ์๋ ๋ณํ) |
| ๊ธฐ๋ณธ๊ฐ (mutable) | field(default_factory=list) | [] ๊ทธ๋๋ก ๊ฐ๋ฅ |
| ํ๋ ์ ์ฝ ์กฐ๊ฑด | __post_init__์์ ์ง์ ๊ตฌํ | Field(ge=0, max_length=50) |
| ์ปค์คํ ๊ฒ์ฆ | __post_init__ | @field_validator, @model_validator |
| dict ๋ณํ | asdict() | .model_dump() |
| JSON ๋ณํ | asdict() + json.dumps() | .model_dump_json() |
| JSON โ ๊ฐ์ฒด | ์๋ ๋งคํ | .model_validate_json() |
| ์ค์ฒฉ ๊ฐ์ฒด ํ์ฑ | ์๋ | ์๋ |
| JSON Schema | X | .model_json_schema() |
| ๋ถ๋ณ ๋ชจ๋ธ | frozen=True | model_config = ConfigDict(frozen=True) |
| ์ฑ๋ฅ | ๋น ๋ฆ (boilerplate ์์ฑ์ผ ๋ฟ) | v2์์ ํฌ๊ฒ ๊ฐ์ (Rust ๊ธฐ๋ฐ ์ฝ์ด) |
| FastAPI ์ฐ๋ | X | ๊ธฐ๋ณธ ํตํฉ |
ํ๋จ ๊ธฐ์ค
์ธ๋ถ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ ๊ฒ์ฆํด์ผ ํ๋๊ฐ?
โโโ Yes โ pydantic
โ (API ์
๋ ฅ, JSON ํ์ฑ, ํ๊ฒฝ๋ณ์, ์ฌ์ฉ์ ์
๋ ฅ)
โโโ No โ ๋ด๋ถ ๋ฐ์ดํฐ๋ฅผ ๊ตฌ์กฐํ๋ง ํ๋ฉด ๋๋๊ฐ?
โโโ Yes โ dataclass (๊ฐ๋ณ๊ณ ํ์ค)
โโโ ๊ฒ์ฆ๋ ์ฝ๊ฐ ํ์ โ pydantic ๋๋ attrs
๊ฐ์ด ์ฐ๋ ๊ฒ๋ ๊ฐ๋ฅํ๋ค
ํ๋์ ํ๋ก์ ํธ์์ ๋์ ์์ด ์ฐ๋ ๊ฒ์ ์์ฐ์ค๋ฝ๋ค. API ๊ฒฝ๊ณ(์ ์ถ๋ ฅ)์์๋ pydantic์ผ๋ก ๊ฒ์ฆํ๊ณ , ๋ด๋ถ ๋ก์ง์์๋ dataclass๋ก ๊ฐ๋ณ๊ฒ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๋ ํจํด์ด ์ผ๋ฐ์ ์ด๋ค.