💿

【FastAPI】環境変数の取得はキャッシュとPydanticのSettingsを使おう!

2024/07/26に公開

はじめに

本記事の目的は、環境変数の取得においてパフォーマンス及び保守性・信頼性を向上させることです。

今回はFastAPIにおいて、.envファイルで定義している環境変数を取得する際に、lru_cacheによってキャッシュを使用する方法を説明します。
今までパフォーマンス面をあまりを気にしてこなかった方や、そもそも環境変数、キャッシュを知らない方にもわかりやすく説明するつもりです!

また.envの取得をPydanticのSettingsを用いて一括管理します。保守性・信頼性をあまり考えたことがない方にも必見です。

基本的にはこちらのFastAPI公式ドキュメントのSettings and Environment Variablesを元に話を進めます。
https://fastapi.tiangolo.com/advanced/settings/

環境変数とは(知ってる人は読み飛ばし推奨)

まずそもそも環境変数が何かという話をします。
アプリケーション開発ではsecret keysdatabase credentialsなど外部の設定が必要になります。
まあそう言われても何もわからないと思うので具体例を示します👇

具体例:OpenAI APIキーの設定方法

具体例:OpenAI APIキー

例えばコードの中でChatGPTを使いたい時には、OpenAIのAPIキーが必要です。
このAPIキーは他の人に知られると勝手に使われて、知らない間にお金を使われている可能性があるの注意!
つまり自分のコードをGithubなどにあげる時には、APIキーを直接コードに書き込むこと(ハードコーディング)は御法度😡ということです。

じゃあどうすればいいかというと、環境変数"OPENAI_API_KEY"として定義すればいいんです。
そして"OPENAI_API_KEY"はどこで設定するかというと大まかに2つの方法があると思います。

非推奨な方法

1つ目がコマンドラインで直接打ち込む方法、もしくは.bashrcとかに書いておくことでシェル起動時に読み込ませる方法です。

$ export OPENAI_API_KEY=<your OpenAI API key>

<your OpenAI API key>の部分にAPIキーを記述)
ただこの方法はチーム開発ですることはないと思うので非推奨です。

推奨方法

2つ目は.envファイルで管理することです。以下のように.envを作成して環境変数を定義できます。

.env
OPENAI_API_KEY=<your OpenAI API key>

ここまでで.envファイルの中で環境変数を定義できました。

PydanticのSettingsを使おう!

ここから本題に入ります。
定義した環境変数をどのように取得するべきかを考えます。

まず一般的には下記のコードを書く人が多いです(所感)。

main.py
import os
from dotenv import load_dotenv
import openai #OpenAIのAPI用

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

# 環境変数からAPIキーを取得
openai.api_key = os.getenv("OPENAI_API_KEY")

python-dotenvによって.envファイルを読み込むことができます。
これでも個人で気ままに開発する分には問題はないですが、もっとパフォーマンスと保守性・信頼性を向上させる方法があります。

それがPydanticのSettingsです。これにより保守性・信頼性を向上させることができます。

上記の実装の問題点

まずそもそも.envの取得をmain.pyでやってることがよろしくないのです。
環境変数をmain.pyでしか使わない場合は特に問題ないですが、ファイルが増えていくにつれmain.py以外でも使用したい時が来るはずです。
その時全てのファイルで同じ記述をすることは冗長であり保守性に欠けます。

PydanticのSettingsを使ってconfig.pyで一括管理

そのためconfig.pyのようなファイルで.envを一括管理します。この時に使用するのがSettingsです。

config.py
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    """ 環境変数を読み込む """
    openai_api_key: str # 文字列型でバリデーション

    class Config:
        env_file = ".env" # .envファイルのパスを指定

# インスタンス化して環境変数を読み込む
settings = Settings()

上記のような書き方をすることで環境変数をconfig.pyだけで管理でき、可読性が向上します。
またstrを指定することで、"OPENAI_API_KEY"を文字列型でバリデーションチェックしてくれるので信頼性も向上します!

このconfig.pyで設定した環境変数を他のファイル(main.pyなど)で読み込むときは以下のようになります。

main.py
import openai
from config import settings  # config.pyから設定をインポート

# OpenAI APIキーを設定
openai.api_key = settings.openai_api_key

今回用いた単純なディレクトリ構成

今回は説明のため、以下のようなディレクトリ構成にしてます。変更したい場合は適宜正しいパスを通してください。

your_project/
|
├── .env        # 環境変数の設定
├── config.py   # 環境変数の一括管理
└── main.py     # メインの処理など

キャッシュを使おう!

config.pyで一括管理できるようになりましたが、まだ問題点があります。
Settingsクラスのインスタンスが一度作成されると.envファイルを直接読み込むため、リクエストごとに再読み込みする必要があります。
これによりパフォーマンスを低下させる可能性があります。

そのためキャッシュを使った実装をします。

キャッシュとは

一度読み込んだ内容を一時的に保存し、再度使用する際に素早く読み込めるようにする

まず先に実装例を示します。

config.py
from functools import lru_cache
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    """ 環境変数を読み込む """
    openai_api_key: str # 文字列型でバリデーション

    class Config:
        env_file = ".env" # .envファイルのパスを指定

# キャッシュ(New)
@lru_cache
def get_settings():
    """ @lru_cacheで.envの結果をキャッシュする """
    return Settings()
main.py
import openai
from config import settings  # config.pyから設定をインポート

# 設定の取得(New)
settings = get_settings()

# OpenAI APIキーを設定
openai.api_key = settings.openai_api_key

先ほどのconfig.pylru_cacheを追加しました。lru_cachefunctoolsモジュールに含まれ、Least Recently Usedキャッシュを提供します。
LRUキャッシュの詳細は以下の記事で紹介されていますので割愛しますが、簡単に言うと最近最も使われていないデータをキャッシュから削除したものです。
https://qiita.com/grouse324/items/8c7c48b17c4fbf246f44

lru_cacheのメリット

lru_cacheによって1回.envファイルを読み込んだら使いまわせるようになります。
また依存関数get_settings()を用いているので、テストではオーバーライドできます。
get_settings()は引数を取らないことで常に同じ値を返すため、グローバル変数のように扱えるのも特徴です。
main.pyでの取得方法を参考にしてください。

まとめ

今回は環境変数の取得方法において、パフォーマンス及び保守性・信頼性の向上の観点から説明しました。
実装ではOpenAI APIキーの取得を例に、FastAPIにおける具体的な実装例も示しました。
以下が今回の実装でのメリットになります。

  • PydanticのSettingsのメリット
    • 可読性:config.pyで環境変数を一括管理
    • 信頼性:バリデーションチェックをしてくれる
  • キャッシュ(lru_cache)のメリット
    • パフォーマンス改善:不要な読み込みを避ける
    • 保守性:テストが容易になる

以上です、ありがとうございました😊

ソーシャルデータバンク テックブログ

Discussion