设置和环境变量¶
在许多情况下,您的应用程序可能需要一些外部设置或配置,例如密钥、数据库凭据、电子邮件服务凭据等。
这些设置大多是可变的(可以更改),如数据库 URL。许多也可能是敏感的,如密钥。
因此,通常将它们作为环境变量提供给应用程序读取。
提示
要了解环境变量,您可以阅读环境变量。
类型和验证¶
这些环境变量只能处理文本字符串,因为它们独立于 Python,必须与其他程序和系统其余部分(甚至与 Linux、Windows、macOS 等不同操作系统)兼容。
这意味着从环境变量中读取的任何 Python 值都将是 str
类型,任何类型转换或验证都必须在代码中完成。
Pydantic Settings
¶
幸运的是,Pydantic 提供了一个出色的实用工具,可以使用 Pydantic:设置管理 来处理来自环境变量的这些设置。
安装 pydantic-settings
¶
首先,请确保创建您的虚拟环境,激活它,然后安装 pydantic-settings
包
$ pip install pydantic-settings
---> 100%
当您安装 all
额外功能时,它也包含在内:
$ pip install "fastapi[all]"
---> 100%
信息
在 Pydantic v1 中,它随主包一起提供。现在它作为这个独立的包分发,以便如果您不需要该功能,可以选择安装或不安装它。
创建 Settings
对象¶
从 Pydantic 导入 BaseSettings
并创建一个子类,这与 Pydantic 模型非常相似。
与 Pydantic 模型一样,您可以使用类型注释声明类属性,并可能设置默认值。
您可以使用所有与 Pydantic 模型相同的验证功能和工具,例如不同的数据类型以及使用 Field()
进行的额外验证。
from fastapi import FastAPI
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
app_name: str = "Awesome API"
admin_email: str
items_per_user: int = 50
settings = Settings()
app = FastAPI()
@app.get("/info")
async def info():
return {
"app_name": settings.app_name,
"admin_email": settings.admin_email,
"items_per_user": settings.items_per_user,
}
信息
在 Pydantic v1 中,您将直接从 pydantic
而不是从 pydantic_settings
导入 BaseSettings
。
from fastapi import FastAPI
from pydantic import BaseSettings
class Settings(BaseSettings):
app_name: str = "Awesome API"
admin_email: str
items_per_user: int = 50
settings = Settings()
app = FastAPI()
@app.get("/info")
async def info():
return {
"app_name": settings.app_name,
"admin_email": settings.admin_email,
"items_per_user": settings.items_per_user,
}
提示
如果您想快速复制粘贴,请不要使用此示例,请使用下面的最后一个示例。
然后,当您创建该 Settings
类的一个实例(在本例中,在 settings
对象中)时,Pydantic 将以不区分大小写的方式读取环境变量,因此,大写变量 APP_NAME
仍将被读取为属性 app_name
。
接下来,它将转换并验证数据。因此,当您使用该 settings
对象时,您将拥有您声明的类型的数据(例如,items_per_user
将是一个 int
)。
使用 settings
¶
然后您可以在应用程序中使用新的 settings
对象
from fastapi import FastAPI
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
app_name: str = "Awesome API"
admin_email: str
items_per_user: int = 50
settings = Settings()
app = FastAPI()
@app.get("/info")
async def info():
return {
"app_name": settings.app_name,
"admin_email": settings.admin_email,
"items_per_user": settings.items_per_user,
}
运行服务器¶
接下来,您将运行服务器,将配置作为环境变量传递,例如,您可以使用以下方式设置 ADMIN_EMAIL
和 APP_NAME
:
$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
提示
要为一个命令设置多个环境变量,只需用空格分隔它们,并将它们全部放在命令之前。
然后 admin_email
设置将被设置为 "deadpool@example.com"
。
app_name
将是 "ChimichangApp"
。
而 items_per_user
将保留其默认值 50
。
在另一个模块中设置¶
您可以将这些设置放在另一个模块文件中,正如您在大型应用程序 - 多个文件 中所看到的。
例如,您可以有一个 config.py
文件,内容如下:
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
app_name: str = "Awesome API"
admin_email: str
items_per_user: int = 50
settings = Settings()
然后在一个 main.py
文件中使用它:
from fastapi import FastAPI
from .config import settings
app = FastAPI()
@app.get("/info")
async def info():
return {
"app_name": settings.app_name,
"admin_email": settings.admin_email,
"items_per_user": settings.items_per_user,
}
提示
您还需要一个 __init__.py
文件,正如您在大型应用程序 - 多个文件 中所看到的。
在依赖项中设置¶
在某些情况下,从依赖项提供设置可能很有用,而不是使用一个在各处都使用的全局 settings
对象。
这在测试期间特别有用,因为使用您自己的自定义设置覆盖依赖项非常容易。
配置文件¶
从前面的示例来看,您的 config.py
文件可能如下所示:
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
app_name: str = "Awesome API"
admin_email: str
items_per_user: int = 50
请注意,现在我们不创建默认实例 settings = Settings()
。
主应用程序文件¶
现在我们创建一个依赖项,它返回一个新的 config.Settings()
。
from functools import lru_cache
from typing import Annotated
from fastapi import Depends, FastAPI
from .config import Settings
app = FastAPI()
@lru_cache
def get_settings():
return Settings()
@app.get("/info")
async def info(settings: Annotated[Settings, Depends(get_settings)]):
return {
"app_name": settings.app_name,
"admin_email": settings.admin_email,
"items_per_user": settings.items_per_user,
}
提示
我们稍后会讨论 @lru_cache
。
目前您可以假设 get_settings()
是一个普通函数。
然后我们可以从路径操作函数中将其作为依赖项进行请求,并在任何需要的地方使用它。
from functools import lru_cache
from typing import Annotated
from fastapi import Depends, FastAPI
from .config import Settings
app = FastAPI()
@lru_cache
def get_settings():
return Settings()
@app.get("/info")
async def info(settings: Annotated[Settings, Depends(get_settings)]):
return {
"app_name": settings.app_name,
"admin_email": settings.admin_email,
"items_per_user": settings.items_per_user,
}
设置和测试¶
然后,通过为 get_settings
创建一个依赖项覆盖,在测试期间提供不同的设置对象将非常容易:
from fastapi.testclient import TestClient
from .config import Settings
from .main import app, get_settings
client = TestClient(app)
def get_settings_override():
return Settings(admin_email="testing_admin@example.com")
app.dependency_overrides[get_settings] = get_settings_override
def test_app():
response = client.get("/info")
data = response.json()
assert data == {
"app_name": "Awesome API",
"admin_email": "testing_admin@example.com",
"items_per_user": 50,
}
在依赖项覆盖中,我们在创建新的 Settings
对象时为 admin_email
设置一个新值,然后返回该新对象。
然后我们可以测试它是否被使用。
读取 .env
文件¶
如果您有许多设置可能经常更改,也许在不同的环境中,将它们放在一个文件中,然后像读取环境变量一样从文件中读取它们可能会很有用。
这种做法非常普遍,以至于它有了一个名称,这些环境变量通常放在一个 .env
文件中,该文件被称为“dotenv”。
提示
以点(.
)开头的文件在类 Unix 系统(如 Linux 和 macOS)中是隐藏文件。
但 dotenv 文件实际上不必具有该确切的文件名。
Pydantic 支持使用外部库从这些类型的文件中读取。您可以在Pydantic Settings: Dotenv (.env) support 中阅读更多内容。
提示
为此,您需要 pip install python-dotenv
。
.env
文件¶
您可以有一个 .env
文件,内容如下:
ADMIN_EMAIL="deadpool@example.com"
APP_NAME="ChimichangApp"
从 .env
读取设置¶
然后更新您的 config.py
,内容如下:
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
app_name: str = "Awesome API"
admin_email: str
items_per_user: int = 50
model_config = SettingsConfigDict(env_file=".env")
提示
model_config
属性仅用于 Pydantic 配置。您可以在Pydantic:概念:配置 中阅读更多内容。
from pydantic import BaseSettings
class Settings(BaseSettings):
app_name: str = "Awesome API"
admin_email: str
items_per_user: int = 50
class Config:
env_file = ".env"
提示
Config
类仅用于 Pydantic 配置。您可以在Pydantic 模型配置 中阅读更多内容。
信息
在 Pydantic 版本 1 中,配置是在内部类 Config
中完成的;在 Pydantic 版本 2 中,则在属性 model_config
中完成。此属性接受一个 dict
,为了获得自动补全和内联错误,您可以导入并使用 SettingsConfigDict
来定义该 dict
。
在这里,我们在您的 Pydantic Settings
类中定义配置 env_file
,并将其值设置为我们想要使用的 dotenv 文件的文件名。
使用 lru_cache
只创建一次 Settings
¶
从磁盘读取文件通常是代价高昂(缓慢)的操作,因此您可能只想执行一次,然后重用相同的设置对象,而不是为每个请求都读取它。
但我们每次执行
Settings()
都会创建一个新的 Settings
对象,并且在创建时会再次读取 .env
文件。
如果依赖函数只是像这样:
def get_settings():
return Settings()
我们就会为每个请求创建该对象,并为每个请求读取 .env
文件。⚠️
但由于我们在顶部使用了 @lru_cache
装饰器,Settings
对象将只创建一次,即在第一次调用时。✔️
from functools import lru_cache
from fastapi import Depends, FastAPI
from typing_extensions import Annotated
from . import config
app = FastAPI()
@lru_cache
def get_settings():
return config.Settings()
@app.get("/info")
async def info(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,
}
然后,对于后续请求中 get_settings()
在依赖项中的任何后续调用,它将返回在第一次调用时返回的相同对象,而不是执行 get_settings()
的内部代码并创建新的 Settings
对象。
lru_cache
技术细节¶
@lru_cache
修改其装饰的函数,使其返回第一次返回的相同值,而不是每次都重新计算并执行函数代码。
因此,在其下方的函数将针对每种参数组合执行一次。然后,每当以完全相同的参数组合调用该函数时,由这些参数组合返回的值将一次又一次地被使用。
例如,如果您有一个函数:
@lru_cache
def say_hi(name: str, salutation: str = "Ms."):
return f"Hello {salutation} {name}"
您的程序可以这样执行:
sequenceDiagram
participant code as Code
participant function as say_hi()
participant execute as Execute function
rect rgba(0, 255, 0, .1)
code ->> function: say_hi(name="Camila")
function ->> execute: execute function code
execute ->> code: return the result
end
rect rgba(0, 255, 255, .1)
code ->> function: say_hi(name="Camila")
function ->> code: return stored result
end
rect rgba(0, 255, 0, .1)
code ->> function: say_hi(name="Rick")
function ->> execute: execute function code
execute ->> code: return the result
end
rect rgba(0, 255, 0, .1)
code ->> function: say_hi(name="Rick", salutation="Mr.")
function ->> execute: execute function code
execute ->> code: return the result
end
rect rgba(0, 255, 255, .1)
code ->> function: say_hi(name="Rick")
function ->> code: return stored result
end
rect rgba(0, 255, 255, .1)
code ->> function: say_hi(name="Camila")
function ->> code: return stored result
end
在我们的依赖项 get_settings()
的情况下,该函数甚至不接受任何参数,因此它总是返回相同的值。
这样,它的行为几乎就像一个全局变量。但由于它使用了一个依赖函数,因此我们可以轻松地在测试期间覆盖它。
@lru_cache
是 functools
的一部分,而 functools
又是 Python 标准库的一部分,您可以在 Python 关于 @lru_cache 的文档 中阅读更多内容。
总结¶
您可以使用 Pydantic Settings 来处理应用程序的设置或配置,并充分利用 Pydantic 模型的强大功能。
- 通过使用依赖项,您可以简化测试。
- 您可以将其与
.env
文件一起使用。 - 使用
@lru_cache
可以避免为每个请求反复读取 dotenv 文件,同时允许您在测试期间覆盖它。