En muchos casos, tu aplicación podría necesitar algunas configuraciones o ajustes externos, por ejemplo, claves secretas, credenciales de base de datos, credenciales para servicios de correo electrónico, etc.
La mayoría de estas configuraciones son variables (pueden cambiar), como las URLs de bases de datos. Y muchas podrían ser sensibles, como los secretos.
Por esta razón, es común proporcionarlas en variables de entorno que son leídas por la aplicación.
Estas variables de entorno solo pueden manejar strings de texto, ya que son externas a Python y tienen que ser compatibles con otros programas y el resto del sistema (e incluso con diferentes sistemas operativos, como Linux, Windows, macOS).
Eso significa que cualquier valor leído en Python desde una variable de entorno será un str, y cualquier conversión a un tipo diferente o cualquier validación tiene que hacerse en código.
Afortunadamente, Pydantic proporciona una gran utilidad para manejar estas configuraciones provenientes de variables de entorno con Pydantic: Settings management.
En Pydantic v1 venía incluido con el paquete principal. Ahora se distribuye como este paquete independiente para que puedas elegir si instalarlo o no si no necesitas esa funcionalidad.
Importa BaseSettings de Pydantic y crea una sub-clase, muy similar a un modelo de Pydantic.
De la misma forma que con los modelos de Pydantic, declaras atributos de clase con anotaciones de tipos, y posiblemente, valores por defecto.
Puedes usar todas las mismas funcionalidades de validación y herramientas que usas para los modelos de Pydantic, como diferentes tipos de datos y validaciones adicionales con Field().
Si quieres algo rápido para copiar y pegar, no uses este ejemplo, usa el último más abajo.
Luego, cuando creas una instance de esa clase Settings (en este caso, en el objeto settings), Pydantic leerá las variables de entorno de una manera indiferente a mayúsculas y minúsculas, por lo que una variable en mayúsculas APP_NAME aún será leída para el atributo app_name.
Luego convertirá y validará los datos. Así que, cuando uses ese objeto settings, tendrás datos de los tipos que declaraste (por ejemplo, items_per_user será un int).
Luego, ejecutarías el servidor pasando las configuraciones como variables de entorno, por ejemplo, podrías establecer un ADMIN_EMAIL y APP_NAME con:
fast →ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
En algunas ocasiones podría ser útil proporcionar las configuraciones desde una dependencia, en lugar de tener un objeto global con settings que se use en todas partes.
Esto podría ser especialmente útil durante las pruebas, ya que es muy fácil sobrescribir una dependencia con tus propias configuraciones personalizadas.
Si tienes muchas configuraciones que posiblemente cambien mucho, tal vez en diferentes entornos, podría ser útil ponerlos en un archivo y luego leerlos desde allí como si fueran variables de entorno.
Esta práctica es lo suficientemente común que tiene un nombre, estas variables de entorno generalmente se colocan en un archivo .env, y el archivo se llama un "dotenv".
Consejo
Un archivo que comienza con un punto (.) es un archivo oculto en sistemas tipo Unix, como Linux y macOS.
Pero un archivo dotenv realmente no tiene que tener ese nombre exacto.
La clase Config se usa solo para configuración de Pydantic. Puedes leer más en Pydantic Model Config.
Información
En la versión 1 de Pydantic la configuración se hacía en una clase interna Config, en la versión 2 de Pydantic se hace en un atributo model_config. Este atributo toma un dict, y para obtener autocompletado y errores en línea, puedes importar y usar SettingsConfigDict para definir ese dict.
Aquí definimos la configuración env_file dentro de tu clase Pydantic Settings, y establecemos el valor en el nombre del archivo con el archivo dotenv que queremos usar.
Leer un archivo desde el disco es normalmente una operación costosa (lenta), por lo que probablemente quieras hacerlo solo una vez y luego reutilizar el mismo objeto de configuraciones, en lugar de leerlo para cada request.
Pero cada vez que hacemos:
Settings()
se crearía un nuevo objeto Settings, y al crearse leería el archivo .env nuevamente.
Si la función de dependencia fuera simplemente así:
defget_settings():returnSettings()
crearíamos ese objeto para cada request, y estaríamos leyendo el archivo .env para cada request. ⚠️
Pero como estamos usando el decorador @lru_cache encima, el objeto Settings se creará solo una vez, la primera vez que se llame. ✔️
Entonces, para cualquier llamada subsiguiente de get_settings() en las dependencias de los próximos requests, en lugar de ejecutar el código interno de get_settings() y crear un nuevo objeto Settings, devolverá el mismo objeto que fue devuelto en la primera llamada, una y otra vez.
@lru_cache modifica la función que decora para devolver el mismo valor que se devolvió la primera vez, en lugar de calcularlo nuevamente, ejecutando el código de la función cada vez.
Así que la función debajo se ejecutará una vez por cada combinación de argumentos. Y luego, los valores devueltos por cada una de esas combinaciones de argumentos se utilizarán una y otra vez cada vez que la función sea llamada con exactamente la misma combinación de argumentos.
En el caso de nuestra dependencia get_settings(), la función ni siquiera toma argumentos, por lo que siempre devolverá el mismo valor.
De esa manera, se comporta casi como si fuera solo una variable global. Pero como usa una función de dependencia, entonces podemos sobrescribirla fácilmente para las pruebas.
@lru_cache es parte de functools, que es parte del library estándar de Python, puedes leer más sobre él en las docs de Python para @lru_cache.