FastAPI設定管理を極める:依存性注入でテスト容易なコードを実現

2025-04-26

別のモジュールに設定を分離する理由

  • テストの容易化
    設定をモジュール化することで、テスト時に異なる設定を簡単に適用できます。
  • 再利用性
    複数のアプリケーションで共通の設定を共有できます。
  • コードの整理
    設定を別のファイルに分離することで、メインのアプリケーションコードがより整理され、読みやすくなります。

別のモジュールに設定を分離する方法

  1. 設定用のモジュールを作成
    設定を格納するPythonファイル(例: config.py)を作成します。

  2. BaseSettingsを継承したクラスを定義
    config.py内で、PydanticのBaseSettingsを継承したクラスを定義し、設定項目をフィールドとして宣言します。

  3. 環境変数や.envファイルからの読み込み
    BaseSettingsは環境変数や.envファイルからの設定読み込みをサポートしています。必要に応じて設定値を環境変数や.envファイルに記述します。

  4. FastAPIアプリケーションで設定をインポートして使用
    メインのFastAPIアプリケーションファイルで、設定モジュールから設定クラスをインポートし、インスタンスを作成して設定値を使用します。


config.py

from pydantic import BaseSettings

class Settings(BaseSettings):
    app_name: str = "My FastAPI App"
    database_url: str = "sqlite:///./test.db"
    debug: bool = False

    class Config:
        env_file = ".env"

settings = Settings()

.env

APP_NAME="My Production App"
DATABASE_URL="postgresql://user:password@host/database"
DEBUG=True

main.py

from fastapi import FastAPI
from config import settings

app = FastAPI()

@app.get("/")
async def root():
    return {
        "app_name": settings.app_name,
        "database_url": settings.database_url,
        "debug": settings.debug
    }

説明

  • main.pyでは、config.pyからsettingsインスタンスをインポートし、設定値を使用しています。
  • .envファイルに設定値を記述することで、環境に応じて設定を変更できます。
  • Configクラス内で.envファイルを指定することで、環境変数に加えて.envファイルからも設定を読み込むことができます。
  • config.pyでは、Settingsクラスを定義し、app_namedatabase_urldebugを設定項目として宣言しています。

ポイント

  • case_sensitiveを使用して、環境変数の大文字と小文字を区別するかどうかを指定できます。
  • env_prefixを使用して、環境変数のプレフィックスを指定できます。
  • 設定項目には型アノテーションを付けることで、Pydanticによる型チェックが有効になります。
  • BaseSettingsは、環境変数、.envファイル、コマンドライン引数、設定ファイルなど、さまざまなソースから設定を読み込むことができます。


よくあるエラーとトラブルシューティング

    • エラー
      FileNotFoundError: [Errno 2] No such file or directory: '.env'
    • 原因
      .envファイルが指定されたパスに存在しない、またはパスが間違っている。
    • 解決策
      • .envファイルがプロジェクトのルートディレクトリに存在することを確認してください。
      • config.pyConfigクラス内のenv_file変数のパスが正しいことを確認してください。
      • 絶対パスまたは相対パスを明示的に指定してみてください。
  1. 環境変数の読み込みエラー

    • エラー
      設定値が環境変数から正しく読み込まれない。
    • 原因
      • 環境変数が設定されていない。
      • 環境変数の名前が間違っている。
      • 環境変数の値が期待される型と異なる。
      • 環境変数のプレフィックス(env_prefix)が間違っている。
    • 解決策
      • 環境変数が正しく設定されていることを確認してください。
      • config.py内の設定フィールド名と環境変数名が一致していることを確認してください。
      • 型アノテーションが設定値の型と一致していることを確認してください。
      • env_prefixを使用している場合は、プレフィックスが正しいことを確認してください。
      • 大文字小文字の区別をするかの設定(case_sensitive)も確認する。
  2. 設定クラスのインポートエラー

    • エラー
      ImportError: cannot import name 'Settings' from 'config'
    • 原因
      • config.pyが存在しない。
      • config.py内のSettingsクラスの名前が間違っている。
      • config.pyのパスが間違っている。
    • 解決策
      • config.pyが存在することを確認してください。
      • config.py内のSettingsクラスの名前が正しいことを確認してください。
      • main.pyconfig.pyを正しくインポートしていることを確認してください。
  3. 設定値の型エラー

    • エラー
      ValidationError
    • 原因
      • 環境変数や.envファイルの値が、設定フィールドの型アノテーションと一致しない。
    • 解決策
      • 環境変数や.envファイルの値が正しい型であることを確認してください。
      • config.py内の型アノテーションを修正してください。
  4. 設定の優先順位に関する問題

    • エラー
      期待される設定値が適用されない。
    • 原因
      • 環境変数、.envファイル、デフォルト値など、複数の設定ソースが競合している。
    • 解決策
      • BaseSettingsの優先順位を理解してください (環境変数 > .envファイル > デフォルト値)。
      • 設定ソースを明確に管理し、競合を避けてください。
  5. 開発環境と本番環境での設定の切り替え

    • エラー
      開発環境と本番環境で異なる設定を適用できない。
    • 原因
      • 環境変数や.envファイルが適切に管理されていない。
    • 解決策
      • 開発環境と本番環境で異なる.envファイルを使用してください。
      • 環境変数を使用して、環境に応じた設定を適用してください。
      • 設定ファイルを環境ごとに分けて管理し、環境変数で切り替えるようにする。

