Pydantic Settingsで設定を階層化!複雑なFastAPIアプリケーションを管理する

2025-03-21

Pydantic Settingsとは?

FastAPIアプリケーションの設定管理を容易にするための仕組みです。Pydanticはデータ検証と設定管理のためのライブラリであり、FastAPIと組み合わせることで、環境変数や設定ファイルから設定値を読み込み、型安全な方法で利用できるようになります。

主な機能と利点

  • 設定値のドキュメント化
    Pydanticモデルのスキーマを自動的に生成できるため、設定値のドキュメントを簡単に作成できます。
  • 開発と本番環境の分離
    環境変数や設定ファイルを使い分けることで、開発環境と本番環境で異なる設定を適用できます。
  • 設定値の階層化
    ネストされたPydanticモデルを使用することで、複雑な設定を階層的に管理できます。
  • 設定値の検証
    Pydanticモデルのバリデーション機能により、設定値が期待される範囲や形式であることを検証できます。
  • 環境変数と設定ファイルのサポート
    環境変数、.envファイル、JSON、YAMLなどの設定ファイルから設定値を読み込むことができます。
  • 型安全な設定
    設定値をPydanticモデルで定義することで、型チェックが自動的に行われます。これにより、設定値の型の間違いによる実行時エラーを防ぎます。

使い方

    • 設定値を格納するためのPydanticモデルを作成します。
    • 各設定値の型とデフォルト値を定義します。
    • Settingsクラスを継承します。
  1. 設定値の読み込み

    • Settingsクラスのインスタンスを作成すると、環境変数や設定ファイルから設定値が自動的に読み込まれます。
    • 設定値はモデルの属性としてアクセスできます。
  2. FastAPIでの利用

    • 作成した設定モデルのインスタンスをFastAPIアプリケーションの依存性としてインジェクトします。
    • ルーティング関数内で設定値を利用します。

コード例

from fastapi import FastAPI, Depends
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"

def get_settings():
    return Settings()

app = FastAPI()

@app.get("/info")
async def read_info(settings: Settings = Depends(get_settings)):
    return {
        "app_name": settings.app_name,
        "database_url": settings.database_url,
        "debug": settings.debug,
    }

この例では、Settingsモデルを定義し、app_namedatabase_urldebugの3つの設定値を定義しています。Configクラスで.envファイルを指定することで、環境変数に加えて.envファイルからも設定値を読み込むことができます。get_settings関数でSettingsインスタンスを生成し、FastAPIの依存性としてインジェクトしています。ルーティング関数read_infoでは、インジェクトされた設定値を利用してレスポンスを生成しています。



一般的なエラーとトラブルシューティング

    • エラー
      環境変数が正しく設定されていない、または.envファイルのパスが間違っている。
    • トラブルシューティング
      • 環境変数が正しく設定されているか確認します。print(os.environ)などで確認できます。
      • .envファイルのパスがSettingsクラスのConfigenv_fileで正しく指定されているか確認します。相対パス、絶対パスに注意してください。
      • .envファイルがアプリケーションの実行ディレクトリに存在することを確認します。
      • .envファイル内の変数のスペルミスや形式(KEY=VALUE)を確認します。
      • .envファイルが正しく読み込まれているかを確認するために、読み込み後に設定値を出力してみます。
  1. 型検証エラー

    • エラー
      環境変数や設定ファイルの値が、Pydanticモデルで定義された型と一致しない。
    • トラブルシューティング
      • エラーメッセージをよく読み、どの設定値で型エラーが発生しているかを確認します。
      • 設定値の型がPydanticモデルで定義された型と一致しているか確認します。
      • 必要に応じて、型変換やバリデーションを追加します。
      • 特にbool値は文字列として読み込まれることがあるので、strtoboolなどを使用してbool値に変換することを検討してください。
  2. 設定値のデフォルト値が適用されない

    • エラー
      環境変数や設定ファイルで値が指定されていない場合に、デフォルト値が適用されない。
    • トラブルシューティング
      • Pydanticモデルでデフォルト値が正しく定義されているか確認します。
      • 環境変数や設定ファイルで値が設定されていないか確認します。
      • 設定値の優先順位(環境変数 > .envファイル > デフォルト値)を理解しておきます。
  3. 設定値の階層構造のエラー

    • エラー
      ネストされたPydanticモデルで設定値を管理する際に、構造が正しくない。
    • トラブルシューティング
      • ネストされたPydanticモデルの定義を確認し、構造が正しいか確認します。
      • 設定ファイル(JSON、YAMLなど)の構造が、ネストされたPydanticモデルの構造と一致しているか確認します。
  4. Configクラスの誤用

    • エラー
      Configクラスの属性を誤って設定している。
    • トラブルシューティング
      • Configクラスのドキュメントを参照し、属性の正しい使い方を確認します。
      • よく使うenv_fileenv_prefixcase_sensitiveなどの属性の設定を確認します。
  5. 設定値の遅延評価(Lazy Loading)

    • エラー
      設定値がアプリケーションの起動時に読み込まれない。
    • トラブルシューティング
      • Settingsクラスのインスタンスが、設定値を使用する前に作成されているか確認します。
      • 依存性インジェクションを使用している場合、依存性が正しく解決されているか確認します。
  6. Docker環境でのエラー

    • エラー
      Dockerコンテナ内で環境変数が正しく設定されていない。
    • トラブルシューティング
      • Dockerコンテナの起動時に、環境変数を正しく設定しているか確認します。-eオプションや--env-fileオプションを使用します。
      • Docker Composeを使用している場合、environmentセクションで環境変数を設定します。
      • Dockerコンテナ内で環境変数を確認します。docker exec <container_id> printenvなどを使用します。

