Lectura y validación de settings.toml con Pydantic en apps de Python
Este módulo se encarga de leer settings.toml, analizarlo con tomllib, almacenar el resultado en caché mediante lru_cache y validar las secciones usando Pydantic. Mantiene la lógica de configuración separada de los parámetros de tu app, reduce lecturas repetidas al disco y garantiza seguridad de tipos.
from functools import lru_cache
from pathlib import Path
from tomllib import load
from typing import Any, Type, TypeVar
from pydantic import BaseModel, SecretStr
ConfigType = TypeVar("ConfigType", bound=BaseModel)
class BotConfig(BaseModel):
token: SecretStr
@lru_cache
def parse_config_file() -> dict[str, Any]:
config_path = Path(__file__).resolve().parent.parent.joinpath("settings.toml")
if not config_path.exists():
error = "No se pudo encontrar el archivo de configuración"
raise ValueError(error)
with open(config_path, "rb") as file:
config_data = load(file)
return config_data
def get_config(model: Type[ConfigType], root_key: str) -> ConfigType:
config_dict = parse_config_file()
if root_key not in config_dict:
error = f"La clave {root_key} no se encontró"
raise ValueError(error)
return model.model_validate(config_dict[root_key])
Por qué usar TOML y Pydantic para la configuración
Los archivos TOML son legibles por humanos y se parsean de forma limpia en diccionarios de Python. Las secciones como [bot] se convierten en diccionarios anidados. Pydantic valida la estructura, maneja conversiones de tipos y oculta secretos con SecretStr.
Ejemplo de TOML:
[bot]
token = "123456:ABCDEF"
El análisis produce {"bot": {"token": "123456:ABCDEF"}}. La validación crea un objeto BotConfig.
El caché con @lru_cache evita lecturas repetidas: la primera llamada parsea, las siguientes usan el caché. Recarga con parse_config_file.cache_clear().
Manejo de rutas con pathlib
Path(__file__).resolve().parent.parent.joinpath("settings.toml") construye una ruta absoluta al archivo en el directorio padre de tu proyecto. exists() verifica la existencia y las propiedades parent simplifican la validación.
Path.joinpath(): añade segmentos de ruta de forma multiplataforma.resolve(): resuelve enlaces simbólicos a rutas absolutas.- Abrir en
"rb": requerido portomllib.load()para lectura binaria.
Indicadores de tipo con TypeVar y Pydantic
ConfigType = TypeVar("ConfigType", bound=BaseModel) permite que get_config funcione con cualquier modelo Pydantic, devolviendo una instancia del tipo pasado.
Type[ConfigType]: espera una clase de modelo, no una instancia.model_validate(): convierte un diccionario en un objeto validado, lanzandoValidationErroren fallos.Anypara diccionarios: TOML mezcla tipos (str, int, bool, dict).
Extiéndelo para otras secciones:
class DatabaseConfig(BaseModel):
host: str
port: int
user: str
config = get_config(DatabaseConfig, "database")
Caché y decoradores
@lru_cache almacena resultados por argumentos (ninguno aquí, así que es simple). LRU elimina entradas antiguas al alcanzar el límite, pero una sola ranura basta para la configuración.
Ventajas:
- Reduce E/S: lectura al disco solo una vez.
- Idempotente: múltiples llamadas a
get_configcomparten un solo análisis. - Seguro para hilos: el decorador de la stdlib lo maneja.
Desventajas: Cambios en disco al TOML se ignoran hasta cache_clear().
Estructura del módulo
- Modelos (
BotConfig): independientes de los cargadores. parse_config_file(): analiza el TOML completo.get_config(): extrae y valida una sección.
Esta separación de responsabilidades mejora la testabilidad: simula rutas o cadenas TOML.
Consejos clave:
- Usa
tomllib(Python 3.11+), retrocompatible contomlipara versiones antiguas. - Valida siempre la configuración con Pydantic antes de usarla.
- Almacena el análisis en caché, pero añade soporte para recarga.
- Guarda secretos en
SecretStrpara ocultarlos en logs/debug. - Construye rutas con
pathlibpara compatibilidad multiplataforma.
— Editorial Team
Aún no hay comentarios.