Saat membangun aplikasi Python, kamu pasti sering menerima data dari luar: respon API, input pengguna, file JSON, atau konfigurasi. Masalahnya, Python adalah bahasa dynamically typed — ia tidak akan menghentikan programmu ketika kamu memasukkan string "123" ke fungsi yang mengharapkan angka. Akibatnya, bug sering baru ketahuan setelah aplikasi berjalan dan data sudah menyebar ke seluruh sistem. Di sinilah Pydantic berperan: library ini memungkinkan kamu mendefinisikan struktur dan tipe data secara eksplisit, lalu memvalidasinya secara otomatis saat runtime. Dalam tutorial ini, kita akan membahas Pydantic v2 dari nol hingga mahir, lengkap dengan contoh kode yang bisa langsung kamu jalankan.

Catatan untuk pengguna Pydantic v1: Tutorial ini menggunakan Pydantic v2. Beberapa API berubah, misalnya @validator menjadi @field_validator, dan .dict()/.json() digantikan oleh model_dump()/model_dump_json(). Pastikan kamu menginstal versi 2.x agar semua contoh di bawah berjalan.

Apa Itu Pydantic dan Mengapa Kamu Membutuhkannya?

Pydantic adalah library validasi data untuk Python yang dibangun di atas fitur type hints. Alih-alih menulis puluhan baris kode if untuk mengecek tipe dan nilai data, kamu cukup mendefinisikan sebuah model dengan anotasi tipe, dan Pydantic akan menangani sisanya: memvalidasi tipe, mengonversi data, menampilkan pesan error yang jelas, serta menyediakan serialisasi ke JSON. Pydantic v2 (dirilis tahun 2023) ditulis ulang dengan engine Rust di balik layar, sehingga jauh lebih cepat — bahkan hingga 5–50× lebih cepat dari v1 untuk banyak kasus.

Kenapa Pydantic penting? Pydantic adalah fondasi banyak framework populer seperti FastAPI, LangChain, dan LlamaIndex. Dengan memahami Pydantic, kamu otomatis memahami cara kerja validasi data di belakang framework-framework tersebut.

Langkah 1 — Instalasi Pydantic v2

Pastikan kamu menggunakan Python 3.8 ke atas, lalu instal Pydantic melalui pip. Jika kamu menggunakan uv sebagai package manager, prosesnya juga sama mudahnya.

bash
pip install "pydantic>=2"

Kamu bisa mengecek versi yang terpasang dengan perintah singkat berikut:

python
import pydantic
print(pydantic.VERSION)
# Output contoh: 2.7.0

Langkah 2 — Membuat Model Pertama dengan BaseModel

Inti dari Pydantic adalah class BaseModel. Kamu mendefinisikan model dengan mewarisi BaseModel dan menuliskan atribut beserta anotasi tipenya, persis seperti menulis dataclass. Mari kita buat model sederhana untuk data pengguna.

python
from pydantic import BaseModel
from datetime import datetime

class User(BaseModel):
    id: int
    name: str
    email: str
    is_active: bool = True
    created_at: datetime

# Data mentah dari "dunia luar" (misalnya respon API)
data = {
    "id": "123",                  # string -> otomatis dikonversi ke int
    "name": "Budi Santoso",
    "email": "budi@example.com",
    "created_at": "2024-05-01T10:30:00",
}

user = User(**data)
print(user)
print(user.id, type(user.id))                  # 123 <class 'int'>
print(user.created_at, type(user.created_at))  # objek datetime

Perhatikan hal menarik di atas: Pydantic mengonversi string "123" menjadi integer 123, dan string tanggal menjadi objek datetime secara otomatis. Proses ini disebut coercion. Jika datanya benar-benar tidak valid, Pydantic akan melempar ValidationError dengan pesan yang sangat informatif.

Langkah 3 — Menangkap Error Validasi

python
from pydantic import BaseModel, ValidationError

class User(BaseModel):
    id: int
    name: str
    age: int

try:
    User(id="bukan-angka", name="Budi", age="dua puluh")
except ValidationError as e:
    print(e)

Error yang dihasilkan akan menampilkan lokasi field yang bermasalah, tipe yang diharapkan, dan nilai yang diterima — jauh lebih mudah dibaca daripada sekadar TypeError biasa. Ini sangat membantu saat melakukan debugging pada data yang datang dari API eksternal.

Langkah 4 — Menambah Constraint dengan Field

Selain tipe, kamu juga bisa menambahkan batasan (constraint) pada nilai, seperti panjang minimal, nilai maksimal, atau pola tertentu. Gunakan fungsi Field() untuk itu.

python
from pydantic import BaseModel, Field

class Product(BaseModel):
    name: str = Field(min_length=3, max_length=50)
    price: float = Field(gt=0)          # harus lebih besar dari 0
    stock: int = Field(ge=0, le=1000)   # 0 <= stock <= 1000
    tags: list[str] = []

print(Product(name="Laptop", price=12000000, stock=15))

Untuk validasi yang lebih spesifik, Pydantic juga menyediakan tipe khusus seperti EmailStr untuk alamat email dan AnyUrl untuk URL.

python
from pydantic import BaseModel, EmailStr, AnyUrl

class Contact(BaseModel):
    email: EmailStr
    website: AnyUrl | None = None

c = Contact(email="budi@example.com", website="https://bengkelkode.com")
print(c)

