Llamadas dispersas a os.getenv() producen strings sin validar y fallos tardíos. pydantic-settings centraliza opciones en un modelo tipado y detiene el inicio si falta configuración.
Aplica conceptos de tipado en Python, pero se instala como paquete separado:
python -m pip install pydantic-settings
from pydantic import SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
debug: bool = False
database_url: str
api_key: SecretStr
workers: int = 2
model_config = SettingsConfigDict(
env_file=".env", env_prefix="APP_", extra="ignore"
)
Espera variables como APP_DATABASE_URL y convierte tipos. Añade .env a .gitignore; SecretStr reduce exposición accidental, pero no cifra valores.
Usa el mismo modelo con valores diferentes por entorno. En producción, inyecta secretos desde la plataforma o un vault. La documentación oficial explica prioridad de fuentes.
Carga settings en un límite explícito e inyéctalos. En FastAPI, la inyección de dependencias facilita overrides. Nunca registres secretos, valida límites y falla al iniciar si falta un campo requerido.
Entender la prioridad de las fuentes
Por defecto, los valores pasados al constructor tienen prioridad sobre variables de entorno, que prevalecen sobre dotenv y después sobre archivos de secrets. Los valores predeterminados quedan al final. Así una prueba puede sobrescribir opciones sin modificar el entorno:
settings_prueba = Settings(
database_url="sqlite://",
api_key="clave-de-prueba",
workers=1,
)
No esperes que .env sustituya una variable ya exportada por el proceso. Normalmente gana el entorno. Es útil en containers y CI, pero puede sorprender al diagnosticar. Registra solo opciones seleccionadas que no sean sensibles y documenta su origen esperado.
La carga de dotenv no busca recursivamente en directorios padres. Una ruta relativa depende del directorio de trabajo del proceso. En un servicio, construye una ruta conocida desde la aplicación o deja que la plataforma proporcione todos los valores.
Modelos anidados y valores complejos
Listas, diccionarios y modelos internos pueden llegar como strings JSON. Un delimitador permite sobrescribir partes mediante variables separadas:
from pydantic import BaseModel
class BaseDatos(BaseModel):
host: str
port: int = 5432
pool_size: int = 10
class Settings(BaseSettings):
database: BaseDatos
model_config = SettingsConfigDict(
env_prefix="APP_",
env_nested_delimiter="__",
)
APP_DATABASE__HOST=db.internal y APP_DATABASE__POOL_SIZE=20 alimentan campos internos. El comportamiento de mayúsculas depende de la configuración y del sistema operativo. Estandariza los nombres y prueba en un entorno equivalente al de producción.
Usa validadores de Pydantic para restricciones adicionales. Un timeout puede exigir valor positivo y producción puede prohibir debug. Mantén la validación determinista y sin llamadas de red. Crear settings debe detectar configuración inválida, no depender de la disponibilidad momentánea de otro servicio.
Prefijos, aliases y nombres antiguos
env_prefix evita colisiones cuando varias aplicaciones comparten entorno. Los aliases permiten integrar nombres existentes, pero alias de validación, serialización y búsqueda tienen objetivos distintos. Confirma el efecto en la versión instalada y prueba el nombre real.
No aceptes varios nombres indefinidamente. Durante una migración, admite el antiguo de forma explícita, avisa a operaciones y define su retirada. La ambigüedad silenciosa complica incidentes.
En plataformas sensibles a mayúsculas, considera la grafía parte del contrato. No dependas solo del comportamiento de desarrollo en Windows si producción usa Linux.
Qué protege SecretStr
SecretStr oculta contenido en repr() y serializaciones comunes, pero el valor sigue en memoria y se obtiene con get_secret_value(). Revélalo únicamente al cliente que lo necesita:
cliente = ClienteExterno(
token=settings.api_key.get_secret_value()
)
No vuelques todo el objeto a logs. Una lista explícita de campos públicos es más segura. Directorios de secrets montados por un orquestador pueden actuar como fuente, pero permisos, rotación y persistencia pertenecen a la plataforma. Pydantic valida el valor; no administra su ciclo de vida.
Ten cuidado con secretos opcionales solo en desarrollo. Un campo str | None puede permitir que producción arranque sin protección. Un campo obligatorio con valor aislado para pruebas suele ser más seguro.
Cargar una vez sin ocultar dependencias
Construir settings repetidamente vuelve a leer fuentes y valida de nuevo. Una aplicación larga puede cargar una vez o usar caché:
from functools import lru_cache
@lru_cache
def get_settings() -> Settings:
return Settings()
Los consumidores deberían recibir Settings, o una sección menor, como argumento. Importar un singleton global en todos los módulos oculta dependencias y complica tests. Si una prueba cambia el entorno, limpia la caché o pasa una instancia creada directamente.
Las bibliotecas reutilizables no deberían leer el entorno al importarse. Deben recibir opciones de la aplicación anfitriona. Así evitan efectos laterales y permiten clientes con configuraciones distintas.
Informar errores de inicio con seguridad
Captura ValidationError en el punto de entrada, presenta un mensaje útil y termina con código distinto de cero. No continúes con configuración parcial. Una entrada inválida puede contener información sensible, por lo que conviene mostrar nombres de campos y tipos esperados, no todos los valores recibidos.
Prueba ausencia de campos obligatorios, conversiones correctas, valores fuera de límites y precedencia. En pytest, usa monkeypatch.setenv() y monkeypatch.delenv() para aislar el entorno, y crea el modelo después. Máquinas de desarrollo y runners pueden contener variables inesperadas.
Por último, trata la configuración como una interfaz versionada entre código y operaciones. Renombrar una variable puede romper el deploy aunque los tests unitarios pasen. Documenta nombres, tipos, obligatoriedad, ejemplos seguros y política de migración junto al proyecto.
Separar dominios operativos
Una aplicación grande no necesita un modelo plano con todas las opciones. Compón modelos para base de datos, correo, observabilidad y clientes externos. Después inyecta en cada componente solo la sección necesaria. Así disminuye el acceso accidental a secretos no relacionados y las pruebas son más claras.
Revisa cada valor predeterminado. Un nombre visible puede tener fallback; una URL de base de datos, una clave de cifrado o un host de producción normalmente no. Un valor cómodo podría conectar silenciosamente el servicio desplegado a localhost o desactivar una protección. Decide según las consecuencias del fallo, no solo por comodidad.
Personalizar fuentes con prudencia
Pydantic Settings permite cambiar la lista y prioridad de fuentes. Es útil para integrar un almacén corporativo o un formato heredado, pero aumenta la complejidad de inicio. Mantén la función de origen pequeña, determinista y cubierta por pruebas. Si consulta red, define claramente timeout, reintentos y comportamiento cuando el proveedor no está disponible.
No conviertas una fuente personalizada en un segundo sistema de configuración oculto. El modelo debe seguir mostrando campos y tipos, y la documentación debe explicar de dónde llega cada valor. Normaliza entradas en la frontera y deja que Pydantic aplique la validación.
Verificación durante el deploy
Incluye un smoke test que cree el modelo mediante el mecanismo real de inyección sin imprimir valores. Detectará nombres mal escritos, mounts ausentes y conversiones inválidas antes del tráfico. No basta con probar un .env local si producción utiliza variables o archivos montados.
Cuando cambie la configuración, actualiza en la misma modificación el archivo de ejemplo seguro, la documentación del deploy y las pruebas de precedencia. Mantén credenciales reales fuera del control de versiones. Si retiras una variable, observa primero que las instancias desplegadas ya usan su reemplazo.
Para diagnosticar, muestra el campo y la regla incumplida, pero no el valor sensible. Una buena salida permite que operaciones corrija el entorno sin revelar tokens en logs, alertas o sistemas de soporte.