設定和環境變數¶
在許多情況下,您的應用程式可能需要一些外部設定或配置,例如密鑰、資料庫憑證、電子郵件服務憑證等。
大多數這些設定都是可變的(可以更改),例如資料庫網址。而且許多設定可能是敏感的,例如密鑰。
因此,通常將它們提供在由應用程式讀取的環境變數中。
提示
要了解環境變數,您可以閱讀環境變數。
類型和驗證¶
這些環境變數只能處理文字字串,因為它們在 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,
}
from functools import lru_cache
from fastapi import Depends, FastAPI
from typing_extensions import Annotated
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,
}
提示
如果可能,建議使用 Annotated
版本。
from functools import lru_cache
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: 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,
}
from functools import lru_cache
from fastapi import Depends, FastAPI
from typing_extensions import Annotated
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,
}
提示
如果可能,建議使用 Annotated
版本。
from functools import lru_cache
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: 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 設定:Dotenv (.env) 支援 中閱讀更多資訊。
提示
要使其運作,您需要 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,
}
from functools import lru_cache
from typing import Annotated
from fastapi import Depends, FastAPI
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,
}
提示
如果可能,建議使用 Annotated
版本。
from functools import lru_cache
from fastapi import Depends, FastAPI
from . import config
app = FastAPI()
@lru_cache
def get_settings():
return config.Settings()
@app.get("/info")
async def info(settings: 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 檔案,同時允許您在測試期間覆寫它。