Cuando un proyecto crece, la configuración hardcodeada deja de ser viable. Necesitas archivos externos, y la elección del formato importa más de lo que parece: TOML y YAML resuelven el mismo problema de formas muy distintas, con garantías de seguridad y ergonomía que difieren bastante.
TOML (Tom’s Obvious Minimal Language) fue diseñado específicamente para configuración. Su premisa central es que cada valor tiene un tipo inequívoco: 42 es siempre un entero, "42" es siempre una cadena, true es siempre un booleano. No hay ambigüedad. Eso es lo que lo hace la base de pyproject.toml y el estándar de facto para configurar proyectos Python modernos.
Desde Python 3.11, la stdlib incluye tomllib para leer TOML de forma segura. El detalle que hay que tener claro desde el principio: tomllib es solo lectura. Si necesitas escribir TOML, necesitas la librería de terceros tomli_w. Esta separación es intencional — la spec de TOML es estable para lectura pero el equipo de CPython prefirió no comprometerse con un serializador en stdlib aún.
YAML es más expresivo pero esa expresividad tiene un coste. yaml.load() sin argumento Loader — o con Loader=yaml.FullLoader en versiones viejas — puede ejecutar código Python arbitrario durante el parseo. No es una advertencia teórica: hay CVEs reales por esto. La regla es simple: yaml.safe_load() siempre, sin excepciones.
Si te preguntas cuándo elegir uno sobre el otro: TOML para configuración de proyectos y herramientas (es lo que espera el ecosistema Python), YAML cuando el formato ya está impuesto externamente — Kubernetes, GitHub Actions, Ansible — o cuando necesitas comentarios en estructuras muy anidadas y jerarquías profundas donde TOML se vuelve verboso.
# config_example.py
# Requiere Python 3.11+ para tomllib; instalar tomli_w para escritura:
# pip install tomli_w
import tomllib
import tomli_w
import yaml # pip install PyYAML
from pathlib import Path
# ── Lectura de TOML ──────────────────────────────────────────────────
def load_toml_config(path: Path) -> dict:
# tomllib.load requiere modo binario ("rb"), no texto
with open(path, "rb") as f:
return tomllib.load(f)
# ── Escritura de TOML (librería de terceros) ─────────────────────────
def save_toml_config(data: dict, path: Path) -> None:
# tomli_w.dumps devuelve str; tomli_w.dump escribe directamente en binario
with open(path, "wb") as f:
tomli_w.dump(data, f)
# ── Lectura de YAML ──────────────────────────────────────────────────
def load_yaml_config(path: Path) -> dict:
with open(path, "r", encoding="utf-8") as f:
# safe_load restringe el parser a tipos YAML básicos,
# bloqueando la construcción de objetos Python arbitrarios
return yaml.safe_load(f)
# ── Demostración ─────────────────────────────────────────────────────
def main() -> None:
# Configuración típica de una herramienta
config = {
"tool": {
"name": "data-pipeline",
"version": "2.1.0",
"workers": 4,
"debug": False,
},
"database": {
"host": "localhost",
"port": 5432,
"timeout": 30.0,
},
"tags": ["production", "eu-west"],
}
toml_path = Path("config.toml")
yaml_path = Path("config.yaml")
# Escribir y releer TOML
save_toml_config(config, toml_path)
toml_data = load_toml_config(toml_path)
print("TOML — port:", toml_data["database"]["port"])
print("TOML — port type:", type(toml_data["database"]["port"])) # <class 'int'>
# Escribir YAML manualmente para ilustrar el problema de tipos
yaml_path.write_text(
"database:\n port: 5432\n enabled: yes\n ratio: 1.0\n",
encoding="utf-8",
)
yaml_data = load_yaml_config(yaml_path)
print("YAML — port:", yaml_data["database"]["port"])
print("YAML — port type:", type(yaml_data["database"]["port"])) # <class 'int'>
print("YAML — enabled:", yaml_data["database"]["enabled"]) # True
print("YAML — enabled type:", type(yaml_data["database"]["enabled"])) # <class 'bool'>
# Limpieza
toml_path.unlink()
yaml_path.unlink()
if __name__ == "__main__":
main()
Lo que hace cada decisión
tomllib.load exige modo binario ("rb") porque TOML está definido en UTF-8 y la librería maneja la decodificación internamente — si abres en modo texto, obtienes un TypeError inmediato. Es una decisión de diseño que evita ambigüedades de encoding.
La separación tomllib (stdlib, solo lectura) + tomli_w (terceros, escritura) puede parecer incómoda, pero refleja una realidad del ecosistema: leer configuración en runtime es casi universal, escribirla programáticamente es mucho menos común. La stdlib cubre el 95% del caso real.
Fíjate en el output de enabled: yes con YAML: safe_load lo convierte a True booleano, no a la cadena "yes". Eso está en la spec de YAML 1.1, que es lo que implementa PyYAML. Es correcto según el estándar, pero si vienes de JSON o TOML te puede sorprender. YAML también interpreta on, off, no como booleanos. TOML no tiene estos casos: solo true y false son booleanos, sin alternativas.
El comportamiento de tipos en TOML es predecible porque el formato requiere que los strings vayan entre comillas. En YAML, el parser infiere el tipo por el valor, lo que crea la colección de “Norway problem” — NO como código de país se convierte en False. TOML no tiene este problema estructuralmente.
Errores que debes conocer
Error: Usar yaml.load() sin Loader explícito permite la deserialización de objetos Python arbitrarios, lo que convierte cualquier archivo YAML no confiable en ejecución de código remoto.
# ❌ Wrong
import yaml
data = yaml.load(open("config.yaml")) # DeprecationWarning en versiones recientes, RCE en versiones viejas
# ✅ Right
import yaml
data = yaml.safe_load(open("config.yaml"))
safe_load restringe el parser a los tipos escalares de YAML (strings, números, booleanos, listas, dicts) y lanza yaml.constructor.ConstructorError si el documento intenta construir objetos Python.
Error: Abrir un archivo TOML en modo texto con tomllib provoca un TypeError porque la función espera un objeto binario con método .read().
# ❌ Wrong
with open("config.toml", "r") as f:
data = tomllib.load(f) # TypeError: file must be opened in binary mode
# ✅ Right
with open("config.toml", "rb") as f:
data = tomllib.load(f)
El modo binario es obligatorio por diseño: tomllib gestiona internamente la decodificación UTF-8 con mejor manejo de errores que dejar esa responsabilidad al open() del caller.
Error: En Python < 3.11, tomllib no existe en stdlib. Importarlo directamente rompe la compatibilidad con versiones anteriores sin ningún mensaje de error útil hasta runtime.
# ❌ Wrong — falla en Python 3.10 y anteriores
import tomllib
# ✅ Right — compatible desde Python 3.9+
try:
import tomllib # stdlib desde 3.11
except ImportError:
import tomli as tomllib # pip install tomli — API idéntica
tomli es el backport oficial con API idéntica a tomllib; de hecho, tomllib en stdlib es básicamente tomli integrado.
N° 187