nikkie-ftnextの日記

イベントレポートや読書メモを発信

pydantic-settings素振りの記:ネストした設定 〜BaseModel継承クラスが全ての属性でデフォルト値を持つならば、インスタンス化してBaseSettings側のデフォルト値とする〜

はじめに

1日1エントリでついに「765のななー」 nikkieです。

コードを見せずにタイトルだけで表現するのがなかなか難しかったのですが、pydantic-settingsの素振りの様子をお届けします。

目次

pydantic-settings

FastAPIなどでおなじみのパースライブラリ Pydantic1
パースすることで実行時の型のバリデーションとして機能し、とても便利です。

そんなPydanticの機能を設定に適用できるのが、pydantic-settings!

Pydantic Settings provides optional Pydantic features for loading a settings or config class from environment variables or secrets files.

pip install pydantic-settingsして使い始められます。

pydantic_settingsのBaseSettingsについて、少し込み入った例で素振りします。

BaseModel継承クラスの属性を、環境変数から設定できる

今回の素振りの題材は、「環境変数からConfigを設定できる」。
こんな感じです

% AWESOME_STRING=awesome AWESOME_NUMBER=765 uv run script.py
awesome=AwesomeModel(string='awesome', number=765)

参照したのは https://docs.pydantic.dev/latest/concepts/pydantic_settings/#parsing-environment-variable-values

from pydantic import BaseModel
from pydantic_settings import BaseSettings, SettingsConfigDict


class AwesomeModel(BaseModel):
    string: str
    number: int


class Config(BaseSettings):
    model_config = SettingsConfigDict(env_nested_delimiter="_")

    awesome: AwesomeModel


config = Config()
print(config)
  • pydantic_settings.BaseSettingsを継承したConfigクラスに、AwesomeModelpydantic.BaseModelを継承)を持たせています
  • AwesomeModelstringnumberを持ちます
    • これらを環境変数から指定する際の書式を指定するのがenv_nested_delimiter="_"
  • 以下の環境変数で設定できます
    • AWESOME_STRING -> AwesomeModelstringを指定
    • AWESOME_NUMBER -> AwesomeModelnumberを指定

BaseModel継承クラスの属性にデフォルト値を持たせ、環境変数から上書きできる

AwesomeModelにはデフォルト値を設定できます。
デフォルト値を使えばよいとき、ユーザは環境変数での指定を省略できるわけです。

説明のためにFabulousModelを追加しました。

class FabulousModel(BaseModel):
    string: str = "fabulous"
    number: int = 108


class Config(BaseSettings):
    model_config = SettingsConfigDict(env_nested_delimiter="_")

    fabulous: FabulousModel = FabulousModel()  # デフォルト値の設定がポイント


config = Config()
print(config)

Configには、すべてデフォルト値を持ったBaseModel(を継承したクラス)を持たせています。
このときのポイントは、FabulousModelインスタンス化して、デフォルト値に設定している点です2
これをしていないと、値が指定されていないという解釈になり、Configが作れません。
https://docs.pydantic.dev/latest/errors/validation_errors/#missing

pydantic_core._pydantic_core.ValidationError: 1 validation error for Config
fabulous
  Field required [type=missing, input_value={}, input_type=dict]

デフォルト値を使う例

% uv run script.py
fabulous=FabulousModel(string='fabulous', number=108)

環境変数で上書きする例

% FABULOUS_STRING=foo FABULOUS_NUMBER=283 uv run script.py
fabulous=FabulousModel(string='foo', number=283)

素振りのまとめ:ネストした設定の例

AwesomeModelも使うとこんな感じです。

class Config(BaseSettings):
    model_config = SettingsConfigDict(env_nested_delimiter="_")

+    awesome: AwesomeModel
    fabulous: FabulousModel = FabulousModel()
% AWESOME_STRING=awesome AWESOME_NUMBER=765 uv run script.py   
awesome=AwesomeModel(string='awesome', number=765) fabulous=FabulousModel(string='fabulous', number=108)

AwesomeModelにデフォルト値を一部設定してもこのままで動きます。

今回の学び(終わりにに代えて)

当初以下のように書いていて、missingのエラーが出ていました。

class Config(BaseSettings):
    model_config = SettingsConfigDict(env_nested_delimiter="_")

    awesome: AwesomeModel
    fabulous: FabulousModel

ステップ・バイ・ステップで手を動かしたところ、全てデフォルト値が指定されているBaseModel(を継承したクラス)の場合は、BaseSettingsを継承したクラスでインスタンスをデフォルト値として持たせる必要があると分かりました。

素振りの例でさらにAwesomeModelnumberにもデフォルト値を設定するならば、Configではawesome: AwesomeModel = AwesomeModel()インスタンスをデフォルト値にすることになります(全ての属性がデフォルト値を持つFabulousModelと同様というわけです)


  1. 過去記事より
  2. このあたりが関係するんでしょうか?「Unlike pydantic BaseModel, default values of BaseSettings fields are validated by default.https://docs.pydantic.dev/latest/concepts/pydantic_settings/#validation-of-default-values