Tipe EmailStr dan AnyUrl memerlukan package tambahan email-validator. Instal sekaligus dengan pip install "pydantic[email]" agar validasi email berjalan akurat.

Langkah 5 — Validator Kustom dengan @field_validator

Kadang validasi bawaan tidak cukup. Misalnya, kamu ingin memastikan username ditulis dalam format tertentu atau password memenuhi aturan keamanan. Gunakan decorator @field_validator untuk menambah logika validasi sendiri.

python
from pydantic import BaseModel, field_validator

class Account(BaseModel):
    username: str
    password: str

    @field_validator("username")
    @classmethod
    def normalize_username(cls, v: str) -> str:
        v = v.strip().lower()
        if " " in v:
            raise ValueError("username tidak boleh mengandung spasi")
        return v

    @field_validator("password")
    @classmethod
    def password_strength(cls, v: str) -> str:
        if len(v) < 8:
            raise ValueError("password minimal 8 karakter")
        return v

user = Account(username="Budi", password="rahasia123")
print(user.username)  # 'budi'

Langkah 6 — Model Bertingkat (Nested Models)

Data di dunia nyata jarang berbentuk datar. Sering kali kamu punya objek di dalam objek. Pydantic menangani ini dengan sangat alami: cukup gunakan model lain sebagai tipe field. Kamu bahkan bisa langsung mem-parsing JSON menjadi objek Python dengan model_validate_json().

python
from pydantic import BaseModel

class Address(BaseModel):
    street: str
    city: str
    zip_code: str

class Customer(BaseModel):
    name: str
    address: Address
    orders: list[dict] = []

payload = '''
{
  "name": "Siti Aminah",
  "address": {"street": "Jl. Merdeka No. 10", "city": "Bandung", "zip_code": "40111"},
  "orders": [{"id": 1, "total": 250000}]
}
'''

customer = Customer.model_validate_json(payload)
print(customer.address.city)   # Bandung
print(customer.model_dump_json(indent=2))

model_dump() dan model_dump_json() adalah cara standar untuk mengubah model kembali menjadi dict atau string JSON — berguna saat kamu mengirim data ke database atau API. Jika kamu bekerja dengan database relasional, pola ini mirip dengan cara SQLAlchemy ORM memetakan objek Python ke tabel database.

Langkah 7 — Mengelola Konfigurasi dengan pydantic-settings

Pydantic juga punya modul khusus untuk membaca konfigurasi aplikasi dari environment variable atau file .env. Ini sangat berguna untuk menyimpan kredensial database, API key, dan pengaturan deployment tanpa menuliskannya langsung di kode.

bash
pip install pydantic-settings
python
from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    app_name: str = "BengkelKode App"
    debug: bool = False
    database_url: str

    model_config = SettingsConfigDict(env_file=".env")

settings = Settings()  # otomatis membaca .env dan environment variable
print(settings.database_url)

Dengan pendekatan ini, nilai DATABASE_URL yang tersimpan di file .env atau environment variable sistem akan otomatis dimuat dan divalidasi. Ini menghindari praktik buruk hard-coding kredensial langsung di source code.

Langkah 8 — Integrasi dengan FastAPI

Salah satu alasan terbesar untuk mempelajari Pydantic adalah integrasinya yang mulus dengan FastAPI. Di FastAPI, model Pydantic langsung dipakai untuk mendefinisikan request body dan response, sehingga validasi serta dokumentasi API dibuat otomatis.

python
from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float = Field(gt=0)
    quantity: int = Field(ge=1, default=1)

@app.post("/items/")
def create_item(item: Item):
    # item sudah tervalidasi di sini
    return {"message": f"{item.name} diterima", "total": item.price * item.quantity}

Begitu request masuk dengan data yang tidak valid (misalnya price bernilai negatif), FastAPI otomatis mengembalikan respon 422 lengkap dengan detail error dari Pydantic — tanpa kamu menulis satu baris pun logika validasi tambahan.

Kesimpulan

Pydantic adalah salah satu library yang paling berdampak dalam ekosistem Python modern. Dengan menambahkan type hints dan model BaseModel, kamu mendapatkan validasi data otomatis, pesan error yang jelas, serialisasi JSON, dan integrasi yang rapi dengan framework seperti FastAPI. Mulailah dari model sederhana, lalu bertahap gunakan Field, validator kustom, dan pydantic-settings. Selamat mencoba, dan jangan ragu untuk bereksperimen dengan contoh-contoh di atas!

Apakah Pydantic cocok untuk proyek kecil?

Ya, bahkan untuk skrip kecil pun Pydantic membantu menangkap bug data lebih awal. Overhead-nya sangat ringan, dan di Pydantic v2 performanya sudah sangat cepat berkat engine Rust. Kamu tidak perlu memakainya di setiap variabel — cukup di titik-titik masuk data (boundary), seperti parsing API atau file konfigurasi.

Apa bedanya Pydantic dengan dataclass bawaan Python?

Dataclass hanya menyimpan data tanpa validasi runtime. Pydantic melakukan validasi tipe, konversi otomatis (coercion), serialisasi JSON, dan menghasilkan pesan error yang informatif. Jika kamu hanya butuh struktur data sederhana, dataclass sudah cukup; jika kamu butuh validasi input, pilih Pydantic.