Si vous avez ouvert un projet Rust, vous avez rencontré TOML. Si vous avez touché un package Python qui utilise
pyproject.toml, vous l'avez utilisé aussi. TOML — Tom's Obvious Minimal Language —
est le format de fichier de configuration qui continue tranquillement de gagner des développeurs qui en ont assez des
pièges des espaces blancs de YAML et du refus de JSON d'autoriser les commentaires. Créé par Tom Preston-Werner, cofondateur
de GitHub, TOML a été conçu autour d'une idée : un format de configuration doit être si évident que vous pouvez le lire
sans spécification. Voyons si cela tient.
Qu'est-ce que TOML exactement ?
TOML est un format de fichier de configuration avec trois objectifs de conception explicites, énoncés dès le début de la spécification officielle : il doit être évident à lire, minimal en complexité, et correspondre sans ambiguïté à une table de hachage (un dictionnaire/map dans la plupart des langages). Ce troisième objectif est le clé — chaque fichier TOML valide a exactement un résultat d'analyse correct. Pas de coercition de type surprenante, pas de pièges booléens style YAML, pas d'ambiguïté sur si une valeur est une chaîne ou un nombre.
Le format emprunte le style de l'en-tête de section des anciens fichiers INI mais ajoute des types appropriés, des tableaux et des tables imbriquées. Le résultat est quelque chose qui semble familier à quiconque a déjà édité un fichier de configuration, mais avec assez de structure pour qu'un analyseur puisse vous donner un vrai modèle de données typé. La version 1.0.0 de la spécification a été publiée en janvier 2021 après des années de raffinement — vous pouvez parcourir la spécification TOML sur GitHub si vous voulez plonger dans les cas limites.
Les bases : paires clé-valeur et commentaires
Un fichier TOML est constitué de paires clé-valeur. Les clés et les valeurs sont séparées par =,
et les commentaires commencent par #. Simple.
# 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 = falsePas besoin de guillemets sur la plupart des clés. Les valeurs sont typées — 8 est un entier, 0.85
est un flottant, false est un booléen. Pas de devinettes, pas de coercition implicite basée sur l'apparence de la valeur.
C'est le TOML quotidien que vous écrirez 90% du temps.
Types de chaînes : basique, littéral et multiligne
TOML a quatre types de chaînes. Cela couvre chaque cas réel proprement :
# 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/
'''Le type de chaîne littérale ('guillemets simples') est celui que les gens oublient d'exister, et il est
vraiment utile — les modèles regex et les chemins Windows sont beaucoup plus propres sans doublement des barres obliques inverses.
Choisissez le style de guillemets qui signifie moins de barres obliques inverses.
Nombres, booléens et dates/heures
Les types natifs de TOML couvrent tout ce que vous mettriez réellement dans un fichier de configuration. Notamment, il a un support datetime de première classe — quelque chose que YAML a techniquement mais gère de manière incohérente entre les analyseurs.
# 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 et false uniquement —
pas True, TRUE, yes, on, ou l'une des autres variantes
que YAML 1.1 accepte. C'est intentionnel. Si vous avez besoin d'une chaîne "true", mettez-la entre guillemets.Tables : les en-têtes de section style INI
Les tables en TOML sont définies avec la syntaxe [en-tête]. Tout ce qui se trouve sous un en-tête appartient
à cette table jusqu'à ce que l'en-tête suivant apparaisse. C'est la caractéristique qui rend TOML familier — c'est
essentiellement des fichiers INI mais avec des types.
[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"Les en-têtes pointillés comme [database.credentials] créent des tables imbriquées. Le résultat analysé
est exactement ce à quoi on s'attend : un objet database avec un objet credentials imbriqué.
Vous pouvez également écrire des tables en ligne pour les cas simples — plus à ce sujet ci-dessous.
Tableaux et tableau de tables
Les tableaux en TOML utilisent des crochets et peuvent s'étendre sur plusieurs lignes. La caractéristique vraiment distinctive de TOML
est le tableau de tables — défini avec des doubles crochets [[en-tête]]. C'est la réponse de TOML à
"comment exprimer une liste d'objets ?" sans que ça ressemble à du JSON.
# 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"]Cette syntaxe [[servers]] se transforme en un tableau de trois objets — équivalent à
"servers": [{...}, {...}, {...}] en JSON. C'est verbeux par rapport aux tableaux d'objets JSON,
mais l'avantage est la lisibilité quand chaque élément a de nombreux champs. Vous pouvez voir ce motif lourdement dans les
manifestes Cargo.toml
pour définir plusieurs cibles binaires, exemples et entrées bench.
Tables en ligne : une ligne compacte
Quand une table n'a que quelques champs et que vous ne voulez pas un en-tête de section entier pour elle, les tables en ligne vous permettent de l'écrire sur une seule ligne :
[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 }Les tables en ligne doivent rester sur une seule ligne et ne peuvent pas être étendues plus tard avec un [en-tête].
Utilisez-les pour les petits groupes de valeurs cohésives — elles sont idéales pour des choses comme des paires de coordonnées,
des cibles de build ou des configurations de drapeaux simples. Ne les utilisez pas quand vous avez plus de trois ou quatre champs ;
à ce stade, une table régulière est plus lisible.
TOML dans le monde réel
TOML s'est taillé une niche solide dans les écosystèmes Rust et Python, et gagne du terrain ailleurs. Voici où vous le rencontrerez au quotidien :
- Rust — Cargo.toml : Chaque projet Rust en a un. Définit les métadonnées du package, les dépendances, les fonctionnalités et les cibles de build. La référence du manifeste Cargo est le guide d'utilisation TOML le plus détaillé dans le monde réel que vous trouverez.
- Python — pyproject.toml : PEP 518 et PEP 621 ont standardisé TOML comme format de métadonnées de projet Python. Poetry, Hatch, PDM et setuptools lisent tous depuis lui.
- Deno : Le fichier de configuration Deno supporte TOML en plus de JSON.
- Hugo : Le générateur de site statique Hugo accepte TOML comme format de configuration et de front-matter — vous le verrez entre les délimiteurs
+++en haut des fichiers Markdown. - uv : Le gestionnaire de packages Python rapide d'Astral utilise pyproject.toml pour toute la configuration, faisant de TOML le standard de facto pour le nouveau outillage Python.
Si vous avez besoin de convertir une configuration existante entre formats, les convertisseurs TOML vers JSON et JSON vers TOML gèrent le mappage structurel pour vous. Ou utilisez le Formateur TOML pour nettoyer les espacements incohérents dans un fichier existant, et le Validateur TOML pour détecter les erreurs de syntaxe avant qu'elles n'apparaissent en production.
Analyse TOML en Python
Depuis Python 3.11, la bibliothèque standard inclut
tomllib
— pas de dépendance externe nécessaire. Pour Python 3.9 et 3.10, le backport
tomli
a une API identique, vous pouvez donc basculer entre eux avec un seul alias d'import.
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")Notez que tomllib.load() requiert le mode binaire ("rb"). C'est intentionnel —
TOML requiert l'encodage UTF-8, et l'ouverture en mode binaire permet à l'analyseur de gérer lui-même la vérification de l'encodage.
C'est un petit piège qui attrape les gens la première fois.
Analyse TOML en Rust
En Rust, la crate toml
est le choix standard. Elle s'intègre étroitement avec serde, vous permettant de désérialiser directement dans
vos propres structures avec un minimum de code répétitif :
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(())
}Ajoutez toml = "0.8" et serde = { version = "1", features = ["derive"] }
à vos [dependencies] dans Cargo.toml et c'est prêt. Les macros dérivées serde gèrent tout le
mappage des champs. Si vos noms de champs de structure utilisent snake_case mais les clés TOML utilisent kebab-case, ajoutez
#[serde(rename_all = "kebab-case")] au niveau de la structure et tout se mappe automatiquement.
TOML vs YAML vs JSON — Quand choisir quoi
Cette question vient à chaque nouveau projet. Voici l'analyse honnête :
- TOML : Meilleur pour les fichiers de configuration que les humains écrivent et maintiennent, où les valeurs typées sont importantes et l'imbrication est superficielle à modérée. Point fort : configs d'application, manifestes de build, paramètres d'outils. S'effondre pour l'imbrication profonde — 4+ niveaux deviennent gênants avec des en-têtes de section répétés.
- YAML : Meilleur quand vous écrivez des données structurées avec beaucoup de liste-d'objets (manifestes Kubernetes, workflows GitHub Actions). Le support de chaînes multilignes est vraiment meilleur que celui de TOML. L'inconvénient : la sensibilité aux espaces blancs et la coercition de type YAML 1.1 créent de vrais bugs dans la nature.
- JSON : Meilleur pour l'échange de données machine à machine, les API et quand vous avez besoin du support de chaîne d'outils le plus large possible. Pas idéal pour la configuration maintenue par des humains — pas de commentaires, et l'échappement des chaînes est fastidieux.
Un vrai pyproject.toml
Pour mettre tout ensemble, voici un pyproject.toml réaliste pour une bibliothèque Python —
le genre que vous trouveriez dans un projet open-source moderne. Remarquez comment le format porte beaucoup d'informations
structurées tout en étant facile à scanner :
[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 = 90C'est du vrai TOML qui fait de vrai travail. Chaque section [tool.x] est un espace de nommage séparé
pour un outil différent — ruff, mypy, coverage — tous vivant dans un seul fichier sans se marcher dessus.
Pas d'imbrication profonde requise, tout est lisible d'un coup d'œil.
Conclusion
TOML tient sa promesse : lisible, sans ambiguïté et proprement typé. Si vous démarrez un nouveau projet en Rust, Python ou Go, TOML vaut la peine d'être le choix par défaut pour les fichiers de configuration — surtout les fichiers qui seront committés dans le contrôle de source et édités par plusieurs personnes. L'absence de sensibilité aux espaces blancs seule en fait un soulagement comparé à YAML. Pour travailler directement avec des fichiers TOML dans votre navigateur, les outils Formateur TOML et Validateur TOML gèrent les tâches les plus courantes. Et si vous migrez un projet existant de JSON ou avez besoin de faire pont TOML vers un pipeline basé sur JSON, les convertisseurs TOML vers JSON et JSON vers TOML vous couvrent.