nikkie-ftnextの日記

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

pydantic-settingsで環境変数からもオプション引数を指定できるCLIを作る 〜サブコマンド篇〜

はじめに

幕が上がる 瞬間が好き♪ nikkieです。

pydantic-settingsでCLIの素振りの続き、今回はサブコマンドまわりを触ります。

目次

pydantic-settingsでCLIが作れる!

パースライブラリPydanticを設定にも適用するpydantic-settings。
なんと設定だけでなく、CLIアプリケーションも作れちゃいます!

pydantic-settingsで作るCLIのよさは、オプション引数を環境変数からも指定できること。
標準ライブラリのargparseでこれを実現するのはいささか骨が折れそうなのですが、pydantic-settingsではデフォルトでサポートされているんです!
argparseすきすきの民の私ですが、これは乗り換えを決意するのに十分すぎる理由でした(なので素振りをしています)

前回の素振りより、オプション引数・位置引数の扱いが分かっています。
pydantic_settings.BaseSettingsを継承したクラスにて

  • オプション引数:string: str = Field(validation_alias=AliasChoices("s", "string"))
    • -sã‚„--stringで指定できる
    • 環境変数Sã‚„STRINGでも指定できる(素晴らしすぎる!!👏)
    • デフォルト値を持たせることも可能(Field("デフォルト値", validation_alias=...))
  • 位置引数:string: CliPositionalArg[str]
    • 位置引数は環境変数からは指定できません(位置で指定しましょう)
    • 位置引数にデフォルト値は持たせられない模様1

サブコマンド

Rustのclapで素振りしたのと同様なCLIを今回作りました2。

% uv run --quiet cli.py position エミリー 5
エミリーエミリーエミリーエミリーエミリー
% OPTION_S=エミリー uv run --quiet cli.py option --num 5
エミリーエミリーエミリーエミリーエミリー

動作環境

  • uv 0.4.27 (Homebrew 2024-10-25)
  • Python 3.13.0
  • pydantic-settings 2.7.0
    • Pydantic 2.10.4

ドキュメント「Subcommands and Positional Arguments」より
https://docs.pydantic.dev/latest/concepts/pydantic_settings/#subcommands-and-positional-arguments

class Settings(BaseSettings):
    position: CliSubCommand[Position]
    option: CliSubCommand[Option]
  • サブコマンドの実体は
    • PydanticのBaseModelを継承したクラス
    • または、pydantic.dataclasses.dataclassでデコレートしたクラス
    • (IMO:サブコマンドにはBaseSettingsは継承しないんですね!)
  • BaseSettingsを継承したクラス(コマンドの大元)では、CliApp.get_subcommand()を使う
  • 設定のとき3と同様に、環境変数は「BaseSettings継承クラスの属性名」+「区切り文字」+「サブコマンドのエイリアス」でした
    • 区切り文字はenv_nested_delimiterでアンダースコアを指定
    • 列挙すると、OPTION_S・OPTION_STRING・OPTION_N・OPTION_NUM
  • (前回記事より)CliApp.run(Settings)でSettingsインスタンスのcli_cmd()メソッドを呼び出してます

サブコマンドもできた〜!🙌

なお、Noteに制約の記述が

it does not allow for multiple subparsers with each subparser having its own set of subcommands.

直面したわけではないのでピンときてはいないのですが、argparseみたいにサブコマンド・サブサブコマンドとネストさせられないってことですかね?
BaseSettingsを継承したクラスをCliSubCommandアノテーションできないようですし。
argparseでは使うことがあったのでちょっと痛いかもしれないですが、サブサブコマンドが必要になったときに考えようかと思います(サブコマンドだけに合わせて、コマンドの設計を見直すことになりそう)

終わりに

pydantic-settingsで作るCLIで、サブコマンドの作り方を完全に理解しました!
clapの素振りでも書きましたが、位置引数・オプション引数・サブコマンドを押さえたので、argparseで書いているCLIの8割くらいは、pydantic-settingsでも書けそうです(※馬鹿の山感)。

Pydanticですから書いてて型がバチバチ当たりますし、環境変数からオプションを指定できるのは広く使われるCLIは備えている印象のある便利機能で、pydantic-settingsは推したいライブラリになりました。
君が天才!


  1. argparse(やRustのclap)ではできていました。位置引数のデフォルト値をそれほど使っていない私としては、あまり痛くはないです
  2. uvがinline script metadataを読んでいるという出力(「Reading inline script metadata」)をしなくする--quietを見つけました。https://docs.astral.sh/uv/reference/cli/#uv-run
  3. 設定の素振りの様子:pydantic-settings素振りの記:ネストした設定 〜BaseModel継承クラスが全ての属性でデフォルト値を持つならば、インスタンス化してBaseSettings側のデフォルト値とする〜 - nikkie-ftnextの日記