Pydantic — Models
Cara kerja model Pydantic secara mendalam: konstruksi, serialisasi, konfigurasi, dan pola yang sering dipakai.
Intisari
Field()menambahkan batasan (panjang, rentang, pola) sekaligus deskripsi yang ikut ke JSON Schema.model_config = ConfigDict(...)mengatur perilaku seluruh model — mode strict, larangan field asing, alias.model_copy(update={...})membuat salinan dengan perubahan — jangan mutasi objek yang sudah divalidasi.- Alias memisahkan nama field JSON dari nama atribut Python (
camelCase↔snake_case). extra="forbid"menolak field yang tidak dikenal — penangkap salah ketik yang sangat efektif.
Field() — batasan dan metadata
from pydantic import BaseModel, Field
class Tiket(BaseModel):
judul: str = Field(min_length=1, max_length=200)
prioritas: int = Field(default=3, ge=1, le=5)
email: str = Field(pattern=r"^[^@]+@[^@]+\.[^@]+$")
tag: list[str] = Field(default_factory=list, max_length=10)
catatan: str | None = Field(default=None, description="Catatan internal")
| Untuk | Parameter | Arti |
|---|---|---|
| Angka | ge / le | ≥ dan ≤ |
gt / lt | > dan < | |
multiple_of | Kelipatan | |
allow_inf_nan | Izinkan inf / NaN | |
| String | min_length / max_length | Panjang |
pattern | Regex | |
strip_whitespace | Pangkas spasi otomatis | |
| Koleksi | min_length / max_length | Jumlah elemen |
| Metadata | description | Ikut ke JSON Schema |
examples | Contoh nilai | |
alias | Nama alternatif di JSON |
description bukan sekadar komentar. Ia masuk ke JSON Schema, yang berarti:
muncul di dokumentasi /docs FastAPI, dan dibaca LLM saat model dipakai
sebagai definisi tool. Deskripsi yang jelas di sini langsung meningkatkan akurasi pemanggilan tool.
model_config
from pydantic import BaseModel, ConfigDict
class Ketat(BaseModel):
model_config = ConfigDict(
strict=True, # jangan konversi tipe otomatis
extra="forbid", # tolak field yang tidak dikenal
frozen=True, # immutable setelah dibuat
str_strip_whitespace=True, # pangkas spasi semua field str
validate_assignment=True, # validasi ulang saat atribut diubah
populate_by_name=True, # terima nama field ASLI meski ada alias
)
nama: str
umur: int
Perilaku extra
| Nilai | Field asing | Kapan dipakai |
|---|---|---|
"ignore" (default) | Dibuang diam-diam | Membaca API pihak ketiga yang sering menambah field |
"forbid" | Error validasi | Konfigurasi & request internal — menangkap salah ketik |
"allow" | Disimpan sebagai atribut | Jarang; data yang bentuknya benar-benar dinamis |
class Settings(BaseModel):
model_config = ConfigDict(extra="forbid")
max_token: int = 4096
Settings(max_tokens=8192) # ← salah ketik ('tokens') langsung ketahuan
Serialisasi lanjutan
t.model_dump()
t.model_dump(mode="json") # datetime → string ISO, dst — siap json.dumps
t.model_dump(exclude_none=True)
t.model_dump(exclude_unset=True) # hanya field yang benar-benar diisi pemanggil
t.model_dump(exclude_defaults=True) # hanya yang berbeda dari default
t.model_dump(include={"judul", "prioritas"})
t.model_dump(by_alias=True) # pakai nama alias, bukan nama Python
Bedanya exclude_unset dan exclude_defaults:
t = Tiket(judul="A", prioritas=3) # 3 kebetulan sama dengan default
t.model_dump(exclude_unset=True) # {'judul': 'A', 'prioritas': 3} ← diisi eksplisit
t.model_dump(exclude_defaults=True) # {'judul': 'A'} ← nilainya = default
exclude_unset=True adalah pola yang tepat untuk endpoint PATCH.
Menyalin, bukan memutasi
t2 = t.model_copy(update={"prioritas": 5})
t3 = t.model_copy(deep=True) # salin juga objek bersarangnya
Mengubah atribut secara langsung (t.prioritas = 99) tidak divalidasi
kecuali kamu menyalakan validate_assignment=True. Kebiasaan yang lebih aman:
perlakukan objek yang sudah tervalidasi sebagai read-only, dan buat salinan saat perlu berubah.
Alias
from pydantic import BaseModel, Field
class Respons(BaseModel):
id_pengguna: int = Field(alias="userId")
dibuat_pada: str = Field(alias="createdAt")
r = Respons.model_validate({"userId": 1, "createdAt": "2026-01-01"})
r.id_pengguna # 1 — nama Python tetap snake_case
r.model_dump(by_alias=True) # {'userId': 1, 'createdAt': '...'}
Untuk mengubah semua field sekaligus tanpa menulis alias satu per satu:
from pydantic.alias_generators import to_camel
class Respons(BaseModel):
model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True)
id_pengguna: int
dibuat_pada: str
Tipe khusus bawaan
from pydantic import BaseModel, EmailStr, HttpUrl, SecretStr, PositiveInt
from datetime import datetime
from uuid import UUID
class Akun(BaseModel):
id: UUID
email: EmailStr # butuh: uv add "pydantic[email]"
situs: HttpUrl
api_key: SecretStr # tidak muncul saat di-print / log
kuota: PositiveInt
dibuat: datetime # menerima string ISO-8601, mengeluarkan datetime
SecretStr layak jadi kebiasaan. Saat di-print atau masuk log, isinya tampil
sebagai **********. Ambil nilai aslinya secara eksplisit dengan
.get_secret_value() — jadi kebocoran ke log tidak bisa terjadi tanpa sengaja.
Union yang dibedakan (discriminated union)
from typing import Literal
from pydantic import BaseModel, Field
class BlokTeks(BaseModel):
type: Literal["text"]
text: str
class BlokTool(BaseModel):
type: Literal["tool_use"]
name: str
input: dict
class Respons(BaseModel):
content: list[BlokTeks | BlokTool] = Field(discriminator="type")
Pydantic memakai field type untuk memilih model yang tepat — lebih cepat dan pesan
errornya jauh lebih jelas dibanding mencoba semua kemungkinan satu per satu. Bentuk ini persis
seperti struktur content pada respons API Claude.
Memvalidasi tanpa membuat class
from pydantic import TypeAdapter
adapter = TypeAdapter(list[Tiket])
tiket = adapter.validate_python(data_json) # validasi list langsung
TypeAdapter(dict[str, int]).validate_python({"a": "1"}) # {'a': 1}
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.