コンテンツへスキップ

設定と環境変数

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

これらの設定のほとんどは、データベースの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 では、BaseSettingspydantic_settings からではなく、pydantic から直接インポートしていました。

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_userint になります) のデータが得られます。

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,
    }

ヒント

@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」と呼ばれます。

ヒント

ドット (.) で始まるファイルは、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,
    }

その後、次のリクエストの依存関係で get_settings() が呼び出されるたびに、get_settings() の内部コードを実行して新しい Settings オブジェクトを作成する代わりに、最初の呼び出しで返された同じオブジェクトが繰り返し返されます。

lru_cache の技術詳細

@lru_cache は、デコレートされた関数を、毎回関数のコードを実行して再計算する代わりに、最初に返されたのと同じ値を返すように変更します。

したがって、その下の関数は、引数の組み合わせごとに1回だけ実行されます。そして、その引数の組み合わせによって返された値は、関数がまったく同じ引数の組み合わせで呼び出されるたびに繰り返し使用されます。

例えば、次のような関数があるとします。

@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 の @lru_cache ドキュメント で詳細を読むことができます。

まとめ

Pydantic Settings を使用すると、Pydantic モデルのすべての機能を使って、アプリケーションの設定や構成を処理できます。

  • 依存関係を使用することで、テストを簡素化できます。
  • .env ファイルを使用できます。
  • @lru_cache を使用することで、リクエストごとに dotenv ファイルを何度も読み込むことを回避し、テスト中にオーバーライドすることもできます。