デバッグのヒント

  • シンプルな設定で動作確認を行い、徐々に複雑な設定を追加していくことで、エラーの原因を特定しやすくなります。
  • ログ出力を有効にし、設定値の読み込みや検証の過程を追跡します。
  • エラーメッセージをよく読み、どの設定値でエラーが発生しているかを確認します。
  • 設定値を読み込んだ後に、print(settings.dict())などで設定値の内容を出力して確認します。


基本的な例:環境変数と.envファイルの利用

from fastapi import FastAPI, Depends
from pydantic import BaseSettings

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

    class Config:
        env_file = ".env"  # .envファイルを読み込む

def get_settings():
    return Settings()

app = FastAPI()

@app.get("/info")
async def read_info(settings: Settings = Depends(get_settings)):
    return {
        "app_name": settings.app_name,
        "database_url": settings.database_url,
        "debug": settings.debug,
    }
  • 実行
    • .envファイルを作成し、上記の例のように設定します。
    • FastAPIアプリケーションを実行します。
    • /infoエンドポイントにアクセスすると、.envファイルの設定が適用された結果が表示されます。
  • .envファイルの例
    APP_NAME="カスタムアプリ名"
    DATABASE_URL="postgresql://user:password@host:port/database"
    DEBUG=True
    
  • 説明
    • Settingsクラスは設定値を定義します。
    • app_name, database_url, debugは設定値の変数です。
    • class Config: env_file = ".env".envファイルを読み込みます。
    • get_settings()関数はSettingsのインスタンスを作成し、依存性として提供します。
    • /infoエンドポイントは設定値を返します。

設定値のプレフィックスとケースセンシティブの設定

from fastapi import FastAPI, Depends
from pydantic import BaseSettings

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

    class Config:
        env_prefix = "MY_APP_"  # 環境変数のプレフィックスを設定
        case_sensitive = False #環境変数の大文字小文字を区別しない

def get_settings():
    return Settings()

app = FastAPI()

@app.get("/info")
async def read_info(settings: Settings = Depends(get_settings)):
    return {
        "app_name": settings.app_name,
        "database_url": settings.database_url,
        "debug": settings.debug,
    }
  • 環境変数の例
    MY_APP_APP_NAME="プレフィックス付きアプリ名"
    MY_APP_DATABASE_URL="mongodb://user:password@host:port/database"
    MY_APP_DEBUG=true
    
  • 説明
    • env_prefix = "MY_APP_"で環境変数のプレフィックスを設定します。例えば、MY_APP_APP_NAMEという環境変数が読み込まれます。
    • case_sensitive = Falseで環境変数の大文字小文字を区別しないようにします。MY_APP_app_nameMY_APP_APP_NAMEとして読み込まれます。

ネストされた設定

from fastapi import FastAPI, Depends
from pydantic import BaseSettings, BaseModel

class DatabaseSettings(BaseModel):
    url: str
    port: int

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

    class Config:
        env_nested_delimiter = "__" #ネストされた設定を環境変数で表現する際区切り文字の設定

def get_settings():
    return Settings()

app = FastAPI()

@app.get("/info")
async def read_info(settings: Settings = Depends(get_settings)):
    return {
        "app_name": settings.app_name,
        "database_url": settings.database.url,
        "database_port": settings.database.port,
        "debug": settings.debug,
    }
  • 環境変数の例
    APP_NAME="ネストされた設定アプリ"
    DATABASE__URL="mysql://user:password@host"
    DATABASE__PORT=3306
    DEBUG=false
    
  • 説明
    • DatabaseSettingsモデルはデータベースの設定を定義します。
    • SettingsモデルはDatabaseSettingsモデルをネストして使用します。
    • env_nested_delimiter = "__"でネストされた設定を環境変数で表現する際の区切り文字を設定します。
import json
from fastapi import FastAPI, Depends
from pydantic import BaseSettings

class Settings(BaseSettings):
    app_name: str
    database_url: str
    debug: bool

    class Config:
        json_file = "config.json" #jsonファイルからの設定

def get_settings():
    return Settings()

app = FastAPI()

@app.get("/info")
async def read_info(settings: Settings = Depends(get_settings)):
    return {
        "app_name": settings.app_name,
        "database_url": settings.database_url,
        "debug": settings.debug,
    }
  • config.jsonファイルの例
    {
      "app_name": "JSON設定アプリ",
      "database_url": "redis://host:port/database",
      "debug": true
    }
    
  • 説明
    • json_file = "config.json"config.jsonファイルを読み込みます。


環境変数のみを使用する


  • 欠点
    型チェックやバリデーションを手動で行う必要があり、設定の階層化が難しい。.envファイルのサポートも自分で実装する必要があります。
  • 利点
    シンプルで、外部ライブラリへの依存が少ない。
  • 方法
    Pydantic Settingsを使用せず、os.environを使用して環境変数を直接読み込みます。
import os
from fastapi import FastAPI

app = FastAPI()

@app.get("/info")
async def read_info():
    app_name = os.environ.get("APP_NAME", "デフォルトアプリ名")
    database_url = os.environ.get("DATABASE_URL", "sqlite:///./default.db")
    debug = os.environ.get("DEBUG", "False").lower() == "true" #bool値の変換

    return {
        "app_name": app_name,
        "database_url": database_url,
        "debug": debug,
    }

Pythonの標準ライブラリconfigparserを使用する


  • 欠点
    型チェックやバリデーションを手動で行う必要があり、複雑なデータ構造の表現が難しい。
  • 利点
    構造化された設定ファイルを扱えます。
  • 方法
    INI形式の設定ファイルを読み込むためにconfigparserを使用します。
import configparser
from fastapi import FastAPI

app = FastAPI()

@app.get("/info")
async def read_info():
    config = configparser.ConfigParser()
    config.read("config.ini")

    app_name = config.get("DEFAULT", "APP_NAME", fallback="デフォルトアプリ名")
    database_url = config.get("DATABASE", "URL", fallback="sqlite:///./default.db")
    debug = config.getboolean("DEFAULT", "DEBUG", fallback=False)

    return {
        "app_name": app_name,
        "database_url": database_url,
        "debug": debug,
    }
  • config.iniの例
[DEFAULT]
APP_NAME = カスタムアプリ名
DEBUG = True

[DATABASE]
URL = postgresql://user:password@host:port/database

YAMLまたはJSONファイルを直接読み込む

  • 例 (YAML)
  • 欠点
    型チェックやバリデーションを手動で行う必要があり、設定の階層化を自分で実装する必要があります。
  • 利点
    複雑なデータ構造を扱えます。
  • 方法
    PyYAMLjsonライブラリを使用して、YAMLまたはJSONファイルを直接読み込みます。
import yaml
from fastapi import FastAPI

app = FastAPI()

@app.get("/info")
async def read_info():
    with open("config.yaml", "r") as f:
        config = yaml.safe_load(f)

    app_name = config.get("app_name", "デフォルトアプリ名")
    database_url = config.get("database", {}).get("url", "sqlite:///./default.db")
    debug = config.get("debug", False)

    return {
        "app_name": app_name,
        "database_url": database_url,
        "debug": debug,
    }
  • config.yamlの例
app_name: カスタムアプリ名
database:
  url: mongodb://user:password@host:port/database
debug: true

独自の設定管理クラスを作成する


  • (省略)
  • 欠点
    実装に時間がかかり、メンテナンスが大変になる可能性があります。
  • 利点
    柔軟性が高く、特定の要件に合わせてカスタマイズできます。
  • 方法
    設定の読み込み、検証、管理を行う独自のクラスを作成します。

環境変数と設定ファイルを組み合わせる


  • (省略)
  • 欠点
    組み合わせる方法を自分で実装する必要があります。
  • 利点
    環境変数で設定を上書きできるため、柔軟性が高い。
  • 方法
    環境変数を優先し、設定ファイルでデフォルト値を定義します。
  • ドキュメント化
    設定値のドキュメントを手動で作成する必要があります。
  • エラー処理
    設定ファイルの読み込みや環境変数の取得でエラーが発生した場合、適切なエラー処理を行う必要があります。
  • 設定の階層化
    複雑な設定を管理する場合、自分で階層構造を実装する必要があります。
  • 型チェックとバリデーション
    手動で型チェックとバリデーションを行う必要があります。