En muchos casos tu aplicación podría necesitar algunas configuraciones o parámetros externos, por ejemplo claves secretas, credenciales de base de datos, credenciales para servicios de email, etc.
La mayoría de estas configuraciones son variables (pueden cambiar), como las URLs de base 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, y 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 que vienen de variables de entorno con Pydantic: Gestión de Settings.
Importa BaseSettings de Pydantic y crea una sub-clase, muy parecido a como lo harías con un modelo de Pydantic.
De la misma manera que con los modelos de Pydantic, declaras atributos de clase con anotaciones de tipo, y posiblemente valores por defecto.
Puedes usar todas las mismas funcionalidades y herramientas de validación 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 de abajo.
Luego, cuando creas una instancia de esa clase Settings (en este caso, en el objeto settings), Pydantic leerá las variables de entorno de manera insensible a mayúsculas y minúsculas, así, una variable en mayúsculas APP_NAME seguirá siendo leída para el atributo app_name.
Luego convertirá y validará los datos. Así, cuando uses ese objeto settings, tendrás datos de los tipos que declaraste (ej. 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:
ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py
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 usa 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.
Luego sería muy fácil proporcionar un objeto de configuración diferente durante las pruebas creando una sobrescritura de dependencia para get_settings:
En la sobrescritura de dependencia establecemos un nuevo valor para admin_email al crear el nuevo objeto Settings, y luego devolvemos ese nuevo objeto.
Si tienes muchas configuraciones que posiblemente cambian mucho, quizás en diferentes entornos, podría ser útil ponerlas en un archivo y luego leerlas desde él como si fueran variables de entorno.
Esta práctica es lo suficientemente común que tiene un nombre, estas variables de entorno se colocan comúnmente en un archivo .env, y el archivo se llama "dotenv".
Consejo
Un archivo que empieza con un punto (.) es un archivo oculto en sistemas tipo Unix, como Linux y macOS.
Pero un archivo dotenv no tiene realmente que tener ese nombre exacto.
Aquí definimos la configuración env_file dentro de tu clase Settings de Pydantic, y establecemos el valor al nombre del archivo dotenv que queremos usar.
Leer un archivo del disco es normalmente una operación costosa (lenta), así que probablemente quieras hacerlo solo una vez y luego reutilizar el mismo objeto de configuración, en lugar de leerlo para cada petición.
Pero cada vez que hacemos:
Settings()
se crearía un nuevo objeto Settings, y al crearlo leería el archivo .env de nuevo.
Si la función de dependencia fuera simplemente:
defget_settings():returnSettings()
crearíamos ese objeto para cada petición, y estaríamos leyendo el archivo .env para cada petición. ⚠️
Pero como estamos usando el decorador @lru_cache encima, el objeto Settings se creará solo una vez, la primera vez que se llame. ✔️
Luego para cualquier llamada subsiguiente de get_settings() en las dependencias para las siguientes peticiones, 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 fue devuelto la primera vez, en lugar de calcularlo de nuevo, ejecutando el código de la función cada vez.
Así, 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 usarán una y otra vez siempre que se llame a la función con exactamente la misma combinación de argumentos.
En el caso de nuestra dependencia get_settings(), la función ni siquiera toma ningún argumento, así que siempre devuelve el mismo valor.
De esa manera, se comporta casi como si fuera una variable global. Pero como usa una función de dependencia, entonces podemos sobrescribirla fácilmente para las pruebas.