Configuraciones y Variables de Entorno¶
🌐 Traducción por IA y humanos
Esta traducción fue hecha por IA guiada por humanos. 🤝
Podría tener errores al interpretar el significado original, o sonar poco natural, etc. 🤖
Puedes mejorar esta traducciónayudándonos a guiar mejor al LLM de IA.
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.
Consejo
Para entender las variables de entorno, puedes leerVariables de Entorno.
Tipos y validació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á unstr, y cualquier conversión a un tipo diferente o cualquier validación tiene que hacerse en código.
PydanticSettings¶
Afortunadamente, Pydantic proporciona una gran utilidad para manejar estas configuraciones provenientes de variables de entorno conPydantic: Settings management.
Instalarpydantic-settings¶
Primero, asegúrate de crear tuentorno virtual, actívalo y luego instala el paquetepydantic-settings:
$pipinstallpydantic-settings---> 100%También viene incluido cuando instalas los extrasall con:
$pipinstall"fastapi[all]"---> 100%Crear el objetoSettings¶
ImportaBaseSettings 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 conField().
fromfastapiimportFastAPIfrompydantic_settingsimportBaseSettingsclassSettings(BaseSettings):app_name:str="Awesome API"admin_email:stritems_per_user:int=50settings=Settings()app=FastAPI()@app.get("/info")asyncdefinfo():return{"app_name":settings.app_name,"admin_email":settings.admin_email,"items_per_user":settings.items_per_user,}Consejo
Si quieres algo rápido para copiar y pegar, no uses este ejemplo, usa el último más abajo.
Luego, cuando creas un instance de esa claseSettings (en este caso, en el objetosettings), Pydantic leerá las variables de entorno de una manera indiferente a mayúsculas y minúsculas, por lo que una variable en mayúsculasAPP_NAME aún será leída para el atributoapp_name.
Luego convertirá y validará los datos. Así que, cuando uses ese objetosettings, tendrás datos de los tipos que declaraste (por ejemplo,items_per_user será unint).
Usar elsettings¶
Luego puedes usar el nuevo objetosettings en tu aplicación:
fromfastapiimportFastAPIfrompydantic_settingsimportBaseSettingsclassSettings(BaseSettings):app_name:str="Awesome API"admin_email:stritems_per_user:int=50settings=Settings()app=FastAPI()@app.get("/info")asyncdefinfo():return{"app_name":settings.app_name,"admin_email":settings.admin_email,"items_per_user":settings.items_per_user,}Ejecutar el servidor¶
Luego, ejecutarías el servidor pasando las configuraciones como variables de entorno, por ejemplo, podrías establecer unADMIN_EMAIL yAPP_NAME con:
$ADMIN_EMAIL="deadpool@example.com"APP_NAME="ChimichangApp"fastapirunmain.py<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)Consejo
Para establecer múltiples env vars para un solo comando, simplemente sepáralas con un espacio y ponlas todas antes del comando.
Y luego la configuraciónadmin_email se establecería en"deadpool@example.com".
Elapp_name sería"ChimichangApp".
Y elitems_per_user mantendría su valor por defecto de50.
Configuraciones en otro módulo¶
Podrías poner esas configuraciones en otro archivo de módulo como viste enAplicaciones Más Grandes - Múltiples Archivos.
Por ejemplo, podrías tener un archivoconfig.py con:
frompydantic_settingsimportBaseSettingsclassSettings(BaseSettings):app_name:str="Awesome API"admin_email:stritems_per_user:int=50settings=Settings()Y luego usarlo en un archivomain.py:
fromfastapiimportFastAPIfrom.configimportsettingsapp=FastAPI()@app.get("/info")asyncdefinfo():return{"app_name":settings.app_name,"admin_email":settings.admin_email,"items_per_user":settings.items_per_user,}Consejo
También necesitarías un archivo__init__.py como viste enAplicaciones Más Grandes - Múltiples Archivos.
Configuraciones en una dependencia¶
En algunas ocasiones podría ser útil proporcionar las configuraciones desde una dependencia, en lugar de tener un objeto global consettings 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.
El archivo de configuración¶
Proveniente del ejemplo anterior, tu archivoconfig.py podría verse como:
frompydantic_settingsimportBaseSettingsclassSettings(BaseSettings):app_name:str="Awesome API"admin_email:stritems_per_user:int=50🤓 Other versions and variants
Tip
Prefer to use theAnnotated version if possible.
frompydantic_settingsimportBaseSettingsclassSettings(BaseSettings):app_name:str="Awesome API"admin_email:stritems_per_user:int=50Nota que ahora no creamos un instance por defectosettings = Settings().
El archivo principal de la app¶
Ahora creamos una dependencia que devuelve un nuevoconfig.Settings().
fromfunctoolsimportlru_cachefromtypingimportAnnotatedfromfastapiimportDepends,FastAPIfrom.configimportSettingsapp=FastAPI()@lru_cachedefget_settings():returnSettings()@app.get("/info")asyncdefinfo(settings:Annotated[Settings,Depends(get_settings)]):return{"app_name":settings.app_name,"admin_email":settings.admin_email,"items_per_user":settings.items_per_user,}🤓 Other versions and variants
Tip
Prefer to use theAnnotated version if possible.
fromfunctoolsimportlru_cachefromfastapiimportDepends,FastAPIfrom.configimportSettingsapp=FastAPI()@lru_cachedefget_settings():returnSettings()@app.get("/info")asyncdefinfo(settings:Settings=Depends(get_settings)):return{"app_name":settings.app_name,"admin_email":settings.admin_email,"items_per_user":settings.items_per_user,}Consejo
Hablaremos del@lru_cache en un momento.
Por ahora puedes asumir queget_settings() es una función normal.
Y luego podemos requerirlo desde lapath operation function como una dependencia y usarlo donde lo necesitemos.
fromfunctoolsimportlru_cachefromtypingimportAnnotatedfromfastapiimportDepends,FastAPIfrom.configimportSettingsapp=FastAPI()@lru_cachedefget_settings():returnSettings()@app.get("/info")asyncdefinfo(settings:Annotated[Settings,Depends(get_settings)]):return{"app_name":settings.app_name,"admin_email":settings.admin_email,"items_per_user":settings.items_per_user,}🤓 Other versions and variants
Tip
Prefer to use theAnnotated version if possible.
fromfunctoolsimportlru_cachefromfastapiimportDepends,FastAPIfrom.configimportSettingsapp=FastAPI()@lru_cachedefget_settings():returnSettings()@app.get("/info")asyncdefinfo(settings:Settings=Depends(get_settings)):return{"app_name":settings.app_name,"admin_email":settings.admin_email,"items_per_user":settings.items_per_user,}Configuraciones y pruebas¶
Luego sería muy fácil proporcionar un objeto de configuraciones diferente durante las pruebas al crear una sobrescritura de dependencia paraget_settings:
fromfastapi.testclientimportTestClientfrom.configimportSettingsfrom.mainimportapp,get_settingsclient=TestClient(app)defget_settings_override():returnSettings(admin_email="testing_admin@example.com")app.dependency_overrides[get_settings]=get_settings_overridedeftest_app():response=client.get("/info")data=response.json()assertdata=={"app_name":"Awesome API","admin_email":"testing_admin@example.com","items_per_user":50,}🤓 Other versions and variants
Tip
Prefer to use theAnnotated version if possible.
fromfastapi.testclientimportTestClientfrom.configimportSettingsfrom.mainimportapp,get_settingsclient=TestClient(app)defget_settings_override():returnSettings(admin_email="testing_admin@example.com")app.dependency_overrides[get_settings]=get_settings_overridedeftest_app():response=client.get("/info")data=response.json()assertdata=={"app_name":"Awesome API","admin_email":"testing_admin@example.com","items_per_user":50,}En la sobrescritura de dependencia establecemos un nuevo valor para eladmin_email al crear el nuevo objetoSettings, y luego devolvemos ese nuevo objeto.
Luego podemos probar que se está usando.
Leer un archivo.env¶
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.
Pydantic tiene soporte para leer desde estos tipos de archivos usando un paquete externo. Puedes leer más enPydantic Settings: Dotenv (.env) support.
Consejo
Para que esto funcione, necesitaspip install python-dotenv.
El archivo.env¶
Podrías tener un archivo.env con:
ADMIN_EMAIL="deadpool@example.com"APP_NAME="ChimichangApp"Leer configuraciones desde.env¶
Y luego actualizar tuconfig.py con:
frompydantic_settingsimportBaseSettings,SettingsConfigDictclassSettings(BaseSettings):app_name:str="Awesome API"admin_email:stritems_per_user:int=50model_config=SettingsConfigDict(env_file=".env")🤓 Other versions and variants
Tip
Prefer to use theAnnotated version if possible.
frompydantic_settingsimportBaseSettings,SettingsConfigDictclassSettings(BaseSettings):app_name:str="Awesome API"admin_email:stritems_per_user:int=50model_config=SettingsConfigDict(env_file=".env")Consejo
El atributomodel_config se usa solo para configuración de Pydantic. Puedes leer más enPydantic: Concepts: Configuration.
Aquí definimos la configuraciónenv_file dentro de tu clase PydanticSettings, y establecemos el valor en el nombre del archivo con el archivo dotenv que queremos usar.
Creando elSettings solo una vez conlru_cache¶
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 objetoSettings, 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 objetoSettings se creará solo una vez, la primera vez que se llame. ✔️
fromfunctoolsimportlru_cachefromtypingimportAnnotatedfromfastapiimportDepends,FastAPIfrom.importconfigapp=FastAPI()@lru_cachedefget_settings():returnconfig.Settings()@app.get("/info")asyncdefinfo(settings:Annotated[config.Settings,Depends(get_settings)]):return{"app_name":settings.app_name,"admin_email":settings.admin_email,"items_per_user":settings.items_per_user,}🤓 Other versions and variants
Tip
Prefer to use theAnnotated version if possible.
fromfunctoolsimportlru_cachefromfastapiimportDepends,FastAPIfrom.importconfigapp=FastAPI()@lru_cachedefget_settings():returnconfig.Settings()@app.get("/info")asyncdefinfo(settings:config.Settings=Depends(get_settings)):return{"app_name":settings.app_name,"admin_email":settings.admin_email,"items_per_user":settings.items_per_user,}Entonces, para cualquier llamada subsiguiente deget_settings() en las dependencias de los próximos requests, en lugar de ejecutar el código interno deget_settings() y crear un nuevo objetoSettings, devolverá el mismo objeto que fue devuelto en la primera llamada, una y otra vez.
Detalles Técnicos delru_cache¶
@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.
Por ejemplo, si tienes una función:
@lru_cachedefsay_hi(name:str,salutation:str="Ms."):returnf"Hello{salutation}{name}"tu programa podría ejecutarse así:
sequenceDiagramparticipant code as Códigoparticipant function as say_hi()participant execute as Ejecutar función rect rgba(0, 255, 0, .1) code ->> function: say_hi(name="Camila") function ->> execute: ejecutar código de la función execute ->> code: devolver el resultado end rect rgba(0, 255, 255, .1) code ->> function: say_hi(name="Camila") function ->> code: devolver resultado almacenado end rect rgba(0, 255, 0, .1) code ->> function: say_hi(name="Rick") function ->> execute: ejecutar código de la función execute ->> code: devolver el resultado end rect rgba(0, 255, 0, .1) code ->> function: say_hi(name="Rick", salutation="Mr.") function ->> execute: ejecutar código de la función execute ->> code: devolver el resultado end rect rgba(0, 255, 255, .1) code ->> function: say_hi(name="Rick") function ->> code: devolver resultado almacenado end rect rgba(0, 255, 255, .1) code ->> function: say_hi(name="Camila") function ->> code: devolver resultado almacenado endEn el caso de nuestra dependenciaget_settings(), la función ni siquiera toma argumentos, por lo que siempre devuelve 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 defunctools, que es parte del paquete estándar de Python, puedes leer más sobre él en lasdocs de Python para@lru_cache.
Resumen¶
Puedes usar Pydantic Settings para manejar las configuraciones o ajustes de tu aplicación, con todo el poder de los modelos de Pydantic.
- Al usar una dependencia, puedes simplificar las pruebas.
- Puedes usar archivos
.envcon él. - Usar
@lru_cachete permite evitar leer el archivo dotenv una y otra vez para cada request, mientras te permite sobrescribirlo durante las pruebas.







