設定と環境変数¶
多くの場合、アプリケーションには外部設定や構成が必要になります。例えば、秘密鍵、データベース認証情報、メールサービス認証情報などです。
これらの設定のほとんどは、データベースの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 では、BaseSettings
を pydantic_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_user
は int
になります) のデータが得られます。
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_EMAIL
と APP_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 ファイルを何度も読み込むことを回避し、テスト中にオーバーライドすることもできます。