Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

BaseSettingsでCLI引数の衝突を回避

Posted at

概要

複数のBaseSettingsサブクラスをインスタンス化するときの、CLI引数の衝突回避方法についてです。
結論としては、cli_ignore_unknown_args=Trueを与えるとよいです。

環境変数とCLI引数でオプション名が違うことに気づかなくて数時間解決にかかったのでメモを残します。

背景

pydantic-settingsのBaseSettingsは環境変数とCLI引数を同時に定義することができて便利です。
複数の箇所でBaseSettingsを使用していて、それらを両方インスタンス化する必要があるとき、CLI引数の衝突がおこることがあります。
つまり、一方には存在し、もう一方には存在しない環境変数やCLI引数があるとエラーになってしまいます。

一方に存在しないCLI引数を指定してインスタンス化させてみます。

from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict


class SettingsA(BaseSettings):
    name: str = Field(default="", description="名前")
    title: str = Field(default="", description="タイトル")

    model_config = SettingsConfigDict(
        env_file=None,
        case_sensitive=False,
        cli_parse_args=True,
        extra = "ignore"
    )


class SettingsB(BaseSettings):
    title: str = Field(default="", description="タイトル")

    model_config = SettingsConfigDict(
        env_file=None,
        case_sensitive=False,
        cli_parse_args=True,
        extra = "ignore"
    )


def test_settings_with_cli_args():
    """CLIからの引数でSettingsインスタンスを作成するテスト"""
    import sys

    # SettingsAのテスト
    sys.argv = [
        "script.py",
        "--name=test_user",
        "--title=test_title",
    ]

    settings_a = SettingsA()
    settings_b = SettingsB(
        title=settings_a.title,
    )


if __name__ == "__main__":
    test_settings_with_cli_args()

実行するとunrecognized argumentsでエラーになります。

usage: script.py [-h] [--title str]
script.py: error: unrecognized arguments: --name=test_user

一方でCLI引数ではなく環境変数を用いた場合にはエラーなく実行されます。

def test_settings_with_envs():
    """環境変数でSettingsインスタンスを作成するテスト"""
    import os

    # SettingsAのテスト1
    os.environ["NAME"] = "test_user"
    settings_a = SettingsA()
    settings_b = SettingsB(title = settings_a.title)


if __name__ == "__main__":
    # test_settings_with_cli_args() # エラー
    test_settings_with_envs()  # 成功

これはextra="ignore"を指定することで、不要な環境変数が無視されるためです。
一方で、extra="ignore"を指定してもCLI引数の衝突は無視できません。

解決方法

model_configでcli_ignore_unknown_args=Trueを指定すると引数の衝突を回避できます。

class SettingsB(BaseSettings):
    title: str = Field(default="", description="タイトル")

    model_config = SettingsConfigDict(
        env_file=None,
        case_sensitive=False,
        cli_parse_args=True,
        cli_ignore_unknown_args=True,  # 追記
        extra="ignore",
    )

おわりに

オプション引数の存在に気づかず、model_config.cli_parse_argsのオーバーライドなどいろいろ検討して時間が過ぎてしまいました。
同じことでハマった方の参考になれば幸いです。

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?