Bab 42 — Antarmuka: Perintah, Jendela, dan Dasbor
Seri I5 — Aplikasi Pertama: Web, Peta, dan Dasbor
Semua skrip bab ini ada di folder kode/bab42/. Di versi web, Anda juga bisa mengunduh semuanya sekaligus (zip).
Skrip yang Anda tulis sejauh ini dijalankan dengan mengetik python nama.py. Itu cukup untuk Anda, tetapi orang lain butuh pintu masuk yang lebih ramah. Bayangkan warung: ada yang memesan lewat loket (cepat, tinggal sebut pesanan), ada yang duduk di meja dengan daftar menu bergambar, dan ada yang melihat papan pengumuman penjualan hari ini. Satu dapur yang sama, tiga cara berinteraksi. Itulah tiga jenis antarmuka (interface) yang kita bahas.

Di bab ini kita memakai data unit usaha tiruan (15 unit di lima sektor, semua angka fiktif) dari berkas data/unit_usaha.csv, dibaca oleh modul bantu data_unit.py.
CLI dengan argparse
CLI (Command-Line Interface) adalah program yang dipakai dengan mengetik perintah di terminal, seperti git commit -m "pesan". argparse adalah modul bawaan Python untuk membaca ketikan itu.
01_argparse_dasar.py
# Bab 42 - argparse: membaca argumen dari baris perintah
import argparse
parser = argparse.ArgumentParser(prog="hitung_pakan", description="Menghitung pakan harian kolam.")
parser.add_argument("biomassa_kg", type=float, help="biomassa ikan (kg)") # posisi, wajib
parser.add_argument("--persen", type=float, default=3.0, help="persen pakan (bawaan 3.0)") # opsi
parser.add_argument("--rinci", action="store_true", help="tampilkan rincian") # sakelar
# Biasanya argparse membaca ketikan terminal. Di sini kita beri daftar sendiri agar bisa dicoba.
for ketikan in (["432.4"], ["432.4", "--persen", "2.5", "--rinci"]):
args = parser.parse_args(ketikan)
pakan = args.biomassa_kg * args.persen / 100
print(f"ketikan {ketikan} -> {args}")
print(f" pakan: {pakan:.2f} kg" + (" (rincian: biomassa x persen / 100)" if args.rinci else ""))
print()
parser.print_help()ketikan ['432.4'] -> Namespace(biomassa_kg=432.4, persen=3.0, rinci=False)
pakan: 12.97 kg
ketikan ['432.4', '--persen', '2.5', '--rinci'] -> Namespace(biomassa_kg=432.4, persen=2.5, rinci=True)
pakan: 10.81 kg (rincian: biomassa x persen / 100)
usage: hitung_pakan [-h] [--persen PERSEN] [--rinci] biomassa_kg
Menghitung pakan harian kolam.
positional arguments:
biomassa_kg biomassa ikan (kg)
options:
-h, --help show this help message and exit
--persen PERSEN persen pakan (bawaan 3.0)
--rinci tampilkan rincian
Tiga jenis argumen muncul di sini:
- Posisi (
biomassa_kg): wajib, dikenali dari urutannya. - Opsi (
--persen): diawali tanda hubung ganda, boleh dilewati, punya nilai bawaan. - Sakelar (
--rinci,action="store_true"): bernilaiTruebila ditulis,Falsebila tidak.
type=float membuat argparse mengubah teks "432.4" menjadi angka. Hasil parse_args() adalah objek Namespace yang atributnya Anda baca (args.persen). Biasanya parse_args() membaca ketikan terminal; di skrip ini kita memberinya list supaya bisa dicoba tanpa terminal (cara yang sama dipakai untuk menguji). Hitungannya: 432,4 × 3 / 100 = 12,97 kg dan 432,4 × 2,5 / 100 = 10,81 kg. Bagian bawah keluaran adalah bantuan otomatis (--help) yang dibuat argparse dari deklarasi tadi, gratis.
Bila pengguna salah ketik
Alat yang baik menolak masukan salah dengan jelas. Kita uji alat unit sungguhan (modul bantu cli_argparse.py, ditampilkan di bawah) lewat subprocess dari Bab 41:
cli_argparse.py
# Bab 42 - Alat baris perintah "unit" versi argparse (modul bantu; dijalankan lewat skrip 02 dan 03)
import argparse
import sys
from data_unit import baca_unit, ringkas_per_sektor
SEKTOR = ["hutan", "kebun", "sawah", "kolam", "kandang"]
def buat_parser():
parser = argparse.ArgumentParser(prog="unit", description="Melihat data unit usaha (data fiktif).")
sub = parser.add_subparsers(dest="perintah", required=True)
daftar = sub.add_parser("daftar", help="menampilkan unit")
daftar.add_argument("--sektor", choices=SEKTOR, help="hanya satu sektor")
daftar.add_argument("--maks", type=int, default=0, help="batas jumlah baris (0 = semua)")
sub.add_parser("ringkas", help="ringkasan per sektor")
return parser
def main(argv=None):
args = buat_parser().parse_args(argv)
unit = baca_unit()
if args.perintah == "daftar":
if args.sektor:
unit = [u for u in unit if u["sektor"] == args.sektor]
if args.maks:
unit = unit[: args.maks]
for u in unit:
print(f'{u["id"]:3} {u["sektor"]:8} {u["unit"]:10} {u["nilai"]:8.1f} {u["satuan"]}')
else:
for sektor, r in ringkas_per_sektor(unit).items():
print(f'{sektor:8} {r["jumlah"]} unit, rata-rata {r["rata"]:.1f} {r["satuan"]}')
return 0
if __name__ == "__main__":
sys.exit(main())02_argparse_galat.py
# Bab 42 - argparse: apa yang terjadi bila pengguna salah mengetik
import subprocess
import sys
for argumen in (["daftar", "--sektor", "empang"], ["daftar", "--maks", "dua"], []):
hasil = subprocess.run(
[sys.executable, "cli_argparse.py", *argumen],
capture_output=True, text=True, encoding="utf-8",
)
print("argumen:", argumen)
print("kode keluar:", hasil.returncode)
print(hasil.stderr.strip().splitlines()[-1])
print()argumen: ['daftar', '--sektor', 'empang']
kode keluar: 2
unit daftar: error: argument --sektor: invalid choice: 'empang' (choose from 'hutan', 'kebun', 'sawah', 'kolam', 'kandang')
argumen: ['daftar', '--maks', 'dua']
kode keluar: 2
unit daftar: error: argument --maks: invalid int value: 'dua'
argumen: []
kode keluar: 2
unit: error: the following arguments are required: perintah
Dokumentasi resmi menyebut perilaku ini: saat masukan tidak sah, parse_args() mencetak pesan penggunaan ke stderr lalu keluar dengan kode keluar 2. Pilihan choices=SEKTOR membuat sektor empang ditolak, type=int menolak dua, dan subperintah yang required=True menolak ketikan kosong. Pesan galat argparse ditulis dalam bahasa Inggris oleh Python; itu bukan kesalahan skrip kita.
Subperintah
Alat besar punya beberapa perintah (git commit, git push). Di argparse, itu dibuat dengan add_subparsers(): daftar dan ringkas pada alat unit kita. Uji lewat program anak, persis seperti dipakai orang:
03_argparse_subperintah.py
# Bab 42 - argparse: alat "unit" dengan subperintah, diuji lewat subprocess
import subprocess
import sys
def unit(*argumen):
hasil = subprocess.run([sys.executable, "cli_argparse.py", *argumen],
capture_output=True, text=True, encoding="utf-8")
return hasil.returncode, hasil.stdout
for argumen in (["ringkas"], ["daftar", "--sektor", "kolam"], ["daftar", "--maks", "2"]):
kode, keluaran = unit(*argumen)
print(f"$ unit {' '.join(argumen)} (kode keluar {kode})")
print(keluaran)$ unit ringkas (kode keluar 0)
hutan 3 unit, rata-rata 35.2 cm
kebun 4 unit, rata-rata 257.5 kg
sawah 2 unit, rata-rata 1115.2 kg
kolam 3 unit, rata-rata 369.3 kg
kandang 3 unit, rata-rata 383.8 kg
$ unit daftar --sektor kolam (kode keluar 0)
L1 kolam Kolam A 432.4 kg
L2 kolam Kolam B 380.0 kg
L3 kolam Kolam C 295.5 kg
$ unit daftar --maks 2 (kode keluar 0)
H1 hutan Plot A 32.5 cm
H2 hutan Plot B 45.2 cm
CLI dengan Typer
argparse kuat, tetapi banyak tulisan. Typer (pustaka pihak ketiga, dibangun di atas Click) membalik caranya: Anda menulis fungsi biasa dengan type hint (Bab 33), dan Typer membuat perintahnya. Parameter tanpa nilai bawaan menjadi argumen wajib; parameter dengan nilai bawaan menjadi opsi; bool menjadi sakelar.
04_typer_dasar.py
# Bab 42 - Typer: fungsi biasa menjadi perintah, lengkap dengan pengujian tanpa terminal
import typer
from typer.testing import CliRunner
app = typer.Typer(add_completion=False)
@app.command()
def pakan(biomassa_kg: float, persen: float = 3.0, rinci: bool = False):
"""Menghitung pakan harian kolam."""
hasil = biomassa_kg * persen / 100
typer.echo(f"pakan: {hasil:.2f} kg")
if rinci:
typer.echo("rincian: biomassa x persen / 100")
runner = CliRunner() # penguji bawaan Typer: memanggil aplikasi tanpa membuka terminal
for ketikan in (["432.4"], ["432.4", "--persen", "2.5", "--rinci"], ["banyak"]):
hasil = runner.invoke(app, ketikan)
print(ketikan, "-> kode keluar", hasil.exit_code)
print(hasil.output.strip().splitlines()[0] if hasil.exit_code == 0 else "(galat penggunaan)")
hasil = runner.invoke(app, ["--help"])
print()
print(hasil.output)['432.4'] -> kode keluar 0
pakan: 12.97 kg
['432.4', '--persen', '2.5', '--rinci'] -> kode keluar 0
pakan: 10.81 kg
['banyak'] -> kode keluar 2
(galat penggunaan)
Usage: pakan [OPTIONS] {biomassa_kg}
Menghitung pakan harian kolam.
┌─ Arguments ─────────────────────────────────────────────────────────────────┐
│ * biomassa_kg <float> [required] │
└─────────────────────────────────────────────────────────────────────────────┘
┌─ Options ───────────────────────────────────────────────────────────────────┐
│ --persen <float> [default: 3.0] │
│ --rinci --no-rinci [default: no-rinci] │
│ --help Show this message and exit. │
└─────────────────────────────────────────────────────────────────────────────┘
- Ketiga ketikan menghasilkan: sukses (12,97), sukses dengan opsi (10,81), dan galat penggunaan (kode keluar 2) untuk
banyak, karena bukan angka. CliRunneradalah penguji bawaan Typer: ia memanggil aplikasi di dalam proses yang sama dan menyimpanexit_codesertaoutput. Tidak ada terminal yang perlu dibuka, jadi cepat dan cocok untuk tes otomatis.- Bantuan otomatis memakai tampilan kotak (dibuat pustaka
rich). Perhatikan--rincidan--no-rinci: Typer menambah pasangan sakelar sendiri.
Berikut alat unit versi Typer, dengan tiga perintah (daftar, ringkas, laporan):
cli_typer.py
# Bab 42 - Alat baris perintah "unit" versi Typer (modul bantu; dipakai skrip 04 sampai 09)
from enum import Enum
from pathlib import Path
from typing import Annotated, Optional
import typer
from data_unit import baca_unit, ringkas_per_sektor
app = typer.Typer(help="Melihat data unit usaha (data fiktif).", no_args_is_help=True, add_completion=False)
class Sektor(str, Enum):
hutan = "hutan"
kebun = "kebun"
sawah = "sawah"
kolam = "kolam"
kandang = "kandang"
@app.command()
def daftar(
sektor: Annotated[Optional[Sektor], typer.Option(help="hanya satu sektor")] = None,
maks: Annotated[int, typer.Option(min=0, help="batas jumlah baris (0 = semua)")] = 0,
):
"""Menampilkan unit."""
unit = baca_unit()
if sektor:
unit = [u for u in unit if u["sektor"] == sektor.value]
if maks:
unit = unit[:maks]
for u in unit:
typer.echo(f'{u["id"]:3} {u["sektor"]:8} {u["unit"]:10} {u["nilai"]:8.1f} {u["satuan"]}')
@app.command()
def ringkas():
"""Ringkasan per sektor."""
for sektor, r in ringkas_per_sektor(baca_unit()).items():
typer.echo(f'{sektor:8} {r["jumlah"]} unit, rata-rata {r["rata"]:.1f} {r["satuan"]}')
@app.command()
def laporan(keluar: Annotated[Path, typer.Argument(help="berkas Markdown tujuan")]):
"""Menulis laporan ringkas ke berkas."""
baris = ["# Ringkasan unit usaha (data fiktif)", ""]
for sektor, r in ringkas_per_sektor(baca_unit()).items():
baris.append(f'- {sektor}: {r["jumlah"]} unit, rata-rata {r["rata"]:.1f} {r["satuan"]}')
keluar.parent.mkdir(parents=True, exist_ok=True)
keluar.write_text("\n".join(baris) + "\n", encoding="utf-8")
typer.echo(f"laporan ditulis: {keluar.name}")
if __name__ == "__main__":
app()Enum (daftar pilihan tetap) menggantikan choices. Annotated[..., typer.Option(...)] menambah keterangan dan batas (min=0). Fungsi di atas bisa dijalankan sebagai program:
05_typer_subperintah.py
# Bab 42 - Typer: alat "unit" lengkap dijalankan sebagai program sungguhan lewat subprocess
import subprocess
import sys
def unit(*argumen):
hasil = subprocess.run([sys.executable, "cli_typer.py", *argumen],
capture_output=True, text=True, encoding="utf-8")
return hasil.returncode, hasil.stdout + hasil.stderr
for argumen in (["--help"], ["daftar", "--sektor", "kandang"], ["daftar", "--sektor", "empang"]):
kode, teks = unit(*argumen)
print(f"$ unit {' '.join(argumen)} (kode keluar {kode})")
print(teks)$ unit --help (kode keluar 0)
Usage: cli_typer.py [OPTIONS] COMMAND [ARGS]...
Melihat data unit usaha (data fiktif).
┌─ Options ───────────────────────────────────────────────────────────────────┐
│ --help Show this message and exit. │
└─────────────────────────────────────────────────────────────────────────────┘
┌─ Commands ──────────────────────────────────────────────────────────────────┐
│ daftar Menampilkan unit. │
│ ringkas Ringkasan per sektor. │
│ laporan Menulis laporan ringkas ke berkas. │
└─────────────────────────────────────────────────────────────────────────────┘
$ unit daftar --sektor kandang (kode keluar 0)
D1 kandang Kandang 1 350.0 kg
D2 kandang Kandang 2 412.5 kg
D3 kandang Kandang 3 389.0 kg
$ unit daftar --sektor empang (kode keluar 2)
Usage: cli_typer.py daftar [OPTIONS]
Try 'cli_typer.py daftar --help' for help.
┌─ Error ─────────────────────────────────────────────────────────────────────┐
│ Invalid value for '--sektor': 'empang' is not one of 'hutan', 'kebun', │
│ 'sawah', 'kolam', 'kandang'. │
└─────────────────────────────────────────────────────────────────────────────┘
Sektor empang ditolak dengan kode keluar 2 dan kotak galat yang menyebut pilihan yang sah.
Pilih argparse bila Anda ingin nol ketergantungan (bawaan Python). Pilih Typer bila alat Anda punya banyak perintah dan Anda suka kode ringkas berbasis type hint. Keduanya sama-sama menghasilkan bantuan otomatis dan kode keluar yang jelas.
Menguji CLI
CLI yang tidak diuji akan rusak diam-diam. Dengan CliRunner, tiap perilaku jadi satu pemeriksaan:
06_uji_cli.py
# Bab 42 - Menguji CLI Typer dengan CliRunner (gaya tes pytest, ditulis sebagai skrip)
from pathlib import Path
from typer.testing import CliRunner
from cli_typer import app
runner = CliRunner()
lulus = 0
def periksa(kondisi, nama):
global lulus
print(("LULUS " if kondisi else "GAGAL ") + nama)
lulus += bool(kondisi)
hasil = runner.invoke(app, ["ringkas"])
periksa(hasil.exit_code == 0 and "kolam" in hasil.output, "ringkas menampilkan sektor kolam")
hasil = runner.invoke(app, ["daftar", "--sektor", "sawah"])
periksa(len(hasil.output.strip().splitlines()) == 2, "daftar --sektor sawah memberi 2 baris")
hasil = runner.invoke(app, ["daftar", "--maks", "4"])
periksa(len(hasil.output.strip().splitlines()) == 4, "daftar --maks 4 memberi 4 baris")
hasil = runner.invoke(app, ["daftar", "--sektor", "empang"])
periksa(hasil.exit_code == 2, "sektor tak dikenal ditolak dengan kode keluar 2")
tujuan = Path(__file__).parent / "_tmp" / "ringkasan.md"
hasil = runner.invoke(app, ["laporan", str(tujuan)])
periksa(hasil.exit_code == 0 and tujuan.read_text(encoding="utf-8").startswith("# Ringkasan"),
"laporan menulis berkas Markdown")
print(f"\n{lulus} dari 5 pemeriksaan lulus")LULUS ringkas menampilkan sektor kolam
LULUS daftar --sektor sawah memberi 2 baris
LULUS daftar --maks 4 memberi 4 baris
LULUS sektor tak dikenal ditolak dengan kode keluar 2
LULUS laporan menulis berkas Markdown
5 dari 5 pemeriksaan lulus
Ini gaya tes pytest (Bab 18) yang ditulis sebagai skrip agar keluarannya tampil. Perhatikan pengujian jalur gagal (sektor tak dikenal harus kode 2) sama pentingnya dengan jalur sukses. Ada dua cara menguji CLI di bab ini: CliRunner (cepat, dalam satu proses) dan subprocess (lambat sedikit, tetapi persis seperti dipakai orang di terminal).
GUI sederhana dengan tkinter
GUI (Graphical User Interface) adalah program berjendela: tombol, kotak pilihan, tabel. tkinter adalah pustaka GUI yang menyertai Python (membungkus Tk). Dokumentasi resmi mencatat bahwa tkinter adalah modul opsional: bila tidak ada di salinan Python Anda, cari petunjuk dari penyedia Python Anda. Di komputer penulis ia ada (versi Tk 9.0). ttk adalah kumpulan widget bertema yang dianjurkan untuk kode baru.
gui_unit.py
# Bab 42 - Jendela tkinter untuk melihat unit usaha (modul bantu)
import tkinter as tk
from tkinter import ttk
from data_unit import baca_unit
SEKTOR = ["semua", "hutan", "kebun", "sawah", "kolam", "kandang"]
def bangun_jendela(root):
"""Menyusun isi jendela dan mengembalikan widget yang perlu diuji."""
root.title("Unit usaha (data fiktif)")
pilihan = tk.StringVar(value="semua")
status = tk.StringVar()
atas = ttk.Frame(root, padding=8)
atas.pack(fill="x")
ttk.Label(atas, text="Sektor:").pack(side="left")
kotak = ttk.Combobox(atas, textvariable=pilihan, values=SEKTOR, state="readonly", width=10)
kotak.pack(side="left", padx=6)
tabel = ttk.Treeview(root, columns=("unit", "jenis", "nilai"), show="headings", height=8)
for kolom, judul in (("unit", "Unit"), ("jenis", "Jenis"), ("nilai", "Nilai")):
tabel.heading(kolom, text=judul)
tabel.pack(fill="both", expand=True, padx=8)
ttk.Label(root, textvariable=status, padding=8).pack(anchor="w")
def muat(*_):
tabel.delete(*tabel.get_children())
terpilih = [u for u in baca_unit() if pilihan.get() in ("semua", u["sektor"])]
for u in terpilih:
tabel.insert("", "end", values=(u["unit"], u["jenis"], f'{u["nilai"]} {u["satuan"]}'))
status.set(f"{len(terpilih)} unit ditampilkan")
kotak.bind("<<ComboboxSelected>>", muat)
muat()
return pilihan, tabel, status, muat
if __name__ == "__main__":
jendela = tk.Tk()
bangun_jendela(jendela)
jendela.mainloop() # menampilkan jendela dan menunggu penggunaJendela ini punya kotak pilihan sektor, tabel unit, dan baris status. Memilih sektor memanggil muat(), yang mengosongkan tabel dan mengisinya kembali. Perhatikan pola yang baik: seluruh isi jendela dibangun oleh fungsi bangun_jendela(root), sedangkan mainloop() ada hanya di bagian if __name__ == "__main__":.
Kode ini tidak dijalankan saat buku dibuat (bagian mainloop()) karena jendela yang tampil butuh layar dan pengguna, jadi keluarannya tidak ditampilkan. Anda menjalankannya sendiri dengan python gui_unit.py. Tetapi isinya tetap bisa diuji tanpa menampilkan apa-apa, karena strukturnya terpisah:
07_tkinter_uji.py
# Bab 42 - tkinter: menguji jendela TANPA menampilkannya (jendela disembunyikan)
import tkinter as tk
from gui_unit import bangun_jendela
root = tk.Tk()
root.withdraw() # sembunyikan: tidak ada jendela muncul di layar
pilihan, tabel, status, muat = bangun_jendela(root)
root.update()
print("judul jendela :", root.title())
print("awal :", status.get(), "| baris tabel:", len(tabel.get_children()))
for sektor in ("kolam", "hutan"):
pilihan.set(sektor) # seolah-olah pengguna memilih di kotak pilihan
muat()
baris = [tabel.item(i, "values")[0] for i in tabel.get_children()]
print(f"{sektor:6} :", status.get(), "->", baris)
root.destroy()judul jendela : Unit usaha (data fiktif)
awal : 15 unit ditampilkan | baris tabel: 15
kolam : 3 unit ditampilkan -> ['Kolam A', 'Kolam B', 'Kolam C']
hutan : 3 unit ditampilkan -> ['Plot A', 'Plot B', 'Plot C']
root.withdraw() menyembunyikan jendela sehingga tidak ada yang muncul di layar. Lalu kita meniru pengguna: pilihan.set("kolam") seperti memilih di kotak, muat() seperti peristiwa pilihan. Hasilnya cocok: 15 unit di awal, 3 unit kolam (Kolam A, B, C), dan 3 unit hutan (Plot A, B, C).
Yang diuji di sini adalah logika jendela (isi tabel, teks status). Tampilannya (posisi tombol, warna, ukuran) tidak bisa diperiksa tanpa melihat jendela itu sendiri. Pemeriksaan tampilan tetap tugas Anda, dengan mata, di komputer Anda.
Dasbor data pengantar
Dasbor (dashboard) adalah satu halaman yang merangkum angka penting dengan tabel dan grafik, seperti panel instrumen mobil. Dasbor paling sederhana tidak butuh server: satu berkas HTML mandiri yang bisa dibuka dengan klik ganda.
08_dasbor_statis.py
# Bab 42 - Dasbor pengantar: satu halaman HTML mandiri berisi tabel dan grafik
import base64
import io
from pathlib import Path
import matplotlib.pyplot as plt
import pandas as pd
from data_unit import BERKAS
df = pd.read_csv(BERKAS)
ringkas = df.groupby("sektor", sort=False)["nilai"].agg(["count", "mean"]).round(1)
print(ringkas)
fig, ax = plt.subplots(figsize=(6, 3))
ringkas["count"].plot.bar(ax=ax, color="#4f8a3b")
ax.set_ylabel("jumlah unit")
ax.set_xlabel("")
fig.tight_layout()
buf = io.BytesIO()
fig.savefig(buf, format="png", dpi=100)
plt.close(fig)
gambar = base64.b64encode(buf.getvalue()).decode("ascii")
halaman = f"""<!doctype html>
<html lang="id"><head><meta charset="utf-8"><title>Dasbor unit usaha</title></head>
<body style="font-family: sans-serif; max-width: 640px; margin: 2em auto">
<h1>Dasbor unit usaha (data fiktif)</h1>
<p>Total unit: <b>{len(df)}</b> di <b>{df['sektor'].nunique()}</b> sektor.</p>
<img alt="jumlah unit per sektor" src="data:image/png;base64,{gambar}">
{ringkas.to_html(classes="ringkas")}
</body></html>
"""
tujuan = Path(__file__).parent / "_tmp" / "dasbor.html"
tujuan.parent.mkdir(exist_ok=True)
tujuan.write_text(halaman, encoding="utf-8")
print("\nberkas dasbor.html dibuat:", tujuan.stat().st_size > 5000, "(bisa dibuka dengan klik ganda)")
print("memuat grafik tertanam :", "data:image/png;base64," in halaman) count mean
sektor
hutan 3 35.2
kebun 4 257.5
sawah 2 1115.2
kolam 3 369.3
kandang 3 383.8
berkas dasbor.html dibuat: True (bisa dibuka dengan klik ganda)
memuat grafik tertanam : True
Grafik dibuat dengan matplotlib, diubah menjadi teks base64, lalu ditanam langsung di HTML (data:image/png;base64,...), sehingga berkas tidak butuh gambar terpisah. Tabel ringkasan datang dari to_html() milik pandas. Kita memeriksa bahwa berkasnya terbentuk dan memuat grafik; kita tidak bisa melihat tampilannya dari skrip ini, jadi buka _tmp/dasbor.html sendiri bila ingin melihatnya.
Kelemahan dasbor statis: angkanya beku saat berkas dibuat dan tidak ada interaksi. Dasbor yang hidup, bisa dipilih-pilih, dan menampilkan peta dibahas di Bab 43.
Proyek Bersambung I5: CLI untuk unit usaha
Di Bab 41 laporan mingguan diambil lewat scraping. Sekarang kita beri alat unit kepada rekan kerja: mereka cukup mengetik perintah. Skrip otomasi harian di bawah memanggil CLI itu seperti seseorang di terminal, memeriksa kode keluar tiap langkah, dan berhenti bila ada yang gagal.
09_proyek_cli.py
# Bab 42 - Proyek bersambung I5: CLI "unit" dipanggil oleh skrip otomasi harian
import subprocess
import sys
from pathlib import Path
tujuan = Path(__file__).parent / "_tmp" / "harian" / "ringkasan.md"
def unit(*argumen):
hasil = subprocess.run([sys.executable, "cli_typer.py", *argumen],
capture_output=True, text=True, encoding="utf-8")
return hasil.returncode, hasil.stdout.strip()
langkah = [
("ringkas", ["ringkas"]),
("dua kolam pertama", ["daftar", "--sektor", "kolam", "--maks", "2"]),
("tulis laporan", ["laporan", str(tujuan)]),
]
for nama, argumen in langkah:
kode, keluaran = unit(*argumen)
print(f"[{nama}] kode keluar {kode}")
print(keluaran)
if kode != 0: # otomasi yang baik berhenti bila satu langkah gagal
raise SystemExit(f"langkah '{nama}' gagal")
print("\nisi berkas laporan:")
print(tujuan.read_text(encoding="utf-8"))[ringkas] kode keluar 0
hutan 3 unit, rata-rata 35.2 cm
kebun 4 unit, rata-rata 257.5 kg
sawah 2 unit, rata-rata 1115.2 kg
kolam 3 unit, rata-rata 369.3 kg
kandang 3 unit, rata-rata 383.8 kg
[dua kolam pertama] kode keluar 0
L1 kolam Kolam A 432.4 kg
L2 kolam Kolam B 380.0 kg
[tulis laporan] kode keluar 0
laporan ditulis: ringkasan.md
isi berkas laporan:
# Ringkasan unit usaha (data fiktif)
- hutan: 3 unit, rata-rata 35.2 cm
- kebun: 4 unit, rata-rata 257.5 kg
- sawah: 2 unit, rata-rata 1115.2 kg
- kolam: 3 unit, rata-rata 369.3 kg
- kandang: 3 unit, rata-rata 383.8 kg
Tiga langkah berjalan, semuanya berkode keluar 0. Berkas laporan berisi rata-rata tiap sektor. Contohnya kebun: 4 unit dengan rata-rata (245,5 + 310,0 + 198,5 + 276,0) / 4 = 1030,0 / 4 = 257,5 kg. Kebiasaan raise SystemExit(...) saat langkah gagal membuat otomasi tidak melanjutkan pekerjaan di atas hasil yang rusak.
Latihan
- Tambahkan opsi
--tinggi(sakelar) pada01_argparse_dasar.py; bila ditulis, cetak juga “pakan per ekor” untuk 800 ekor ikan. - Pada
cli_typer.py, tambahkan perintahsatuyang menerimaid_unit(mis.D2) dan mencetak unit itu, atau “tidak ada” dengan kode keluar 1. Tambahkan dua pemeriksaan di06_uji_cli.py. - Ubah
gui_unit.pysupaya status juga menampilkan nilai tertinggi yang sedang tampil, dan perbarui07_tkinter_uji.pyuntuk memeriksanya. - Tantangan: buat CLI
unitmemiliki perintahdasboryang memanggil kode08_dasbor_statis.pydan mencetak lokasi berkasnya. Uji denganCliRunner.
Ringkasan Bab
- Antarmuka ada tiga: baris perintah (CLI), jendela (GUI), dan halaman web (dasbor).
- argparse (bawaan): argumen posisi, opsi, sakelar,
choices, subperintah. Masukan salah: pesan kestderrdan kode keluar 2. - Typer: fungsi bertype hint menjadi perintah; uji dengan
CliRunner(exit_code,output). - Uji CLI di dua jalur: sukses dan gagal;
CliRunneratausubprocess. - tkinter: pisahkan pembangunan jendela dari
mainloop()supaya logikanya bisa diuji denganwithdraw(); tampilan tetap harus dilihat manusia. - Dasbor statis = satu HTML mandiri dengan grafik tertanam; dasbor interaktif menunggu di Bab 43.
Selanjutnya, di Bab 43: aplikasi data cepat dan dasbor hidup: Streamlit, Gradio, Dash, peta interaktif, HTMX, cara menyajikannya ke pengguna, dan penutup Seri I5.