tomllib, disponible en la biblioteca estándar desde Python 3.11, lee TOML 1.0 y devuelve diccionarios, listas y tipos de fecha. Permite inspeccionar configuración y pyproject.toml sin otra dependencia.
from pathlib import Path
import tomllib
def cargar_config(ruta: Path) -> dict[str, object]:
if ruta.stat().st_size > 1_000_000:
raise ValueError("archivo de configuración demasiado grande")
with ruta.open("rb") as archivo:
datos = tomllib.load(archivo)
app = datos.get("app")
if not isinstance(app, dict) or not isinstance(app.get("puerto"), int):
raise ValueError("app.puerto es obligatorio")
return datos
Un parsing correcto confirma la sintaxis, no el contrato. Valida tablas, tipos, límites y campos desconocidos. Captura tomllib.TOMLDecodeError sin exponer contenido sensible.
Usa decimal.Decimal con parse_float cuando importe la precisión. La guía de pyproject.toml explica las tablas habituales.
tomllib no escribe ni conserva comentarios. No esperes edición round-trip a partir del diccionario.
La documentación oficial de tomllib, consultada el 22 de julio de 2026, describe conversiones y recomienda limitar el tamaño de entrada no confiable. Separa parsing, validación y aplicación.
Elegir load() o loads()
tomllib.load() lee un archivo binario; tomllib.loads() recibe una cadena:
with open("pyproject.toml", "rb") as archivo:
proyecto = tomllib.load(archivo)
fragmento = tomllib.loads("""
[servidor]
host = "127.0.0.1"
puerto = 8080
""")
TOML usa UTF-8. Abre con "rb" para load(), como exige la API. Si otra fuente ya decodificó el documento, usa loads(). Rechaza entradas grandes antes del parsing cuando no sean fiables, pues las estructuras profundas consumen CPU y memoria.
La sintaxis inválida genera tomllib.TOMLDecodeError. Su mensaje ayuda al desarrollador, pero puede revelar contenido sensible. Tradúcelo en la frontera:
def leer_toml(ruta: Path) -> dict[str, object]:
try:
with ruta.open("rb") as archivo:
return tomllib.load(archivo)
except tomllib.TOMLDecodeError as exc:
raise ValueError(f"TOML inválido en {ruta.name}") from exc
No conviertas toda excepción en “TOML inválido”. FileNotFoundError, PermissionError y fallos de I/O describen problemas operativos diferentes.
Conocer los tipos convertidos
Las cadenas se convierten en str, enteros en int, booleanos en bool, arrays en listas y tablas en diccionarios. Fechas y horas locales se convierten en date, time o datetime ingenuo; las fechas con offset producen datetime consciente. No supongas que todo valor temporal es una cadena.
from datetime import datetime
datos = tomllib.loads("""
lanzamiento = 2026-08-15T15:00:00Z
mantenimiento = 2026-08-16
""")
lanzamiento = datos["lanzamiento"]
if not isinstance(lanzamiento, datetime) or lanzamiento.tzinfo is None:
raise ValueError("lanzamiento necesita offset")
Los floats normalmente se convierten en float. Usa parse_float para semántica decimal:
from decimal import Decimal
precios = tomllib.loads(
'mensualidad = 19.90',
parse_float=Decimal,
)
assert precios["mensualidad"] == Decimal("19.90")
El callable recibe el token TOML original y no puede devolver diccionario ni lista. Afecta floats, no enteros.
Validar el esquema después
TOML válido puede ser configuración inválida. Comprueba tablas obligatorias, tipos exactos, rangos, opciones incompatibles y claves desconocidas. Como bool es subclase de int, isinstance(True, int) es verdadero; a veces necesitas type(valor) is int.
from dataclasses import dataclass
@dataclass(frozen=True)
class ConfigServidor:
host: str
puerto: int
debug: bool
def validar_servidor(datos: dict[str, object]) -> ConfigServidor:
tabla = datos.get("servidor")
if not isinstance(tabla, dict):
raise ValueError("tabla [servidor] obligatoria")
desconocidas = set(tabla) - {"host", "puerto", "debug"}
if desconocidas:
raise ValueError(f"campos desconocidos: {sorted(desconocidas)}")
host = tabla.get("host")
puerto = tabla.get("puerto")
debug = tabla.get("debug", False)
if not isinstance(host, str) or not host:
raise ValueError("host debe ser cadena no vacía")
if type(puerto) is not int or not 1 <= puerto <= 65535:
raise ValueError("puerto debe estar entre 1 y 65535")
if type(debug) is not bool:
raise ValueError("debug debe ser booleano")
return ConfigServidor(host, puerto, debug)
Convertir el diccionario en objeto inmutable concentra validación e impide que código distante dependa de claves sin comprobar. Las bibliotecas de esquema ayudan en contratos grandes, pero tomllib no realiza esta tarea.
Leer pyproject.toml defensivamente
La tabla [project] está estandarizada por la especificación de empaquetado; los ajustes propios viven bajo [tool.<nombre>]. El archivo puede omitir [project] si otro mecanismo aporta metadatos, así que una herramienta debe informar la ausencia.
Las claves con puntos tienen significado TOML salvo cuando están entre comillas. Examina la estructura analizada, no deduzcas por el texto. Los arrays de tablas son listas de diccionarios; valida cada elemento.
La configuración puede controlar imports, archivos, destinos de red o comandos. El parsing no vuelve seguros esos valores. Aplica contención de rutas, políticas URL y listas permitidas cuando el valor gane poder. Nunca ejecutes expresiones Python obtenidas de TOML.
Combinar fuentes explícitamente
Muchas aplicaciones combinan valores predeterminados, TOML, entorno y argumentos. Documenta la precedencia y valida el resultado final. Un dict.update() superficial puede sustituir una tabla completa; un merge recursivo genérico puede crear combinaciones no previstas. Construye campo a campo el objeto tipado.
No registres el diccionario entero: puede contener credenciales y endpoints privados. Registra qué fuente se cargó y solo modos no secretos. Distingue archivo opcional ausente de archivo obligatorio ilegible.
Respetar el límite de solo lectura
tomllib no serializa TOML ni conserva comentarios, espacios, comillas o presentación. Sus diccionarios sirven para consumir valores, no para edición round-trip. Usa un writer para archivos nuevos y un editor que conserve estilo para modificar archivos humanos. No reescribas pyproject.toml desde el diccionario esperando mantener el original.
Prueba entrada mínima, configuración completa, sintaxis rota, tablas ausentes, tipos erróneos, campos desconocidos, límites, Unicode, variantes temporales y rechazo por tamaño. Mantén cada fixture enfocada.
El flujo fiable es limitar y leer, interpretar TOML, validar el contrato, convertir a valores tipados y solo entonces aplicar. Esta separación produce errores precisos y evita confundir sintaxis aceptada con autorización o validez de negocio.
Planificar la evolución
La configuración es una interfaz y necesita compatibilidad. Al renombrar una clave, decide si aceptas la forma antigua temporalmente, emites aviso de desuso o la rechazas con instrucciones de migración. No elijas en silencio entre claves conflictivas. Si varias versiones comparten archivo, documenta la versión mínima para cada opción.
Un campo config_version ayuda en cambios estructurales, pero no sustituye validación. Interpreta primero la versión, envía al validador correspondiente y convierte estructuras antiguas en un único objeto de dominio actual. Prueba todas las versiones admitidas y el mensaje para las demás.
Los valores predeterminados pertenecen al código si forman parte del comportamiento; los ejemplos, a archivos de muestra documentados. Copiar una muestra a producción no debe activar placeholders de secretos que parezcan válidos. Exige que las credenciales lleguen mediante el mecanismo persistente aprobado, sin fallback inseguro.
Si la configuración se recarga durante la ejecución, interpreta y valida un candidato completo antes de sustituir el estado activo. Una aplicación parcial puede dejar componentes en desacuerdo. Cambia un objeto inmutable atómicamente cuando sea posible, conserva el último valor válido tras un fallo y emite diagnóstico redactado.
Los eventos del observador pueden repetirse o llegar mientras el editor sustituye el archivo. Agrúpalos y reintenta solo errores transitorios comprendidos. No uses sintaxis rota como motivo para vaciar la configuración.
Revisar seguridad y mantenimiento
Define permisos para que solo la cuenta de despliegue escriba y la aplicación lea. tomllib no impide una modificación maliciosa. Cuando el riesgo lo exija, verifica origen, propiedad e integridad antes de aplicar.
Al retirar una opción, busca su uso en muestras, documentación y archivos desplegados. Los mensajes deben citar la clave y la acción necesaria sin imprimir el valor. Para listas y tablas extensas, incluye el índice del elemento inválido.
En revisión de código, confirma límite de tamaño, modo binario, tratamiento específico de errores, campos desconocidos, rangos y comportamiento temporal. Comprueba también que valores poderosos reciben controles en el punto de uso.
Documenta estas decisiones junto al contrato de la aplicación. Así, una futura opción conserva la misma disciplina de validación y no introduce una ruta lateral que evite controles existentes.