トラブルシューティングのヒント

  • テスト
    設定に関するユニットテストを作成し、異なる設定値でテストしてください。
  • ログ
    ロギングを設定して、設定の読み込み状況やエラーメッセージを記録してください。
  • デバッグ
    print()文やデバッガを使用して、設定値が正しく読み込まれているか確認してください。


例1: 基本的な設定の分離

config.py

from pydantic import BaseSettings

class Settings(BaseSettings):
    app_name: str = "デフォルトのアプリ名"
    debug: bool = False

settings = Settings()

main.py

from fastapi import FastAPI
from config import settings

app = FastAPI()

@app.get("/")
async def root():
    return {
        "app_name": settings.app_name,
        "debug": settings.debug,
    }

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=8000)

説明

  • main.pyconfig.pyからsettingsインスタンスをインポートし、ルートエンドポイントで設定値を使用しています。
  • config.pySettingsクラスを定義し、app_namedebugのデフォルト値を設定しています。

例2: 環境変数と.envファイルの使用

config.py

from pydantic import BaseSettings

class Settings(BaseSettings):
    app_name: str = "デフォルトのアプリ名"
    database_url: str = "sqlite:///./test.db"
    debug: bool = False

    class Config:
        env_file = ".env"

settings = Settings()

.env

APP_NAME="環境変数のアプリ名"
DATABASE_URL="postgresql://user:password@host/mydatabase"
DEBUG=True

main.py

from fastapi import FastAPI
from config import settings

app = FastAPI()

@app.get("/")
async def root():
    return {
        "app_name": settings.app_name,
        "database_url": settings.database_url,
        "debug": settings.debug,
    }

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=8000)

説明

  • 環境変数の値がデフォルト値を上書きします。
  • config.pyConfigクラスでenv_file = ".env"を指定することで、.envファイルから設定を読み込みます。
  • .envファイルに環境変数を設定しています。

例3: 環境変数プレフィックスの使用

config.py

from pydantic import BaseSettings

class Settings(BaseSettings):
    app_name: str = "デフォルトのアプリ名"
    database_url: str = "sqlite:///./test.db"
    debug: bool = False

    class Config:
        env_prefix = "MYAPP_"

settings = Settings()

環境変数

export MYAPP_APP_NAME="プレフィックス付きアプリ名"
export MYAPP_DATABASE_URL="postgresql://user:password@host/mydatabase"
export MYAPP_DEBUG=True

main.py

from fastapi import FastAPI
from config import settings

app = FastAPI()

@app.get("/")
async def root():
    return {
        "app_name": settings.app_name,
        "database_url": settings.database_url,
        "debug": settings.debug,
    }

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=8000)

説明

  • プレフィックスを使用することで、環境変数の衝突を避けることができます。
  • 環境変数はMYAPP_プレフィックス付きで設定する必要があります。
  • config.pyConfigクラスでenv_prefix = "MYAPP_"を指定することで、環境変数のプレフィックスを設定します。

例4: 複数の設定ファイルの使用

