コンテンツへスキップ

設定と環境変数

多くの場合、アプリケーションは秘密鍵、データベース認証情報、メールサービスの認証情報など、外部設定または構成を必要とする場合があります。

これらの設定のほとんどは、データベース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_settings`ではなく`pydantic`から`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」と呼ばれます。

ヒント

ドット(.)で始まるファイルは、LinuxやmacOSなどのUnix系システムでは隠しファイルです。

しかし、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: 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"

ヒント

Configクラスは、Pydanticの設定にのみ使用されます。詳しくはPydantic Model Configをご覧ください。

情報

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はPythonの標準ライブラリの一部であるfunctoolsの一部です。詳しくはPython docs for @lru_cacheをご覧ください。

要約

Pydantic Settingsを使用して、Pydanticモデルのすべての機能を備えたアプリケーションの設定または構成を処理できます。

  • 依存関係を使用することで、テストを簡素化できます。
  • .envファイルを使用できます。
  • @lru_cacheを使用すると、テスト中にオーバーライドしながら、各リクエストに対してdotenvファイルを繰り返し読み取るのを回避できます。