← Semua pembelajaran / Python untuk AI Engineer
Fase 2 · Type Hints & Pydantic

Pydantic — Validators

Validasi kustom di Pydantic: aturan per-field, aturan lintas-field, dan kapan memakai mode before atau after.

Sumber asli pydantic.dev Resmi Rangkuman ~6 menit baca

Intisari

  • @field_validator untuk satu field. @model_validator untuk aturan yang melibatkan beberapa field.
  • Mode after (default) berjalan setelah tipe dicek — nilainya sudah pasti tipe yang benar.
  • Mode before berjalan pada data mentah — untuk membersihkan atau mengubah bentuk input.
  • Validator harus mengembalikan nilainya. Lupa return membuat field jadi None.
  • Lempar ValueError untuk menolak. Pydantic membungkusnya jadi ValidationError yang 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 berjalanSetelah tipe divalidasiSebelum apa pun dicek
Nilai yang diterimaSudah bertipe benarData mentah apa adanya
Untuk apaAturan bisnisMembersihkan / 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)
KebutuhanPakai
Rentang angka, panjang string, regexField(...)
Daftar nilai yang sahLiteral[...] atau StrEnum
Email, URL, UUIDTipe bawaan Pydantic
Aturan bisnis satu field@field_validator
Aturan melibatkan beberapa field@model_validator(mode="after")
Mengubah bentuk input mentahmode="before"

Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.