Belajar MLOps - Python Production & Packaging
Episode 3 of 28

Belajar MLOps - Python Production & Packaging

Membawa kode ML keluar dari Jupyter menuju Python production: struktur project src-layout, type hints & dataclass untuk skema data, unit testing dengan pytest, dan packaging dengan pyproject.toml agar model code siap dipakai pipeline.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Setelah di episode 2 kita membangun kerangka siklus hidup ML, sekarang kita mulai praktik di fondasi paling bawah: kode Python itu sendiri. Jupyter Notebook adalah alat yang luar biasa untuk eksplorasi — tetapi ia bukan tempat yang tepat untuk model yang akan dideploy, diuji, dan dirawat selama bertahun-tahun.

Mengapa episode ini penting? Karena hampir semua masalah di dunia MLOps berakar dari kode ML yang tidak terstruktur: fungsi di notebook yang tidak bisa di-import, skema data yang tidak didefinisikan, dan tidak ada tes yang menangkap kesalahan sebelum pipeline jalan. Episode ini mengubah cara kalian menulis kode ML — bukan cara melatih model.

Dari Notebook ke Project Structure

Notebook menjadi script yang terstruktur lewat pola src-layout. Struktur project ML yang sehat kira-kira seperti ini:

text
churn-predictor/
├── src/
│   └── churn/
│       ├── __init__.py
│       ├── config.py
│       ├── data/
│       │   ├── load.py
│       │   └── validate.py
│       ├── features.py
│       ├── model.py
│       └── evaluate.py
├── tests/
│   ├── test_features.py
│   └── test_model.py
├── pyproject.toml
├── README.md
└── .gitignore

Kunci polanya: kode produktif tinggal di src/, tes di tests/, dan config terpisah di config.py. Setiap modul punya satu tanggung jawab: memuat data, membuat fitur, melatih model, mengevaluasi. Struktur ini bukan soal estetika — ia menentukan apakah fungsi bisa di-import, diuji, dan dikemas dengan benar.

Type Hints dan Dataclass: Menangkap Skema Lebih Dini

Model ML hidup dari data tabular dengan skema tertentu. Alih-alih mengoper dict atau list acak yang gampang salah, definisikan skema sebagai dataclass:

Definisi skema input
from dataclasses import dataclass
 
@dataclass
class CustomerFeatures:
    age: int
    monthly_charges: float
    tenure_months: int
    contract_type: str
 
    def risk_score(self) -> float:
        return self.monthly_charges / max(self.tenure_months, 1)

Dengan dataclass, IDE dan type checker menangkap kesalahan tipe sebelum runtime. Di production nanti (episode 9), skema ini akan menjadi validasi request API — memastikan model tidak menerima input yang bentuknya salah.

Untuk data yang lebih kompleks atau ingin validasi runtime yang ketat, kalian bisa memakai Pydantic. Ia umum dipakai bersama FastAPI di episode 9.

Testing dengan pytest

Notebook jarang diuji; kode production harus diuji. Untuk ML, unit test bukan hanya menguji logika, tapi juga skema data dan bentuk output model — kesalahan yang paling sering muncul di pipeline:

Unit test model & fitur
from churn.features import build_features
from churn.model import predict_proba
import numpy as np
 
def test_features_shape():
    df = build_features(sample_rows=10)
    assert df.shape[0] == 10
    assert "tenure_ratio" in df.columns
 
def test_predict_proba_output():
    probs = predict_proba(np.zeros((1, 8)))
    assert probs.shape == (1, 1)
    assert 0.0 <= probs[0][0] <= 1.0

Tes-tes ini terlihat sepele, tetapi ia menjaga pipeline dari kegagalan kelas "kolom hilang setelah refactor" dan "output berubah dimensi". Jalankan dengan:

Jalankan pytest
pip install pytest
pytest -q

Di episode 8, tes-tes ini akan dijalankan otomatis di CI — dan menjadi gate yang menahan pipeline jika model tidak sehat.

Warning

Jangan menaruh data besar atau download dataset di dalam unit test — test akan lambat, flaky, dan menggantung CI. Mock data kecil (10-50 baris) sudah cukup untuk menguji logika dan skema. Uji besar dilakukan di episode 7 (pipeline training).

Packaging dengan pyproject.toml

Agar kode bisa dipakai oleh pipeline lain (mlflow run, CI, serving), kemas sebagai package Python. Format modernnya adalah pyproject.toml:

pyproject.toml
[project]
name = "churn-predictor"
version = "0.1.0"
description = "Churn prediction model"
requires-python = ">=3.12"
dependencies = [
    "numpy>=2.0",
    "pandas>=2.2",
    "scikit-learn>=1.6",
    "mlflow>=2.20",
]
 
[project.optional-dependencies]
dev = ["pytest>=8.0", "ruff>=0.9"]
 
[build-system]
requires = ["setuptools>=75"]
build-backend = "setuptools.build_meta"
 
[tool.setuptools.packages.find]
where = ["src"]

Dengan file ini, siapa pun bisa memasang package dan semua dependency-nya secara reproducible:

Install package dalam mode editable
pip install -e ".[dev]"
python -c "from churn.features import build_features; print('OK')"

Kalian bisa mengganti pip dengan uv untuk kecepatan jauh lebih tinggi, atau Poetry jika ingin lockfile dan publishing terintegrasi. Yang penting konsisten — episode 4 akan memperketat pinning dependency.

Common Pitfalls

  • Semua logika di notebook → sulit di-import, diuji, dan di-version; refactor bertahap, bukan sekaligus.
  • Skema tidak didefinisikan → kolom hilang baru ketahuan saat pipeline produksi crash.
  • Tes menyentuh jaringan/data besar → lambat dan flaky; kecilkan dan mock.
  • Import antar folder dengan hack sys.path → gunakan struktur src + package yang benar.
  • Mengabaikan lint & type check → setup ruff dan pyright/mypy sejak awal.

Penutup

Pada episode 3 ini, kalian telah membawa kode ML keluar dari Jupyter menuju Python production.

Inti yang harus dibawa pulang:

  • Gunakan src-layout: kode di src/, tes di tests/, config terpisah.
  • Definisikan skema data dengan dataclass/Pydantic agar kesalahan ketangkap lebih dini.
  • Tulis unit test untuk skema data dan bentuk output model.
  • Kemas dengan pyproject.toml supaya pipeline dan CI bisa memasangnya dengan mudah.

Di episode 4 selanjutnya kita akan membahas reproducibility & environment — dependency pinning, lockfile, dan container Docker untuk ML — agar hasil training kalian bisa direproduksi di mesin mana pun, dan "di laptop saya jalan" tidak lagi jadi alasan. Sampai jumpa di episode 4!