config.py

from pydantic import BaseSettings
import os

class Settings(BaseSettings):
    app_name: str = "デフォルトのアプリ名"
    database_url: str = "sqlite:///./test.db"
    debug: bool = False

    class Config:
        env_file = os.getenv("ENV_FILE", ".env")

settings = Settings()

.env.dev

APP_NAME="開発環境アプリ名"
DEBUG=True

.env.prod

APP_NAME="本番環境アプリ名"
DATABASE_URL="postgresql://user:password@productionhost/productiondb"
DEBUG=False

環境変数

export ENV_FILE=".env.prod" #本番環境の場合

main.py

from fastapi import FastAPI
from config import settings

app = FastAPI()

@app.get("/")
async def root():
    return {
        "app_name": settings.app_name,
        "database_url": settings.database_url,
        "debug": settings.debug,
    }

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=8000)
  • os.getenv()を使うことで環境変数から値を取得することができます。
  • 開発環境と本番環境で異なる設定ファイルを適用できます。
  • 環境変数ENV_FILEを使用して、使用する.envファイルを切り替えます。


設定ファイルを直接読み込む方法 (ConfigParser, JSON, YAML)

PydanticのBaseSettingsを使用せず、標準ライブラリのconfigparserjson、サードパーティライブラリのPyYAMLなどを使用して設定ファイルを直接読み込む方法です。

  • PyYAML (YAMLファイル)

    • より複雑な設定を扱うのに適しています。
    • 可読性が高い設定ファイルを記述できます。
    • pip install pyyamlでインストールする必要があります。
  • json (JSONファイル)

    • 構造化された設定を扱うのに適しています。
    • 標準ライブラリに含まれているため、追加のインストールは不要です。
    • シンプルな設定ファイル形式(INI)を扱うのに適しています。
    • 標準ライブラリに含まれているため、追加のインストールは不要です。

例 (JSONファイル)

config.json

{
  "app_name": "代替アプリ名",
  "database_url": "mysql://user:password@host/db",
  "debug": true
}

config.py

import json

def load_config(filepath: str):
    with open(filepath, "r") as f:
        return json.load(f)

config = load_config("config.json")

main.py

from fastapi import FastAPI
from config import config

app = FastAPI()

@app.get("/")
async def root():
    return {
        "app_name": config["app_name"],
        "database_url": config["database_url"],
        "debug": config["debug"],
    }

環境変数を直接読み込む方法 (os.environ)

os.environを使用して、環境変数を直接読み込む方法です。

  • 設定の型チェックやデフォルト値の設定は、自分で実装する必要があります。
  • BaseSettingsを使用せずに、環境変数を直接取得します。


config.py

import os

app_name = os.environ.get("APP_NAME", "デフォルトアプリ名")
database_url = os.environ.get("DATABASE_URL", "sqlite:///./test.db")
debug = os.environ.get("DEBUG", "False").lower() == "true"

main.py

from fastapi import FastAPI
from config import app_name, database_url, debug

app = FastAPI()

@app.get("/")
async def root():
    return {
        "app_name": app_name,
        "database_url": database_url,
        "debug": debug,
    }

設定オブジェクトを自作する方法

独自のクラスを作成して、設定オブジェクトを管理する方法です。

  • 設定の検証や変換を柔軟に実装できます。
  • より複雑な設定管理が必要な場合に適しています。


config.py

class Config:
    def __init__(self, app_name, database_url, debug):
        self.app_name = app_name
        self.database_url = database_url
        self.debug = debug

def load_config():
    # 環境変数やファイルから設定を読み込む
    app_name = "自作アプリ名"
    database_url = "postgresql://user:password@host/db"
    debug = True
    return Config(app_name, database_url, debug)

config = load_config()

main.py

from fastapi import FastAPI
from config import config

app = FastAPI()

@app.get("/")
async def root():
    return {
        "app_name": config.app_name,
        "database_url": config.database_url,
        "debug": config.debug,
    }

依存性注入 (Dependency Injection) を利用した設定の提供

FastAPIの依存性注入システムを利用して、設定オブジェクトを依存として提供する方法です。

  • コードの可読性や保守性が向上します。
  • テストや設定の切り替えが容易になります。