Pydantic — Validators
Validasi kustom di Pydantic: aturan per-field, aturan lintas-field, dan kapan memakai mode before atau after.
Intisari
@field_validatoruntuk satu field.@model_validatoruntuk aturan yang melibatkan beberapa field.- Mode
after(default) berjalan setelah tipe dicek — nilainya sudah pasti tipe yang benar. - Mode
beforeberjalan pada data mentah — untuk membersihkan atau mengubah bentuk input. - Validator harus mengembalikan nilainya. Lupa
returnmembuat field jadiNone. - Lempar
ValueErroruntuk menolak. Pydantic membungkusnya jadiValidationErroryang rapi.
@field_validator — satu field
from pydantic import BaseModel, field_validator
class Pengguna(BaseModel):
email: str
username: str
@field_validator("email")
@classmethod
def email_harus_valid(cls, v: str) -> str:
if "@" not in v:
raise ValueError("format email tidak valid")
return v.lower() # ← WAJIB return; nilainya bisa diubah di sini
@field_validator("username")
@classmethod
def username_alfanumerik(cls, v: str) -> str:
if not v.isalnum():
raise ValueError("username hanya boleh huruf dan angka")
return v
Tiga hal yang wajib benar: dekorator @classmethod ada di bawah
@field_validator (bukan di atas), parameter pertamanya cls,
dan fungsinya mengembalikan nilai. Lupa return adalah kesalahan
paling umum — field-nya jadi None tanpa error.
Beberapa field sekaligus
@field_validator("judul", "deskripsi")
@classmethod
def tidak_boleh_kosong(cls, v: str) -> str:
if not v.strip():
raise ValueError("tidak boleh kosong")
return v.strip()
@field_validator("*") # semua field
@classmethod
def rapikan(cls, v):
return v.strip() if isinstance(v, str) else v
Mode before vs after
mode="after" (default) | mode="before" | |
|---|---|---|
| Kapan berjalan | Setelah tipe divalidasi | Sebelum apa pun dicek |
| Nilai yang diterima | Sudah bertipe benar | Data mentah apa adanya |
| Untuk apa | Aturan bisnis | Membersihkan / mengubah bentuk input |
class Artikel(BaseModel):
tag: list[str]
@field_validator("tag", mode="before")
@classmethod
def terima_string_dipisah_koma(cls, v):
if isinstance(v, str):
return [t.strip() for t in v.split(",")]
return v
Artikel.model_validate({"tag": "ai, python, llm"})
# tag=['ai', 'python', 'llm']
Kapan butuh before: saat data dari luar datang dalam bentuk yang tidak persis
seperti yang kamu inginkan, tapi masih bisa diterjemahkan. Contoh nyata: query parameter yang
selalu string, CSV yang memakai "NULL" untuk nilai kosong, atau tanggal dengan
format tidak standar.
@model_validator — aturan lintas field
from pydantic import BaseModel, model_validator
from typing import Self
class Rentang(BaseModel):
mulai: int
selesai: int
@model_validator(mode="after")
def cek_urutan(self) -> Self:
if self.mulai >= self.selesai:
raise ValueError("mulai harus lebih kecil dari selesai")
return self # ← kembalikan self, bukan nilai field
Contoh yang sering dipakai — memastikan salah satu dari dua field terisi:
class Kredensial(BaseModel):
api_key: str | None = None
aws_profile: str | None = None
@model_validator(mode="after")
def salah_satu_wajib(self) -> Self:
if not (self.api_key or self.aws_profile):
raise ValueError("isi salah satu: api_key atau aws_profile")
if self.api_key and self.aws_profile:
raise ValueError("jangan isi keduanya sekaligus")
return self
model_validator(mode="before")
class Konfigurasi(BaseModel):
host: str
port: int
@model_validator(mode="before")
@classmethod
def pecah_alamat(cls, data):
if isinstance(data, dict) and "alamat" in data:
host, port = data.pop("alamat").split(":")
data["host"], data["port"] = host, port
return data
Konfigurasi.model_validate({"alamat": "localhost:8000"})
# host='localhost' port=8000
Validator dengan konteks
from pydantic import ValidationInfo, field_validator
class Password(BaseModel):
kata_sandi: str
konfirmasi: str
@field_validator("konfirmasi")
@classmethod
def harus_cocok(cls, v: str, info: ValidationInfo) -> str:
if "kata_sandi" in info.data and v != info.data["kata_sandi"]:
raise ValueError("konfirmasi tidak cocok")
return v
info.data hanya berisi field yang sudah divalidasi sebelumnya — mengikuti
urutan deklarasi. Untuk aturan lintas-field, @model_validator(mode="after") hampir
selalu lebih jelas dan tidak bergantung urutan.
Validator yang bisa dipakai ulang
from typing import Annotated
from pydantic import AfterValidator
def tidak_kosong(v: str) -> str:
if not v.strip():
raise ValueError("tidak boleh kosong")
return v.strip()
TeksIsi = Annotated[str, AfterValidator(tidak_kosong)]
class Artikel(BaseModel):
judul: TeksIsi
isi: TeksIsi
ringkasan: TeksIsi
Ini pola yang lebih rapi daripada menulis @field_validator yang sama berulang kali di banyak model.
Validator serialisasi
from pydantic import field_serializer
from datetime import datetime
class Kejadian(BaseModel):
waktu: datetime
@field_serializer("waktu")
def format_waktu(self, v: datetime) -> str:
return v.strftime("%Y-%m-%d %H:%M")
Kapan tidak perlu validator
Sebagian besar kebutuhan sudah tercakup Field() — dan itu lebih cepat serta ikut ke JSON Schema:
# ❌ berlebihan
@field_validator("umur")
@classmethod
def umur_positif(cls, v: int) -> int:
if v < 0:
raise ValueError("harus positif")
return v
# ✅ cukup begini
umur: int = Field(ge=0)
| Kebutuhan | Pakai |
|---|---|
| Rentang angka, panjang string, regex | Field(...) |
| Daftar nilai yang sah | Literal[...] atau StrEnum |
| Email, URL, UUID | Tipe bawaan Pydantic |
| Aturan bisnis satu field | @field_validator |
| Aturan melibatkan beberapa field | @model_validator(mode="after") |
| Mengubah bentuk input mentah | mode="before" |
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.