Wer ein Rust-Projekt öffnet, begegnet TOML. Wer ein Python-Paket mit
pyproject.toml berührt, hat es ebenfalls genutzt. TOML — Tom's Obvious Minimal Language —
ist das Konfigurationsdateiformat, das Entwickler, die von YAMLs Whitespace-Fallen und JSONs
Kommentarverweigerung genug haben, still und leise für sich gewinnt. Erstellt von Tom Preston-Werner,
Mitgründer von GitHub, wurde TOML mit einer einzigen Idee entworfen: Ein Konfigurationsformat soll
so offensichtlich sein, dass man es ohne Spezifikation lesen kann. Mal sehen, ob es diesem Versprechen gerecht wird.
Was genau ist TOML?
TOML ist ein Konfigurationsdateiformat mit drei expliziten Designzielen, die direkt am Anfang der offiziellen Spezifikation beschrieben werden: Es soll offensichtlich lesbar, minimal in seiner Komplexität und eindeutig auf eine Hashtabelle (Dictionary/Map in den meisten Sprachen) abbildbar sein. Das dritte Ziel ist das entscheidende — jede gültige TOML-Datei hat genau ein korrektes Parse-Ergebnis. Keine überraschende Typumwandlung, keine YAML-artigen Boolean-Fallen, keine Mehrdeutigkeit, ob ein Wert ein String oder eine Zahl ist.
Das Format übernimmt den Abschnittsheader-Stil alter INI-Dateien, fügt aber echte Typen, Arrays und verschachtelte Tabellen hinzu. Das Ergebnis fühlt sich für jeden, der schon einmal eine Konfigurationsdatei bearbeitet hat, vertraut an, aber mit genug Struktur, dass ein Parser ein echtes typisiertes Datenmodell daraus erstellen kann. Version 1.0.0 der Spezifikation wurde im Januar 2021 nach Jahren der Verbesserung veröffentlicht — die vollständige TOML-Spezifikation auf GitHub kann man für Randfall-Details durchsuchen.
Die Grundlagen: Schlüssel-Wert-Paare und Kommentare
Eine TOML-Datei besteht aus Schlüssel-Wert-Paaren. Schlüssel und Werte werden durch =
getrennt, Kommentare beginnen mit #. Ganz einfach.
# Cargo.toml — Rust package manifest
[package]
name = "image-resizer"
version = "0.4.2"
edition = "2021"
authors = ["Ada Lovelace <[email protected]>"]
description = "Fast image resizing with Lanczos3 resampling"
license = "MIT"
repository = "https://github.com/example/image-resizer"
# Integers, floats, booleans — all native types
max_threads = 8
quality_default = 0.85
verbose_logging = falseBei den meisten Schlüsseln sind keine Anführungszeichen erforderlich. Werte sind typisiert —
8 ist ein Integer, 0.85 ist ein Float, false ist ein Boolean.
Kein Raten, keine implizite Typumwandlung basierend auf dem Aussehen des Werts. Das ist das alltägliche
TOML, das man 90 % der Zeit schreibt.
String-Typen: Basic, Literal und Mehrzeilig
TOML hat vier String-Typen. Diese decken jeden realen Anwendungsfall sauber ab:
# Basic strings — double quotes, support escape sequences
greeting = "Hello, \nworld!"
path = "C:\\Users\\ada\\Documents"
# Literal strings — single quotes, no escape processing at all
regex_pattern = '\d{4}-\d{2}-\d{2}'
windows_path = 'C:\Users\ada'
# Multiline basic string — triple double quotes
sql_query = """
SELECT user_id, email, created_at
FROM users
WHERE active = true
AND created_at > '2024-01-01'
ORDER BY created_at DESC
"""
# Multiline literal string — triple single quotes, no escapes
shell_script = '''
#!/bin/bash
echo "Deploying $APP_NAME to $ENV"
kubectl apply -f k8s/
'''Der Literal-String-Typ ('einfache Anführungszeichen') ist derjenige, den Leute vergessen,
und er ist wirklich nützlich — Regex-Muster und Windows-Pfade sind ohne das Verdoppeln von Backslashes viel
klarer. Man wählt den Anführungszeichenstil, bei dem man weniger Backslashes schreiben muss.
Zahlen, Booleans und Datumsangaben
TOMLs native Typen decken alles ab, was man tatsächlich in eine Konfigurationsdatei schreiben würde. Bemerkenswert ist die erstklassige Datetime-Unterstützung — etwas, das YAML technisch gesehen hat, aber bei verschiedenen Parsern inkonsistent behandelt.
# Integers — underscores allowed as separators (like numeric literals in code)
max_connections = 1_000_000
port = 5432
hex_color = 0xFF6B6B # hex prefix supported
octal_permissions = 0o755 # octal prefix supported
# Floats
pi = 3.14159265
compression_ratio = 1.5e-3
infinity_val = inf # special values: inf, -inf, nan
# Booleans — lowercase only (not True, TRUE, yes, on)
ssl_enabled = true
dry_run = false
# Datetimes — RFC 3339 format
created_at = 2024-03-15T09:30:00Z
updated_at = 2024-03-15T14:22:10+05:30
log_date = 2024-03-15 # local date (no time)
backup_time = 03:00:00 # local time (no date)true und false —
nicht True, TRUE, yes, on oder andere Varianten,
die YAML 1.1 akzeptiert. Das ist beabsichtigt. Wenn man den String "true" benötigt, setzt man
Anführungszeichen darum.Tabellen: Die INI-artigen Abschnittsheader
Tabellen in TOML werden mit der [header]-Syntax definiert. Alles unterhalb eines Headers
gehört zu dieser Tabelle, bis der nächste Header erscheint. Das ist das Feature, das TOML vertraut erscheinen
lässt — es sind im Wesentlichen INI-Dateien, aber mit Typen.
[database]
host = "db.internal"
port = 5432
name = "app_production"
pool_size = 20
[database.credentials]
username = "app_user"
# Don't put real passwords here — use env vars or a secrets manager
password_env = "DB_PASSWORD"
[server]
host = "0.0.0.0"
port = 8080
workers = 4
[server.tls]
enabled = true
cert_file = "/etc/ssl/certs/app.crt"
key_file = "/etc/ssl/private/app.key"Gepunktete Header wie [database.credentials] erstellen verschachtelte Tabellen.
Das geparste Ergebnis ist genau das, was man erwartet: ein database-Objekt mit einem
verschachtelten credentials-Objekt. Für einfache Fälle kann man auch Inline-Tabellen
schreiben — dazu weiter unten mehr.
Arrays und Array of Tables
Arrays in TOML verwenden eckige Klammern und können sich über mehrere Zeilen erstrecken.
Das wirklich charakteristische TOML-Feature ist Array of Tables — definiert mit
doppelten Klammern [[header]]. Das ist TOMLs Antwort auf die Frage „Wie kann ich eine
Liste von Objekten ausdrücken?" ohne dabei wie JSON auszusehen.
# Regular arrays — can be split across lines, trailing comma is fine
allowed_origins = [
"https://app.example.com",
"https://admin.example.com",
"http://localhost:3000",
]
supported_formats = ["jpeg", "png", "webp", "avif"]
retry_delays_ms = [100, 250, 500, 1000, 2000]
# Array of Tables — [[double brackets]]
# Each [[servers]] header appends a new object to the servers array
[[servers]]
name = "web-01"
ip = "10.0.1.10"
role = "primary"
tags = ["web", "prod"]
[[servers]]
name = "web-02"
ip = "10.0.1.11"
role = "replica"
tags = ["web", "prod"]
[[servers]]
name = "db-01"
ip = "10.0.2.10"
role = "primary"
tags = ["database", "prod"]Die [[servers]]-Syntax wird in ein Array aus drei Objekten geparst — äquivalent zu
"servers": [{...}, {...}, {...}] in JSON. Es ist ausführlicher als JSON-Arrays von Objekten,
aber der Vorteil ist die Lesbarkeit, wenn jedes Element viele Felder hat. Dieses Muster ist häufig in
Cargo.toml-Manifesten
zu sehen, wo mehrere binäre Targets, Beispiele und Benchmark-Einträge definiert werden.
Inline-Tabellen: Kompakte Einzeiler
Wenn eine Tabelle nur ein paar Felder hat und man keinen vollständigen Abschnittsheader möchte, erlauben Inline-Tabellen das Schreiben in einer einzigen Zeile:
[build]
# Inline table — must stay on one line
target = { arch = "x86_64", os = "linux", libc = "musl" }
# Equivalent to writing:
# [build.target]
# arch = "x86_64"
# os = "linux"
# libc = "musl"
[feature_flags]
auth = { enabled = true, rollout_pct = 100 }
search = { enabled = true, rollout_pct = 50 }
beta = { enabled = false, rollout_pct = 0 }Inline-Tabellen müssen in einer einzigen Zeile bleiben und können später nicht mit einem
[header] erweitert werden. Man verwendet sie für kleine, zusammenhängende Wertegruppen —
sie eignen sich hervorragend für Koordinatenpaare, Build-Targets oder einfache Flag-Konfigurationen.
Man sollte sie nicht verwenden, wenn mehr als drei oder vier Felder vorhanden sind; dann ist eine
reguläre Tabelle besser lesbar.
TOML in der Praxis
TOML hat sich eine starke Nische im Rust- und Python-Ökosystem erarbeitet und gewinnt anderswo an Bedeutung. Hier begegnet man ihm im Alltag:
- Rust — Cargo.toml: Jedes Rust-Projekt hat eine. Definiert Paketmetadaten, Abhängigkeiten, Features und Build-Targets. Die Cargo-Manifest-Referenz ist der detaillierteste TOML-Praxis-Leitfaden, den man finden kann.
- Python — pyproject.toml: PEP 518 und PEP 621 haben TOML als Python-Projektmetadaten-Format standardisiert. Poetry, Hatch, PDM und setuptools lesen alle davon.
- Deno: Die Deno-Konfigurationsdatei unterstützt TOML zusätzlich zu JSON.
- Hugo: Der statische Website-Generator Hugo akzeptiert TOML als Konfigurations- und Front-Matter-Format — man findet es zwischen
+++-Trennzeichen am Anfang von Markdown-Dateien. - uv: Der schnelle Python-Paketmanager von Astral verwendet pyproject.toml für alle Konfigurationen, was TOML zum De-facto-Standard für neue Python-Tools macht.
Wenn man eine bestehende Konfiguration zwischen Formaten konvertieren muss, übernehmen die TOML zu JSON- und JSON zu TOML-Konverter die strukturelle Abbildung. Oder man verwendet den TOML-Formatter, um inkonsistente Abstände in einer bestehenden Datei zu bereinigen, und den TOML-Validator, um Syntaxfehler zu erkennen, bevor sie in der Produktion auftauchen.
TOML in Python parsen
Seit Python 3.11 enthält die Standardbibliothek
tomllib
— keine externe Abhängigkeit nötig. Für Python 3.9 und 3.10 hat der
tomli-Backport
eine identische API, sodass man mit einem einzigen Import-Alias wechseln kann.
import sys
if sys.version_info >= (3, 11):
import tomllib
else:
import tomli as tomllib # pip install tomli
# tomllib only reads binary mode — open with "rb"
with open("pyproject.toml", "rb") as f:
config = tomllib.load(f)
# Types match TOML exactly: str, int, float, bool, datetime, list, dict
project_name = config["project"]["name"] # str
python_requires = config["project"]["requires-python"] # str
dependencies = config["project"]["dependencies"] # list[str]
print(f"Project: {project_name}")
print(f"Requires Python: {python_requires}")
print(f"Dependencies ({len(dependencies)}):")
for dep in dependencies:
print(f" {dep}")
# Parse from a string with tomllib.loads()
raw = """
[server]
host = "localhost"
port = 8080
debug = true
"""
server_config = tomllib.loads(raw)
print(server_config["server"]["port"]) # 8080 (int, not "8080")Zu beachten ist, dass tomllib.load() den binären Modus ("rb") erfordert.
Das ist beabsichtigt — TOML erfordert UTF-8-Kodierung, und das Öffnen im binären Modus lässt den Parser
die Kodierungsprüfung selbst durchführen. Das ist eine kleine Besonderheit, die beim ersten Mal auffällt.
TOML in Rust parsen
In Rust ist der toml-Crate
die Standardwahl. Er integriert sich eng mit serde, sodass man direkt in eigene Structs
deserialisieren kann mit minimalem Boilerplate:
use serde::Deserialize;
use std::fs;
#[derive(Debug, Deserialize)]
struct AppConfig {
server: ServerConfig,
database: DatabaseConfig,
feature_flags: FeatureFlags,
}
#[derive(Debug, Deserialize)]
struct ServerConfig {
host: String,
port: u16,
workers: usize,
}
#[derive(Debug, Deserialize)]
struct DatabaseConfig {
host: String,
port: u16,
name: String,
pool_size: u32,
}
#[derive(Debug, Deserialize)]
struct FeatureFlags {
enable_beta: bool,
max_upload_mb: u32,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let raw = fs::read_to_string("config.toml")?;
let config: AppConfig = toml::from_str(&raw)?;
println!("Server: {}:{}", config.server.host, config.server.port);
println!("DB pool size: {}", config.database.pool_size);
println!("Beta enabled: {}", config.feature_flags.enable_beta);
Ok(())
}Man fügt toml = "0.8" und serde = { version = "1", features = ["derive"] }
zu [dependencies] in Cargo.toml hinzu, und schon ist alles bereit. Die serde-Derive-Makros
übernehmen das gesamte Feld-Mapping. Wenn die Struct-Feldnamen snake_case verwenden, die TOML-Schlüssel
aber kebab-case, fügt man #[serde(rename_all = "kebab-case")] auf Struct-Ebene hinzu und
alles wird automatisch gemappt.
TOML vs YAML vs JSON — Wann welches wählen
Diese Frage stellt sich bei jedem neuen Projekt. Hier ist die ehrliche Einschätzung:
- TOML: Am besten für Konfigurationsdateien, die Menschen schreiben und pflegen, wo typisierte Werte wichtig sind und die Verschachtelung flach bis moderat ist. Ideal für: App-Konfigurationen, Build-Manifeste, Tool-Einstellungen. Wird bei tiefer Verschachtelung unbequem — 4+ Ebenen werden mit wiederholten Abschnittsheadern umständlich.
- YAML: Am besten, wenn man strukturierte Daten mit vielen Objekt-Listen schreibt (Kubernetes-Manifeste, GitHub-Actions-Workflows). Die mehrzeilige String-Unterstützung ist wirklich besser als TOMLs. Der Nachteil: Whitespace-Sensitivität und YAML-1.1-Typumwandlung erzeugen echte Fehler in der Praxis.
- JSON: Am besten für den Datenaustausch zwischen Maschinen, APIs und wenn man die breitestmögliche Toolchain-Unterstützung benötigt. Nicht ideal für manuell gepflegte Konfigurationen — keine Kommentare, und String-Escaping ist mühsam.
Ein echtes pyproject.toml
Um alles zusammenzuführen, hier ein realistisches pyproject.toml für eine Python-Bibliothek —
wie man es in einem modernen Open-Source-Projekt findet. Man beachte, wie das Format viele strukturierte
Informationen trägt und dabei noch einfach zu überfliegen ist:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "httpx-cache"
version = "1.2.0"
description = "Transparent HTTP caching layer for httpx"
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.9"
authors = [
{ name = "Ada Lovelace", email = "[email protected]" },
]
keywords = ["http", "cache", "httpx", "async"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
dependencies = [
"httpx>=0.25.0",
"anyio>=4.0.0",
]
[project.optional-dependencies]
redis = ["redis>=5.0.0"]
dev = [
"pytest>=7.4.0",
"pytest-asyncio>=0.23.0",
"coverage[toml]>=7.3.0",
"ruff>=0.1.0",
"mypy>=1.7.0",
]
[project.urls]
Homepage = "https://github.com/example/httpx-cache"
Changelog = "https://github.com/example/httpx-cache/blob/main/CHANGELOG.md"
[tool.ruff]
line-length = 100
target-version = "py39"
[tool.ruff.lint]
select = ["E", "F", "I", "UP"]
[tool.mypy]
strict = true
python_version = "3.9"
[tool.coverage.run]
source = ["httpx_cache"]
branch = true
[tool.coverage.report]
fail_under = 90Das ist echtes TOML bei echter Arbeit. Jeder [tool.x]-Abschnitt ist ein separater
Namespace für ein anderes Tool — ruff, mypy, coverage — alle in einer Datei, ohne sich gegenseitig zu
beeinträchtigen. Keine tiefe Verschachtelung erforderlich, alles ist auf einen Blick lesbar.
Zusammenfassung
TOML hält, was es verspricht: lesbar, eindeutig und sauber typisiert. Wer ein neues Projekt in Rust, Python oder Go startet, sollte TOML als Standard für Konfigurationsdateien in Betracht ziehen — besonders für Dateien, die in die Versionskontrolle eingecheckt werden und von mehreren Personen bearbeitet werden sollen. Allein das Fehlen von Whitespace-Sensitivität macht es im Vergleich zu YAML angenehmer. Für die direkte Arbeit mit TOML-Dateien im Browser übernehmen der TOML-Formatter und der TOML-Validator die häufigsten Aufgaben. Und wenn man ein bestehendes Projekt von JSON migriert oder TOML in eine JSON-basierte Pipeline integrieren muss, sind die TOML zu JSON- und JSON zu TOML-Konverter zur Stelle.