Pydanticで環境変数の取り扱いを楽にする
Pythonで環境変数を読み込みたいときありますよね。
pydantic BaseSettings
を使うといい感じに読み取りができるので紹介したいと思います。
pydanticとは
公式から
Data validation and settings management using Python type annotations.
pydantic enforces type hints at runtime, and provides user friendly errors when data is invalid.
Define how data should be in pure, canonical Python; validate it with pydantic.
型アノテーションを使った検証や管理を行ってくれます。
Pythonは動的型付け言語のため、本来は型を意識しなくて良いですが
pydanticは型付け検証を行ってくれるため、静的型付けを意識した開発ができます。
検証で使用したバージョンです。
pydantic==1.10.9
BaseSettingsを使ってみる
基本的な使い方です。
BaseSettings
を継承してクラスを定義します。
from pydantic import BaseSettings, Field
class Settings(BaseSettings):
sample: str | None = Field(default=None)
インスタンス変数sample
が環境変数名です。
sample
またはSAMPLE
の環境変数が設定されると値が自動的に設定されます。
Field
関数は変数に対して検証の追加設定を記載できます。
ここではデフォルト値をNone
にするよう設定しています。
この状態で下記コードを実行してみます。
from pydantic import BaseSettings, Field
class Settings(BaseSettings):
sample: str | None = Field(default=None)
# 環境変数なし
print(Settings().dict())
# 結果: {'sample': None}
# 環境変数を設定して出力
os.environ["sample"] = "settings_sample"
print(Settings().dict())
# 結果: {'sample': 'settings_sample'}
# 大文字で環境変数を設定
os.environ["SAMPLE"] = "SETTINGS_SAMPLE"
print(Settings().dict())
# 結果: {'sample': 'SETTINGS_SAMPLE'}
BaseSettings
はインスタンス生成後dict()
メソッドでインスタンス変数名:値
のdictを出力します。
環境変数設定前はNoneで出力されますが、設定後は値が出力されます。
インスタンス変数名と環境変数名を別にしたい
先程の例ではインスタンス変数sample
に対応する環境変数名は
sample
またはSAMPLE
でした。
Field
属性のオプションを使えば
インスタンス変数名と環境変数名を別々にすることが可能です。
class ENVSettings(BaseSettings):
api_key: str | None = Field(default=None, env="api_sample")
# 設定無し
print(ENVSettings().dict())
# {"api_key": None}
# envを指定するとenvの環境変数のみに対応されます。
os.environ["api_key"] = "aaaaabbbbbccccc"
print(ENVSettings().dict())
# {'api_key': None}
os.environ["api_sample"] = "dddddeeeeefffff"
print(ENVSettings().dict())
# {'api_key': 'dddddeeeeefffff'}
# 大文字も反映
os.environ["API_SAMPLE"] = "DDDDDEEEEEFFFFF"
print(ENVSettings().dict())
# {'api_key': 'DDDDDEEEEEFFFFF'}
Field
関数にenv
オプションを追加しました。
env
オプションは指定したキーを環境変数とみなしてくれます。
上記例では、インスタンス変数api_key
は環境変数api_sample
によって値が設定されることになります。
大文字と小文字で区別したい
先程までは環境変数名が大文字でも小文字でも設定が行われました。
BaseSetting
を使用すればで大文字と小文字の区別をすることが可能です。
class SettingsCase(BaseSettings):
name: str | None = Field(default=None, env="NAME_KEY")
class Config:
# 小文字・大文字区別を行う
case_sensitive = True
# 使用例
os.environ["name_key"] = "xxxxxyyyyyzzzzz"
print(SettingsCase().dict())
# envが大文字のため対応しない
# {'name': None}
os.environ["NAME_KEY"] = "XXXXXYYYYYZZZZZ"
print(SettingsCase().dict())
# {'name': 'XXXXXYYYYYZZZZZ'}
BaseSettings
を継承したクラスにConfig
のサブクラスを定義します。
継承先でcase_sensitive = True
にすると大文字・小文字の区別が行われます。
上記例だと、環境変数NAME_KEY
は値が設定されますが、name_key
では設定されません。
.envから環境変数を読み込みたい
実際の開発では.env
などの外部ファイルを使って環境変数の管理を行うと思います。
その場合は、pydantic[dotenv]
で.env
ファイル内の環境変数を読み込んでくれます。
pydantic[dotenv]
はpydantic + python-dotenv
の組み合わせで
いずれかの方法でインストールします。
pip install pydantic python-dotenv
# または
pip install pydantic[dotenv]
以下サンプルです。
.
├── .env
└── {実行ファイル}.py
DOT_ENV="dot_sample"
class DotSettings(BaseSettings):
dot_env: str = Field(default=None)
class Config:
env_file = ".env"
print(DotSettings().dict())
# {'dot_env': 'dot_sample'}
.env
を使用する場合は、サブクラスConfig
にenv_file
パラメータを付与します。
値に.env
ファイルパスを指定すれば設定完了です。
具体例
具体的な使用例です。
boto3.Session()
のパラメータを環境変数で渡す想定です。
class ProfileSettings(BaseSettings):
profile_name: str | None = Field(default=None, env="AWS_PROFILE_NAME")
class Config:
env_file = ".env"
case_sensitive = True
# None以外の項目をアンパックして渡す
session = boto3.Session(**ProfileSettings().dict(exclude_none=True))
.env
のAWS_PROFILE_NAME
から値を読み込みprofile_name
に値を挿入。
セッション作成時にパラメータをアンパックして渡します。
dictメソッド内でexclude_none=True
を設定しています。
これはインスタンス変数にNone
が指定されているパラメータを除外するオプションです。
開発環境ではプロファイル名を使用して、本番環境では使用しない。
といった制御を環境変数だけで実現できます。
まとめ
pydantic BaseSettings
で環境変数の取り扱いが楽になります。
今回は記述していませんが、BaseSettings
自体はクラスなのでメソッド定義も可能です。
関係ロジックを一纏めにできるメリットもあります。
是非使ってみてください!
Discussion