YAML #
YAML (YAML Ain’t Markup Language) adalah format serialisasi data yang dirancang untuk mudah dibaca manusia. Berbeda dengan JSON yang menggunakan kurung kurawal dan tanda kutip, YAML mengandalkan indentasi dan tanda titik dua — hasilnya jauh lebih bersih untuk file konfigurasi. Kamu akan menemukan YAML di mana-mana: docker-compose.yml, GitHub Actions, Kubernetes manifests, Ansible playbooks, hingga konfigurasi aplikasi Python seperti Django dan FastAPI. Memahami YAML dengan baik — termasuk jebakannya, terutama soal keamanan — adalah skill penting bagi setiap developer Python.
Instalasi #
PyYAML adalah library pihak ketiga yang perlu diinstal terlebih dahulu:
pip install pyyaml
Untuk kebutuhan performa tinggi (parsing file YAML besar), pertimbangkanruamel.yamlsebagai alternatif yang lebih lengkap, ataupyyamldengan C extension yang terinstal otomatis di sebagian besar sistem.
Sintaks YAML Dasar #
Sebelum membaca atau menulis YAML dari Python, penting memahami struktur YAML itu sendiri. YAML menggunakan indentasi (spasi, bukan tab) untuk menentukan hierarki.
# Komentar dimulai dengan tanda #
# --- Tipe data skalar ---
nama: Budi Santoso
usia: 28
tinggi: 175.5
aktif: true # boolean: true/false (bukan True/False seperti Python)
tidak_ada: null # null atau ~
# --- String multi-baris ---
deskripsi: | # literal block -- newline dipertahankan
Baris pertama.
Baris kedua.
Baris ketiga.
ringkasan: > # folded block -- newline jadi spasi
Ini semua akan
jadi satu baris
panjang.
# --- List ---
hobi:
- membaca
- hiking
- coding
# Atau format inline (seperti JSON array)
tag: [python, backend, api]
# --- Dict/Mapping ---
alamat:
kota: Bandung
provinsi: Jawa Barat
kode_pos: "40115" # string -- tanda kutip untuk angka yang perlu jadi string
# --- List of dict ---
tim:
- nama: Budi
peran: backend
- nama: Sari
peran: frontend
Membaca YAML #
PyYAML menyediakan dua fungsi utama untuk membaca: safe_load() dan load(). Pilihan di antara keduanya bukan soal preferensi — ini soal keamanan.
import yaml
# safe_load() -- dari string
yaml_string = """
nama: Budi
usia: 28
hobi:
- membaca
- coding
aktif: true
"""
data = yaml.safe_load(yaml_string)
print(data)
# {'nama': 'Budi', 'usia': 28, 'hobi': ['membaca', 'coding'], 'aktif': True}
print(type(data["aktif"])) # <class 'bool'>
print(type(data["hobi"])) # <class 'list'>
# safe_load() -- dari file
with open("config.yaml", "r", encoding="utf-8") as f:
config = yaml.safe_load(f)
Jangan gunakan
yaml.load()tanpa Loader pada input yang tidak kamu kontrol.yaml.load()tanpa Loader (atau denganLoader=yaml.Loader) dapat mengeksekusi kode Python arbitrer yang tersemat dalam YAML — ini adalah celah keamanan serius (arbitrary code execution). Selalu gunakanyaml.safe_load()untuk data dari luar, atau minimalyaml.load(..., Loader=yaml.SafeLoader).# ANTI-PATTERN: rentan eksekusi kode arbitrer data = yaml.load(user_input) # ✗ -- BERBAHAYA # BENAR: gunakan safe_load untuk input eksternal data = yaml.safe_load(user_input) # ✓
Menulis YAML #
yaml.dump() mengonversi objek Python menjadi string YAML, dengan berbagai opsi formatting.
import yaml
data = {
"nama": "Budi Santoso",
"usia": 28,
"hobi": ["membaca", "hiking"],
"alamat": {
"kota": "Bandung",
"provinsi": "Jawa Barat"
},
"aktif": True,
"catatan": None
}
# Output default (key diurutkan alfabetis, ASCII only)
print(yaml.dump(data))
# aktif: true
# alamat:
# kota: Bandung
# provinsi: Jawa Barat
# usia: 28
# ...
# Output yang lebih terbaca
yaml_output = yaml.dump(
data,
allow_unicode=True, # karakter non-ASCII tidak di-escape
default_flow_style=False, # format block, bukan inline
sort_keys=False, # urutan key dipertahankan
indent=2
)
print(yaml_output)
# Langsung ke file
with open("output.yaml", "w", encoding="utf-8") as f:
yaml.dump(data, f, allow_unicode=True, default_flow_style=False)
# ANTI-PATTERN: dump tanpa allow_unicode
yaml.dump({"kota": "Jëpara"})
# kota: "J\xebpara" ✗ -- karakter non-ASCII di-escape, sulit dibaca
# BENAR: aktifkan allow_unicode
yaml.dump({"kota": "Jëpara"}, allow_unicode=True)
# kota: Jëpara ✓
Anchors dan Aliases #
Anchors (&) dan aliases (*) adalah fitur YAML yang memungkinkan kamu mendefinisikan nilai sekali dan mereferensikannya di beberapa tempat — sangat berguna di file konfigurasi untuk menghindari duplikasi.
# Contoh: config.yaml dengan anchors
defaults: &defaults
timeout: 30
retry: 3
log_level: INFO
database:
<<: *defaults # merge key -- inherit semua dari defaults
host: localhost
port: 5432
name: myapp
cache:
<<: *defaults # juga inherit dari defaults
host: localhost
port: 6379
timeout: 5 # override timeout dari defaults
import yaml
with open("config.yaml", "r", encoding="utf-8") as f:
config = yaml.safe_load(f)
# PyYAML otomatis me-resolve anchors dan aliases
print(config["database"]["timeout"]) # 30 (dari defaults)
print(config["cache"]["timeout"]) # 5 (di-override)
print(config["cache"]["retry"]) # 3 (dari defaults)
Multi-Document YAML #
Satu file YAML bisa berisi beberapa dokumen yang dipisahkan dengan ---. Ini umum dipakai di Kubernetes manifests dan konfigurasi pipeline.
# multi.yaml
---
nama: Budi
peran: backend
---
nama: Sari
peran: frontend
---
nama: Andi
peran: devops
import yaml
# Membaca semua dokumen sekaligus
with open("multi.yaml", "r", encoding="utf-8") as f:
dokumen_list = list(yaml.safe_load_all(f))
print(len(dokumen_list)) # 3
print(dokumen_list[0]["nama"]) # Budi
print(dokumen_list[2]["peran"]) # devops
# Menulis beberapa dokumen
tim = [
{"nama": "Budi", "peran": "backend"},
{"nama": "Sari", "peran": "frontend"},
]
with open("tim.yaml", "w", encoding="utf-8") as f:
yaml.dump_all(tim, f, allow_unicode=True, default_flow_style=False)
Objek Kustom #
Untuk serialize dan deserialize kelas Python kustom, PyYAML menggunakan mekanisme representer dan constructor.
import yaml
from dataclasses import dataclass
@dataclass
class Konfigurasi:
host: str
port: int
debug: bool = False
# Representer: Python object → YAML
def konfigurasi_representer(dumper, data):
return dumper.represent_mapping("!Konfigurasi", {
"host": data.host,
"port": data.port,
"debug": data.debug
})
# Constructor: YAML → Python object
def konfigurasi_constructor(loader, node):
nilai = loader.construct_mapping(node)
return Konfigurasi(**nilai)
yaml.add_representer(Konfigurasi, konfigurasi_representer)
yaml.add_constructor("!Konfigurasi", konfigurasi_constructor)
# Serialize
cfg = Konfigurasi(host="localhost", port=8080, debug=True)
yaml_string = yaml.dump(cfg)
print(yaml_string)
# !Konfigurasi
# debug: true
# host: localhost
# port: 8080
# Deserialize
cfg2 = yaml.load(yaml_string, Loader=yaml.FullLoader)
print(cfg2) # Konfigurasi(host='localhost', port=8080, debug=True)
print(type(cfg2)) # <class 'Konfigurasi'>
Pola Konfigurasi Aplikasi #
YAML adalah pilihan utama untuk file konfigurasi aplikasi karena keterbacaannya. Berikut pola yang umum dipakai.
# config.yaml
app:
nama: MyApp
versi: "1.0.0"
debug: false
secret_key: "ganti-dengan-nilai-aman"
server:
host: "0.0.0.0"
port: 8000
workers: 4
database:
host: localhost
port: 5432
nama: myapp_db
user: admin
password: "" # kosongkan, isi via environment variable
logging:
level: INFO
format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
import yaml
import os
from pathlib import Path
from dataclasses import dataclass
def load_config(path: str = "config.yaml") -> dict:
config_path = Path(path)
if not config_path.exists():
raise FileNotFoundError(f"File konfigurasi tidak ditemukan: {path}")
with open(config_path, "r", encoding="utf-8") as f:
config = yaml.safe_load(f)
if config is None:
raise ValueError("File konfigurasi kosong atau tidak valid")
# Override nilai sensitif dari environment variable
if os.getenv("DB_PASSWORD"):
config["database"]["password"] = os.getenv("DB_PASSWORD")
if os.getenv("SECRET_KEY"):
config["app"]["secret_key"] = os.getenv("SECRET_KEY")
return config
config = load_config()
print(config["app"]["nama"]) # MyApp
print(config["server"]["port"]) # 8000
YAML vs JSON — Kapan Memilih #
Pilih YAML jika:
✓ File konfigurasi yang dibaca/diedit manusia (docker-compose, CI/CD)
✓ Butuh komentar dalam file (JSON tidak mendukung komentar)
✓ Butuh anchors/aliases untuk menghindari duplikasi
✓ Struktur hierarkis yang dalam dan perlu keterbacaan tinggi
Pilih JSON jika:
✓ API response / komunikasi antar service
✓ Data yang diproses mesin, keterbacaan bukan prioritas
✓ Performa parsing lebih penting (JSON lebih cepat di-parse)
✓ Ekosistem yang sudah menggunakan JSON (package.json, tsconfig.json)
✓ Butuh validasi schema yang mudah (JSON Schema lebih matang)
Ringkasan #
- Selalu
safe_load()— gunakanyaml.safe_load()untuk semua input dari luar;yaml.load()tanpa Loader yang tepat adalah celah keamanan serius.allow_unicode=True— aktifkan saatdump()agar karakter non-ASCII tidak di-escape menjadi\uXXXX.default_flow_style=False— gunakan agar output YAML berbentuk block (bertingkat), bukan inline seperti JSON.sort_keys=False— pertahankan urutan key asli saat serialisasi jika urutan penting.- Anchors (
&) dan aliases (*) — manfaatkan untuk menghindari duplikasi di file konfigurasi yang kompleks.safe_load_all()dandump_all()— untuk membaca dan menulis file YAML yang berisi beberapa dokumen.- Custom representer/constructor — gunakan
yaml.add_representer()danyaml.add_constructor()untuk serialize/deserialize kelas kustom.- YAML untuk config, JSON untuk API — YAML unggul dalam keterbacaan dan fitur (komentar, anchors), JSON unggul dalam performa dan kompatibilitas antar platform.