🐙

pydantic-settingsで環境変数を読み込む方法

に公開

今回はpydantic-settingsを用いて環境変数を読み込むと同時に、環境変数の値のバリデーションをしてみようと思います。

pydantic-settingsとは?

pydantic-settingsは環境変数やシークレットファイルを読み込んで利用するための機能をpydanticから拡張したものになっています。通常Pythonで環境変数を読み込むときはosモジュールを利用することが多いかと思いますが、pydantic-settingsを利用するとpydanticに備わっているバリデーション機能などを利用することができるため、より安全に環境変数などを扱うことができます。

https://docs.pydantic.dev/latest/concepts/pydantic_settings/#usage

早速使ってみる

今回はpydantic-settingsから.envの取り扱いおよび、pydanticからバリデーションの機能を紹介します。以下を参考にしていただければと思います。

https://docs.pydantic.dev/latest/concepts/pydantic_settings/#usage
https://docs.pydantic.dev/latest/concepts/validators/

環境構築

uvを利用して環境を整えます。

uv init pydantic_settings_env -p 3.12
cd pydantic_settings_env
uv add pydantic-settings

また、今回は以下のような環境変数を定義しようと思います。

.env
INT_VALUE=1
STRING_FROM_CHOICE=hoge

コードの実装

それではまずは以下のコードで環境変数を読み込みます。

load_env.py
from pydantic import Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict

class Envs(BaseSettings):
    INT_VALUE: int = Field(description="Int value")
    STRING_FROM_CHOICE: str = Field(description="String value")

    model_config = SettingsConfigDict(env_file='.env')

    @field_validator('STRING_FROM_CHOICE', mode='before')
    @classmethod
    def validate_string(cls, value: str) -> str:
        if value not in ["hoge", "fuga"]:
            raise ValueError("Invalid string was specified")

        return value

    @field_validator('INT_VALUE', mode='after')
    @classmethod
    def validate_int(cls, value: int) -> str:
        if value not in range(1, 101):
            raise ValueError("Invalid int was not in target range.")

        return value

print(Envs())

まずは必要な機能をインポートします。pydantic.Fieldはフィールドの定義をし、pydantic.field_validatorは読み込んだ環境変数のバリデーションに利用します。pydantic_settings.BaseSettingspydantic_settings.SettingsConfigDictが今回利用する環境変数の取り扱いに重要な要素になります。

pydantic-settingsで環境変数を扱うには、まずpydantic_settings.BaseSettingsを継承したクラスを定義する必要があります。また、そのクラス内でmodel_config = SettingsConfigDict(env_file=".env")を指定することで、このクラスで環境変数ファイルを読み込むことができるようになります。

class Envs(BaseSettings):
    model_config = SettingsConfigDict(env_file=".env")

この準備が完了すれば、あとはpydanticを利用するときの要領でFieldを定義します。今回はINT_VALUEとSTRING_FROM_CHOICEという名前の環境変数を取得したいので、それぞれフィールドを設定しています。

class Envs(BaseSettings):
    INT_VALUE: int = Field(description="Int value")
    STRING_FROM_CHOICE: str = Field(description="String value")

    model_config = SettingsConfigDict(env_file='.env')

これで環境変数を読み込むことはできますが、合わせてバリデーションもできるようにしてみます。今回はINT_VALUEは1~100の値で、STRING_FROM_CHOICEはhogeまたはfuga以外の値が指定されるとエラーになるようにしています。

class Envs(BaseSettings):
    ...
    @field_validator('STRING_FROM_CHOICE', mode='before')
    @classmethod
    def validate_string(cls, value: str) -> str:
        if value not in ["hoge", "fuga"]:
            raise ValueError("Invalid string was specified")

        return value

    @field_validator('INT_VALUE', mode='after')
    @classmethod
    def validate_int(cls, value: int) -> str:
        if value not in range(1, 101):
            raise ValueError("Invalid int was not in target range.")

        return value

それではこのコードを実行してみましょう。

uv run load_env.py

# 結果
INT_VALUE=1 STRING_FROM_CHOICE='hoge'

INT_VALUEは1~100に治っていますし、STRING_FROM_CHOICEもhogeで候補の中に入っているのでエラーは出ません。それでは次に環境変数を以下のようにして両方ともバリデーションに引っかかる内容にしてみます。

.env
INT_VALUE=1000
STRING_FROM_CHOICE=hogehoge

この設定で実行してみると、以下のようになりました。

uv run load_env.py

# 結果
Traceback (most recent call last):
  File "/Users/user/Documents/Blog/blog_materials/pydantic_settings_env/main.py", line 26, in <module>
    print(Envs())
          ^^^^^^
  File "/Users/user/Documents/Blog/blog_materials/pydantic_settings_env/.venv/lib/python3.12/site-packages/pydantic_settings/main.py", line 193, in __init__
    super().__init__(
  File "/Users/user/Documents/Blog/blog_materials/pydantic_settings_env/.venv/lib/python3.12/site-packages/pydantic/main.py", line 253, in __init__
    validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 2 validation errors for Envs
INT_VALUE
  Value error, Invalid int was not in target range. [type=value_error, input_value='1000', input_type=str]
    For further information visit https://errors.pydantic.dev/2.11/v/value_error
STRING_FROM_CHOICE
  Value error, Invalid string was specified [type=value_error, input_value='hogehoge', input_type=str]
    For further information visit https://errors.pydantic.dev/2.11/v/value_error

以下のエラーのように、指定された値(input_value)に対してValueErrorが出ているのが確認できました。

INT_VALUE
  Value error, Invalid int was not in target range. [type=value_error, input_value='1000', input_type=str]
    For further information visit https://errors.pydantic.dev/2.11/v/value_error
STRING_FROM_CHOICE
  Value error, Invalid string was specified [type=value_error, input_value='hogehoge', input_type=str]
    For further information visit https://errors.pydantic.dev/2.11/v/value_error

このようにバリデーションを入れることにより、読み込んだ環境変数が想定した内容であるかをチェックすることができます。

まとめ

今回はpydantic-settingsを用いて環境変数を読み込み、バリデーションする機能を作ってみました。環境変数を扱う場合はosモジュールの利用がよくみられますが、pydantic-settingsを利用することで信頼性の高いサービスの実装に寄与できると思うので、ぜひ使ったことがない方は使ってみてください。

Discussion