Aturan validasi pada chapter sebelumnya mengasumsikan bentuk tabel sudah benar: total berupa angka, order_id ada di tempatnya. Asumsi tersebut runtuh begitu sumber mengirim total sebagai teks atau menambah kolom tak terduga — fungsi rentang malah memunculkan TypeError sebelum sempat melapor. Schema validation memeriksa bentuk tabel terlebih dahulu: kolom apa saja yang hadir, tipe data tiap kolom, dan kolom mana yang boleh kosong. Chapter Schema dan Data Types sudah membahas schema sebagai kontrak dan pemetaan tipe antar format, sedangkan Schema Evolution membahas cara mengubahnya dengan aman; chapter ini fokus pada penegakannya — bagaimana schema berubah dari dokumen pasif menjadi gerbang validasi aktif yang dijalankan sebelum aturan nilai.
Apa yang Diperiksa Schema Validation #
Schema validation menjawab pertanyaan tentang bentuk, bukan isi. Empat pemeriksaan berikut dijalankan berurutan sebelum satu pun aturan nilai dianggap sah:
| Pemeriksaan | Contoh Sesuai | Contoh Melanggar |
|---|---|---|
| Kolom wajib ada | order_id hadir | kolom total hilang |
| Tipe data sesuai | total bertipe int | total berupa teks "mahal" |
| Nullability terjaga | pelanggan terisi semua | order_id kosong di 3 baris |
| Tidak ada kolom asing | hanya 4 kolom kontrak | muncul kolom catatan |
Perbedaan fokus ini penting saat menentukan respons: pelanggaran schema biasanya berarti produsen data berubah — versi aplikasi baru, format ekspor berbeda — sehingga keputusannya cenderung menghentikan pipeline dan menghubungi pemilik sumber, bukan mengarantina baris demi baris. Urutan pemeriksaan juga mencerminkan urutan eksekusi yang benar: tipe diperiksa sebelum batasan nilai, karena aturan rentang atas kolom teks tidak bermakna.
Dua istilah yang akan sering muncul di dunia schema validation: coerce berarti mengonversi nilai agar sesuai tipe yang dijanjikan, sedangkan reject berarti menolak data yang melanggar. Bab ini memilih jalur reject; konversi otomatis kita tangani sebagai transformasi eksplisit di pipeline, bukan sebagai kemurahan hati validator.
Validasi Schema dengan pandera #
Kita deklarasikan schema tabel pesanan memakai pandera, lalu ujikan pada dua DataFrame: satu sehat dan satu yang melanggar lima klausul sekaligus. Penjelasan mendalam tentang pandera ada pada chapter Pandera; di sini kita cukup merasakan pola kerjanya:
"""Validasi schema dengan pandera: kontrak bentuk tabel dan laporan SchemaErrors."""
import pandas as pd
import pandera.pandas as pa
from pandera.pandas import Check, Column, DataFrameSchema
schema_pesanan = DataFrameSchema(
{
"order_id": Column(str, Check.str_matches(r"^ORD-\d{3}$"), unique=True),
"pelanggan": Column(str, Check.str_length(min_value=1)),
"total": Column(int, [Check.ge(0), Check.le(50_000_000)]),
"status": Column(str, Check.isin(["baru", "dibayar", "kirim", "selesai"])),
},
strict=True,
)
pesanan_baik = pd.DataFrame(
{
"order_id": ["ORD-201", "ORD-202", "ORD-203"],
"pelanggan": ["Budi", "Siti", "Dewi"],
"total": [150000, 320000, 99000],
"status": ["baru", "dibayar", "selesai"],
}
)
hasil = schema_pesanan.validate(pesanan_baik)
print(f"validasi pesanan_baik: LULUS ({len(hasil)} baris)")
pesanan_buruk = pd.DataFrame(
{
"order_id": ["ORD-301", "ORD-301", "ORD-30X"],
"pelanggan": ["Budi", "Siti", "Dewi"],
"total": [150000, 320000000, 99000],
"status": ["baru", "refund", "selesai"],
"catatan": ["voucher", "voucher", "-"],
}
)
try:
schema_pesanan.validate(pesanan_buruk, lazy=True)
except pa.errors.SchemaErrors as galat:
print("validasi pesanan_buruk: GAGAL")
kasus = galat.failure_cases[["schema_context", "column", "check", "failure_case", "index"]]
print(kasus.to_string(index=False))Output yang Dihasilkan:
validasi pesanan_baik: LULUS (3 baris)
validasi pesanan_buruk: GAGAL
schema_context column check failure_case index
DataFrameSchema None column_in_schema catatan None
Column order_id field_uniqueness ORD-301 0
Column order_id field_uniqueness ORD-301 1
Column order_id str_matches('^ORD-\d{3}$') ORD-30X 2
Column total less_than_or_equal_to(50000000) 320000000 1
Column status isin(['baru', 'dibayar', 'kirim', 'selesai']) refund 1Mari kita telaah baris-baris kode di atas:
- Baris 6-14 (
DataFrameSchema): schema dideklarasikan sebagai struktur data — nama kolom, tipe (str,int), dan batasan (Check). Argumenunique=Truemenuntutorder_idtidak ganda, sedangkanstrict=Truemembuat kolom di luar schema ditolak. - Baris 25-26 (validasi data sehat):
validate()mengembalikan DataFrame yang sama bila semua klausul terpenuhi — tidak ada exception berarti lolos. - Baris 39 (
lazy=True): tanpa argumen ini, pandera berhenti di pelanggaran pertama;lazy=Truemengumpulkan seluruh kegagalan dulu, seperti pola “kumpulkan semua, jangan berhenti di yang pertama” pada chapter sebelumnya. - Baris 40-43 (
SchemaErrors): exception membawa atributfailure_casesberupa tabel kegagalan; kita memilih lima kolomnya untuk dicetak rapi.
Membaca Laporan Kegagalan #
Setiap baris laporan menjawab satu pertanyaan: apa yang salah, di kolom mana, pada nilai apa, di indeks mana. Baris pertama (column_in_schema, kolom catatan) adalah pelanggaran bentuk murni — kolom asing yang ditolak strict=True dan tidak memiliki indeks karena bukan masalah baris. Empat baris berikutnya adalah pelanggaran per nilai: ORD-301 ganda di indeks 0 dan 1, format ORD-30X salah, total 320 juta melebihi batas, dan status refund tidak ada dalam daftar sah. Dari satu laporan, tim data tahu persis harus menelepon siapa: pelanggaran bentuk berarti produsen berubah, pelanggaran nilai berarti data kotor.
Atribut failure_cases sebenarnya menyimpan kolom tambahan seperti check_number untuk aturan multi-batas; lima kolom yang kita cetak dipilih agar laporan mudah dibaca manusia. Di pipeline produksi, tabel kegagalan ini biasanya diekspor ke penyimpanan audit agar pelanggaran schema dapat dianalisis trennya dari minggu ke minggu.
Di Titik Mana Schema Diverifikasi #
Praktik yang sehat memverifikasi schema di tiga gerbang, masing-masing memakai schema dengan perspektif berbeda:
| Gerbang | Schema yang Dipakai | Contoh Kegagalan yang Ditangkap |
|---|---|---|
| Saat ingestion | schema sumber (bentuk data mentah) | produsen mengganti nama kolom |
| Setelah transformasi | schema antara (bentuk tabel kerja) | join menggandakan baris dan merusak keunikan kunci |
| Sebelum publikasi | schema keluaran (janji ke konsumen) | kolom wajib terbuang oleh seleksi |
Ketiga gerbang memakai mekanisme penegakan yang sama — deklarasikan schema, jalankan validate(), tangani SchemaErrors — yang berbeda hanya isi schema dan pihak yang menerima laporannya. Di banyak tim, gerbang terakhir dijalankan sebagai langkah CI sebelum publikasi, sehingga perubahan yang melanggar schema keluaran tertolak otomatis sebelum menyentuh konsumen. Pandera terdokumentasi lengkap di pandera.readthedocs.io
.
Schema yang ditegakkan menjelaskan bentuk data, tetapi kesepakatan antara produsen dan konsumen jarang berhenti di bentuk: ada aturan kualitas, janji freshness, dan nama pemilik yang bertanggung jawab. Kesepakatan lengkap tersebut memiliki nama tersendiri, dan bermula dari pertanyaan sederhana: siapa sebenarnya yang berhak mengubah tabel ini? Kita bahas pada chapter berikutnya: Data Contracts.