作ったもの
こんな感じにpydanticからArgumentParserを自動で生成するライブラリを作りました。
https://github.com/elda27/pydantic_argify
from argparse import ArgumentParser
from pydantic import BaseModel, Field
from pydantic_argify import build_parser
class Config(BaseModel):
string: str = Field(description="string parameter")
integer: int = Field(description="integer parameter")
parser = ArgumentParser()
build_parser(parser)
parser.print_help()
# config = Config(**vars(parser.parse_args()))
usage: basic.py [-h] --string STRING --integer INTEGER
optional arguments:
-h, --help show this help message and exit
Config:
--string STRING, -s STRING
a required string
--integer INTEGER, -i INTEGER
a required integer
pydanticとは
pydanticは、Pythonのライブラリで、データ検証と設定の管理に使用されます。
Pythonの標準ライブラリであるdataclassesの高機能版と言ってもいいかもしれません。
特徴は、データを型アノテーションに基づいてデータの検証を行い、適切な型への変換や不正なデータをフィルタリングすることができることです。
特に型の自動変換は非常に便利で、文字列→数値、文字列→floatはもちろん、メールアドレスやUUIDの書式検証・変換も行ってくれるので非常に便利で、私は愛用しています。
pydantic-argparse-builder
の他のライブラリとの違いについて
似たようなことができるライブラリとしてpydantic-argparse
やpydantic-cli
があります。
いずれも、同様にpydanticを用いて構築したクラスを基に、引数パーサーを作ることができます。
非常に便利で、pydanticを用いて構築したクラスを引数にした関数を1つ作れば、たくさんの引数をVSCodeの補完ありで実装できるため開発者体験がとても向上します。
私も自作ライブラリを作る前はよくお世話になっていました。
一方で、ArgumentParserを内部で隠蔽した設計のため、SubParserや引数のグループ化, 排他的引数など込み入った使い方をすることは容易ではありません。
特にSubParserをネストした使い方(例えば、aws s3 cp <引数>
)はいずれのライブラリも非対応のように思います。
使い方
冒頭でも説明しましたとおりですが、少し実用的な使い方について例を上げて使用方法を示します。
ライブラリのインストールは以下のとおりです。
pip install pydantic_argify
今回の例は先述のpydantic-argparse
やpydantic-cli
でも実現可能ですが、これがネストしたサブコマンドになると同じ様にすることは不可能です。
from argparse import ArgumentParser
from pydantic import BaseModel, Field
from pydantic_argify import build_parser
class SubConfigA(BaseModel):
string: str = Field(description="string parameter")
integer: int = Field(description="integer parameter")
class SubConfigB(BaseModel):
double: float = Field(description="a required string")
integer: int = Field(0, description="a required integer")
def main():
parser = ArgumentParser()
subparsers = parser.add_subparsers()
alpha_parser = subparsers.add_parser("alpha")
build_parser(alpha_parser, SubConfigA)
alpha_parser.set_defaults(_command="A")
build_parser(subparsers.add_parser("beta"), SubConfigB)
beta_parser = subparsers.add_parser("beta")
build_parser(beta_parser, SubConfigB)
beta_parser.set_defaults(_command="B")
parser.set_defaults(_command="help")
args = parser.parse_args()
if args._command == "A":
command_a(config=SubConfigA(**vars(args)))
elif args._command == "B":
command_b(config=SubConfigA(**vars(args)))
else:
parser.print_help()
def command_a(config: SubConfigA):
print(config)
def command_b(config: SubConfigB):
print(config)
if __name__ == "__main__":
main()
$ python -m example alpha -s main -i 10
string='main' integer=10
より単純な記法
from pydantic import BaseModel, Field
from pydantic_argify import sub_command, main
class SubConfigA(BaseModel):
string: str = Field(description="string parameter")
integer: int = Field(description="integer parameter")
class SubConfigB(BaseModel):
double: float = Field(description="a required string")
integer: int = Field(0, description="a required integer")
@sub_command("alpha")
def command_a(config: SubConfigA):
print(config)
@sub_command("beta")
def command_b(config: SubConfigB):
print(config)
if __name__ == "__main__":
main()
$ python -m example2 alpha -s main -i 10
string='main' integer=10
まとめ
今回は自作ライブラリについてご紹介しました。
一応、一通りのユースケースについては実装しているつもり担っていますが、まだまだ網羅できていない機能はたくさんあると思うので、もし使えないケース等あればissueを立てるなりでご教示いただきたいです。