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), pertimbangkan ruamel.yaml sebagai alternatif yang lebih lengkap, atau pyyaml dengan 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 dengan Loader=yaml.Loader) dapat mengeksekusi kode Python arbitrer yang tersemat dalam YAML — ini adalah celah keamanan serius (arbitrary code execution). Selalu gunakan yaml.safe_load() untuk data dari luar, atau minimal yaml.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)  # ✓

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() — gunakan yaml.safe_load() untuk semua input dari luar; yaml.load() tanpa Loader yang tepat adalah celah keamanan serius.
  • allow_unicode=True — aktifkan saat dump() 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() dan dump_all() — untuk membaca dan menulis file YAML yang berisi beberapa dokumen.
  • Custom representer/constructor — gunakan yaml.add_representer() dan yaml.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.

← Sebelumnya: JSON   Berikutnya: MySQL →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact