FastAPI 環境変数とは?初心者向けに基本から応用までわかりやすく解説

2025-03-21

FastAPIにおける環境変数とは?

FastAPIアプリケーションにおいて、環境変数は、アプリケーションの設定や機密情報を外部から安全に提供するための仕組みです。環境変数は、オペレーティングシステムレベルで設定され、アプリケーションのコードを変更せずに設定を変更できるため、柔軟性とセキュリティが向上します。

なぜ環境変数を使うのか?

  • 柔軟性と移植性
    環境変数は、アプリケーションのコードを変更せずに設定を変更できるため、アプリケーションの柔軟性と移植性が向上します。
  • 設定の分離
    開発環境、テスト環境、本番環境など、異なる環境で異なる設定を使用する場合、環境変数を利用することで、コードを変更せずに設定を切り替えることができます。

FastAPIでの環境変数の使い方

FastAPIで環境変数を使用するには、通常、python-dotenvなどのライブラリを使用します。

  1. pip install python-dotenv
    
  2. .envファイルの作成
    アプリケーションのルートディレクトリに.envファイルを作成し、環境変数を定義します。 例:

    DATABASE_URL=postgresql://user:password@host:port/database
    API_KEY=your_api_key
    
  3. FastAPIアプリケーションでの環境変数の読み込み
    python-dotenvを使用して.envファイルを読み込み、環境変数を取得します。 例:

    from fastapi import FastAPI
    from dotenv import load_dotenv
    import os
    
    load_dotenv()  # .envファイルを読み込む
    
    app = FastAPI()
    
    DATABASE_URL = os.getenv("DATABASE_URL")
    API_KEY = os.getenv("API_KEY")
    
    @app.get("/")
    async def root():
        return {"database_url": DATABASE_URL, "api_key": API_KEY}
    

説明

  • os.getenv("変数名"): この関数は、指定された名前の環境変数の値を取得します。
  • load_dotenv(): この関数は、.envファイルから環境変数を読み込み、オペレーティングシステムの環境変数として設定します。

重要な点

  • 環境変数は、コンテナ環境(Dockerなど)で特に便利です。コンテナの起動時に環境変数を設定することで、コンテナの設定を柔軟に変更できます。
  • 環境変数は、オペレーティングシステムレベルで設定することもできます。例えば、LinuxやmacOSではexport 変数名=値、Windowsではset 変数名=値コマンドを使用します。
  • .envファイルは、バージョン管理システム(Gitなど)から除外することをお勧めします。機密情報が含まれているため、リポジトリにコミットしないように注意してください。.gitignore.envを追加してください。


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

    • 原因
      • .envファイルが存在しない、または正しい場所にない。
      • load_dotenv()が呼び出されていない。
      • .envファイルの構文が間違っている(例:スペースや特殊文字の誤用)。
      • 環境変数がオペレーティングシステムレベルで設定されていない。
    • トラブルシューティング
      • .envファイルがアプリケーションのルートディレクトリに存在することを確認してください。
      • load_dotenv()がコードの先頭で呼び出されていることを確認してください。
      • .envファイルの構文を再確認してください。変数名=値の形式になっているか確認します。
      • print(os.environ)を使用して、読み込まれた環境変数を確認します。目的の変数が表示されない場合は、読み込みに失敗しています。
      • オペレーティングシステムレベルで環境変数を設定している場合は、設定が正しいか確認してください。コマンドプロンプトやターミナルでecho $変数名(Linux/macOS)またはecho %変数名%(Windows)を実行して値を確認します。
  1. 環境変数の値がNoneになる

    • 原因
      • 環境変数が設定されていない。
      • os.getenv("変数名")に渡された変数名が間違っている。
    • トラブルシューティング
      • 環境変数が.envファイルまたはオペレーティングシステムレベルで設定されていることを確認してください。
      • os.getenv()に渡された変数名が、.envファイルまたはオペレーティングシステムで設定した変数名と正確に一致していることを確認してください。大文字と小文字も区別されます。
      • os.getenv()の戻り値をprint()で出力して、Noneになっているか確認します。
  2. .envファイルがGitにコミットされてしまう

    • 原因
      • .gitignoreファイルに.envが追加されていない。
    • トラブルシューティング
      • .gitignoreファイルに.envを追加してください。
      • すでにコミットされている場合は、git rm --cached .envを実行してから再度コミットしてください。
  3. コンテナ環境(Dockerなど)での環境変数に関する問題

    • 原因
      • Dockerコンテナの起動時に環境変数が正しく設定されていない。
      • Docker Composeファイルでの環境変数の設定が間違っている。
    • トラブルシューティング
      • docker runコマンドの-eオプションまたはDocker Composeファイルのenvironmentセクションを使用して、環境変数を設定してください。
      • コンテナ内でprint(os.environ)を実行して、環境変数が正しく設定されているか確認してください。
      • Docker Composeを使用している場合は、ファイルの構文を再確認してください。
  4. 環境変数と設定ファイルの優先順位の問題

    • 原因
      • 環境変数と設定ファイル(例:JSON、YAML)の両方で同じ設定が定義されており、優先順位が不明確。
    • トラブルシューティング
      • アプリケーションの設計で、環境変数と設定ファイルの優先順位を明確に定義してください。一般的には、環境変数を優先する設計が推奨されます。
      • 設定ファイルから読み込んだ値を、環境変数が設定されている場合は環境変数の値で上書きするようにコードを実装します。

デバッグのヒント

  • ステップ実行
    デバッガを使用して、コードをステップ実行し、環境変数の値の変化を追跡します。
  • ログ出力
    環境変数の読み込みや使用に関するログを出力するようにコードを修正します。
  • print(os.environ)
    この関数を使用して、すべての環境変数とその値を出力し、デバッグに役立てます。


例1:基本的な環境変数の読み込みと使用

この例では、.envファイルから環境変数を読み込み、FastAPIアプリケーションで使用します。

from fastapi import FastAPI
from dotenv import load_dotenv
import os

load_dotenv()  # .envファイルを読み込む

app = FastAPI()

DATABASE_URL = os.getenv("DATABASE_URL")
API_KEY = os.getenv("API_KEY")

@app.get("/")
async def root():
    return {"database_url": DATABASE_URL, "api_key": API_KEY}

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

.envファイルの内容:

DATABASE_URL=postgresql://user:password@host:port/database
API_KEY=your_api_key

説明

  • if __name__ == "__main__":: uvicornサーバーを起動します。
  • @app.get("/"): ルートエンドポイントで、環境変数の値をJSON形式で返します。
  • os.getenv("変数名"): 指定された名前の環境変数の値を取得します。
  • load_dotenv(): .envファイルから環境変数を読み込みます。

例2:環境変数のデフォルト値の設定

環境変数が設定されていない場合に、デフォルト値を使用する例です。

from fastapi import FastAPI
from dotenv import load_dotenv
import os

load_dotenv()

app = FastAPI()

PORT = os.getenv("PORT", 8080)  # PORTが設定されていない場合は8080を使用

@app.get("/")
async def root():
    return {"port": PORT}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=int(PORT)) #PORTをint型に変換してuvicornに渡す

説明

  • uvicorn.run(..., port=int(PORT)): PORTの値を整数に変換してuvicornに渡します。環境変数は文字列として扱われるため、適切な型に変換する必要があります。
  • os.getenv("変数名", デフォルト値): 環境変数が設定されていない場合に、デフォルト値を返します。

例3:環境変数の型変換とバリデーション

環境変数の値を適切な型に変換し、バリデーションを行う例です。

from fastapi import FastAPI, HTTPException
from dotenv import load_dotenv
import os

load_dotenv()

app = FastAPI()

try:
    TIMEOUT = int(os.getenv("TIMEOUT"))
except (TypeError, ValueError):
    raise ValueError("TIMEOUT must be an integer.")

if TIMEOUT <= 0:
    raise ValueError("TIMEOUT must be a positive integer.")

@app.get("/")
async def root():
    return {"timeout": TIMEOUT}

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

説明

  • if TIMEOUT <= 0: TIMEOUTの値が正の整数であることをバリデーションします。
  • try...except: 型変換が失敗した場合に例外を捕捉し、エラーメッセージを表示します。
  • int(os.getenv("TIMEOUT")): TIMEOUTの値を整数に変換します。

例4: pydanticを使って環境変数を扱う

pydanticを使うことで環境変数を型とバリデーションを含めて管理できます。

from fastapi import FastAPI
from pydantic_settings import BaseSettings, SettingsConfigDict
import os

class Settings(BaseSettings):
    database_url: str
    api_key: str
    model_config = SettingsConfigDict(env_file=".env")

settings = Settings()

app = FastAPI()

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

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=8000)
DATABASE_URL=postgresql://user:password@host:port/database
API_KEY=your_api_key
  • pydanticが型変換とバリデーションを自動で行います。
  • settings = Settings(): 設定を読み込みます。
  • SettingsConfigDict(env_file=".env"): 環境変数を.envファイルから読み込む設定です。
  • BaseSettings: pydanticの環境変数設定を管理するクラスです。


設定ファイルの使用 (JSON, YAML, TOMLなど)

環境変数の代わりに、設定ファイルをアプリケーションの外部に配置し、そこから設定を読み込む方法です。

  • 欠点
    • 機密情報を安全に管理するための追加の対策が必要。
    • 環境変数よりも設定の変更がやや煩雑になる場合がある。
  • 利点
    • 複雑な設定を構造化して管理しやすい。
    • 環境変数よりも多くの設定情報を格納できる。
    • 設定をファイルとして管理できるため、バージョン管理システムで管理しやすい。

例 (JSON設定ファイル)

config.jsonファイル:

{
  "database_url": "postgresql://user:password@host:port/database",
  "api_key": "your_api_key",
  "port": 8080
}

FastAPIアプリケーション:

from fastapi import FastAPI
import json

app = FastAPI()

with open("config.json", "r") as f:
    config = json.load(f)

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

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

データベースの使用

アプリケーションの設定をデータベースに保存し、必要に応じてデータベースから設定を読み込む方法です。

  • 欠点
    • データベースへの依存性が高まる。
    • データベース接続設定をどこか別の場所で管理する必要がある。
    • 設定の読み込みにデータベースアクセスが必要となり、パフォーマンスに影響を与える可能性がある。
  • 利点
    • 動的な設定変更が可能。
    • 複数のアプリケーションで設定を共有できる。
    • 設定の履歴管理やバージョン管理が容易。

設定サーバーの使用 (Consul, etcd, ZooKeeperなど)

分散システムにおける設定管理に特化した設定サーバーを使用する方法です。

  • 欠点
    • 設定サーバーの導入と管理が必要。
    • 小規模なアプリケーションではオーバーエンジニアリングになる可能性がある。
  • 利点
    • 大規模な分散システムにおける設定管理に最適。
    • 動的な設定変更や設定の共有が容易。
    • 設定のバージョン管理や監視機能が充実。

pydantic_settings の使用の拡張

pydantic_settingsは、環境変数だけでなく、設定ファイルやシークレット管理ツールなど、さまざまな設定ソースを統合的に扱うことができます。

  • 欠点
    • pydantic_settingsの機能を十分に活用するには、追加の学習が必要となる場合がある。
  • 利点
    • 複数の設定ソースを組み合わせた柔軟な設定管理が可能。
    • 型チェックとバリデーションを統合的に実行できる。
    • シークレット管理ツールとの連携により、機密情報を安全に管理できる。

例 (pydantic_settingsと設定ファイルの組み合わせ)

from fastapi import FastAPI
from pydantic_settings import BaseSettings, SettingsConfigDict
import os

class Settings(BaseSettings):
    database_url: str
    api_key: str
    port: int = 8000
    model_config = SettingsConfigDict(env_file=".env", json_file_encoding='utf-8', json_file='config.json')

settings = Settings()

app = FastAPI()

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

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

この例では、.envファイルとconfig.jsonファイルの両方から設定を読み込みます。優先順位は、SettingsConfigDictで指定した順序に従います。