跳至內容

設定和環境變數

在許多情況下,您的應用程式可能需要一些外部設定或配置,例如密鑰、資料庫憑證、電子郵件服務憑證等。

大多數這些設定都是可變的(可以更改),例如資料庫網址。而且許多設定可能是敏感的,例如密鑰。

因此,通常將它們提供在由應用程式讀取的環境變數中。

提示

要了解環境變數,您可以閱讀環境變數

類型和驗證

這些環境變數只能處理文字字串,因為它們在 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_EMAILAPP_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_cachefunctools 的一部分,而 functools 是 Python 標準函式庫的一部分,您可以在 Python 的 @lru_cache 文件 中了解更多資訊。

摘要

您可以使用 Pydantic Settings 來處理應用程式的設定,並運用 Pydantic 模型的所有功能。

  • 透過使用依賴項,您可以簡化測試。
  • 您可以搭配使用 .env 檔案。
  • 使用 @lru_cache 可以避免為每個請求重複讀取 dotenv 檔案,同時允許您在測試期間覆寫它。