Argumen CLI cocok untuk pengaturan yang berubah setiap eksekusi; environment variables cocok untuk perbedaan antar lingkungan. Untuk pengaturan yang terstruktur dan hidup lama — host database, level log, daftar folder yang dipantau — berkas konfigurasi adalah rumah yang tepat. Pada Chapter ini kita memuat berkas TOML, JSON, dan YAML, menggabungkannya dengan nilai default dan environment variables, lalu menemukan lokasi berkasnya mengikuti konvensi sistem operasi.
Tiga Format, Satu Bentuk Data #
Ketiga format berikut memuat isi yang identik. Berkas konfigurasi.toml:
host = "example.com"
[log]
level = "DEBUG"Berkas konfigurasi.json:
{
"host": "example.com",
"log": {
"level": "DEBUG"
}
}Berkas konfigurasi.yaml:
host: example.com
log:
level: DEBUGSkrip baca_tiga_format.py memuat ketiganya lalu membandingkan hasilnya:
import json
import tomllib
import yaml
with open("konfigurasi.toml", "rb") as berkas:
dari_toml = tomllib.load(berkas)
with open("konfigurasi.json") as berkas:
dari_json = json.load(berkas)
with open("konfigurasi.yaml") as berkas:
dari_yaml = yaml.safe_load(berkas)
print("isi TOML:", dari_toml)
print("sama semua:", dari_toml == dari_json == dari_yaml)Output yang Dihasilkan:
isi TOML: {'host': 'example.com', 'log': {'level': 'DEBUG'}}
sama semua: TrueApapun formatnya, hasil akhirnya adalah dictionary bertingkat yang sama, sehingga logika program tidak perlu tahu asal konfigurasi. Perlu dicatat: tomllib hadir di Python 3.11 dan hanya membaca TOML; json adalah standard library; sedangkan YAML memerlukan paket pihak ketiga PyYAML. Selalu gunakan yaml.safe_load(), bukan yaml.load() — alasan keamanannya dibahas pada Chapter Deserialization Risks.
Perbandingan ketiganya dirangkum berikut:
| Aspek | TOML | JSON | YAML |
|---|---|---|---|
| Dukungan standard library | ya (tomllib, 3.11+) | ya (json) | tidak |
| Komentar | ya | tidak | ya |
| String multibaris | ya | lewat escape \n | ya |
| Tipe data bawaan | angka, bool, tanggal | terbatas | kaya, termasuk null |
| Kecenderungan galat penulisan | rendah | kurung dan koma yang telat | indentasi |
Konfigurasi Berlapis: defaults, Berkas, Environment #
Praktik umum aplikasi matang: simpan default dalam kode, timpa dengan berkas, lalu timpa lagi dengan environment variables. Penggabungan dictionary bertingkat memerlukan fungsi merge yang menurun ke dalam. Berkas config_berlapis.py:
import os
import tomllib
def deep_merge(base, override):
hasil = dict(base)
for kunci, nilai in override.items():
if isinstance(nilai, dict) and isinstance(hasil.get(kunci), dict):
hasil[kunci] = deep_merge(hasil[kunci], nilai)
else:
hasil[kunci] = nilai
return hasil
DEFAULTS = {
"host": "localhost",
"port": 8000,
"log": {"level": "INFO", "warna": False},
}
with open("konfigurasi.toml", "rb") as berkas:
dari_berkas = tomllib.load(berkas)
env_override = {}
if "APP_PORT" in os.environ:
env_override["port"] = int(os.environ["APP_PORT"])
config = deep_merge(DEFAULTS, dari_berkas)
config = deep_merge(config, env_override)
print("host:", config["host"])
print("port:", config["port"])
print("log :", config["log"])Mari kita bedah inti mekanismenya:
- Baris 5-12 (
def deep_merge): menyalinbaseagar tidak berubah, lalu menimpanya dengan pasangan kunci-nilai darioverride. - Baris 8-9 (
isinstance(nilai, dict)): bila kedua nilai bertipe dictionary, fungsi memanggil dirinya sendiri sehingga satu cabang dapat menimpa sebagian saja. - Baris 24-26 (
env_override): environment variables diterjemahkan ke bentuk dictionary agar bisa melewatideep_mergeyang sama. - Baris 28-29 (
config = deep_merge(DEFAULTS, dari_berkas)): urutan pemanggilan menentukan prioritas — berkas menimpa default, lalu environment menimpa berkas.
Output yang Dihasilkan tanpa APP_PORT:
host: example.com
port: 8000
log : {'level': 'DEBUG', 'warna': False}Perhatikan bahwa log.warna tetap False dari default meski berkas hanya mengatur log.level — inilah manfaat merge bertingkat dibanding sekadar dict.update(). Dengan APP_PORT=9000, environment menimpa kedua lapisan sebelumnya:
host: example.com
port: 9000
log : {'level': 'DEBUG', 'warna': False}Menemukan Lokasi Berkas Config #
Program perlu mencari berkas konfigurasi tanpa hardcode path absolut. Konvensi XDG pada Linux menempatkannya di ~/.config/<nama-app>/config.toml, dengan /etc/<nama-app>.toml sebagai lokasi sistem. Fungsi cari_config() berikut memeriksa kandidat dari yang paling spesifik:
from pathlib import Path
def cari_config(nama_app):
kandidat = [
Path.home() / ".config" / nama_app / "config.toml",
Path("/etc") / f"{nama_app}.toml",
]
for path in kandidat:
if path.is_file():
return path
return kandidat[0]
lokasi = cari_config("aplikasi-saya")
print("berkas config :", lokasi.name)
print("folder config :", lokasi.parent.name)Output yang Dihasilkan (dengan ~/.config/aplikasi-saya/config.toml yang sudah ada):
berkas config : config.toml
folder config : aplikasi-sayaBila tidak ada kandidat yang ditemukan, fungsi mengembalikan kandidat pertama — path tempat program bisa membuat berkas baru. Berikan juga opsi eksplisit seperti --config path/ke/berkas agar pengguna tetap bisa memakai lokasi khusus.
Validasi Schema #
Berkas konfigurasi ditulis manusia, jadi salah ketik pasti terjadi: port = "delapan" atau host yang hilang akan meledak jadi galat aneh jauh di dalam program. Validasi schema menolak konfigurasi jelek sedini mungkin dengan pesan yang jelas. Library pydantic adalah pilihan populer untuk tugas ini:
from pydantic import BaseModel, ValidationError
class Config(BaseModel):
host: str
port: int
try:
Config(host="example.com", port="bukan angka")
except ValidationError as galat:
print("konfigurasi tidak valid:", galat.error_count(), "galat")Output yang Dihasilkan:
konfigurasi tidak valid: 1 galatClass Config mendefinisikan struktur beserta tipe tiap kolom; pydantic memeriksa data hasil parsing terhadap definisi tersebut. Alternatif yang lebih ringan adalah dataclass plus pemeriksaan manual, materi Chapter Dataclasses.
Dengan pemuatan berlapis, penemuan lokasi standar, dan validasi schema, rantai konfigurasi kita lengkap: default dalam kode, berkas untuk yang stabil, environment untuk yang berubah per lingkungan, dan argumen CLI untuk penimpaan ad hoc. Berikutnya kita keluar dari proses Python untuk menjalankan program lain melalui subprocess.