设置与环境变量¶
在许多情况下,您的应用程序可能需要一些外部设置或配置,例如密钥、数据库凭据、电子邮件服务凭据等。
大多数这些设置是可变的(可能更改),比如数据库URL。其中许多可能是敏感的,比如密钥。
因此,通常的做法是通过应用程序读取环境变量来提供这些设置。
Tip
要了解环境变量,您可以阅读环境变量。
类型与验证¶
这些环境变量只能处理文本字符串,因为它们独立于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%
Info
在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,
}
Info
在Pydantic v1中,您可以直接从pydantic导入BaseSettings,而不是从pydantic_settings导入。
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,
}
Tip
如果您想要快速复制粘贴的内容,请不要使用此示例,使用下面的最后一个示例。
然后,当您创建该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)
Tip
要为单个命令设置多个环境变量,只需用空格分隔它们,并将它们全部放在命令之前。
然后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,
}
Tip
您还需要一个__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,
}
Tip
我们稍后将讨论@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”。
Tip
以点(.)开头的文件在类Unix系统(如Linux和macOS)中是隐藏文件。
但dotenv文件实际上不必具有该确切文件名。
Pydantic支持使用外部库从这些类型的文件中读取。您可以在Pydantic Settings: Dotenv (.env) support中阅读更多内容。
Tip
为此,您需要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")
Tip
model_config属性仅用于Pydantic配置。您可以在Pydantic: Concepts: Configuration中阅读更多内容。
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"
Tip
Config类仅用于Pydantic配置。您可以在Pydantic Model Config中阅读更多内容。
Info
在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 docs for @lru_cache中阅读更多内容。
回顾¶
您可以使用Pydantic Settings来处理应用程序的设置或配置,利用Pydantic模型的所有功能。
- 通过使用依赖项,您可以简化测试。
- 您可以与它一起使用
.env文件。 - 使用
@lru_cache可以避免为每个请求重复读取dotenv文件,同时允许您在测试期间覆盖它。