開発メモ

Python/TyperでCLIツール開発メモ

January 30, 2021

  • pythonでCLIツール作るときのライブラリとして,Clickが有名
  • これを更に簡単に使えるようにしたライブラリとしてTyperがある
  • これを使って開発したときにつまづいたところのメモ

やりたいこと

  • 複数のサブコマンドをもつコマンドを作る
    • fooコマンドが,2つのサブコマンドbarbazを持つ
    • barコマンドは,1機能しか持たない(サブコマンドを持たない)が,単体実行したいかつモジュール(実装ファイル)を分けたいのでサブコマンドとしたい
    • bazコマンドは,さらに2つのサブコマンドquxcorgeを持つ
1# コマンドイメージ
2$ foo bar <arg>
3...(some output)...
4
5$ foo baz qux <arg>
6...(some output)...
7
8$ foo baz corge <arg>
9...(some output)...
  • また,各サブコマンドは単体でも実行できるようにする
1# コマンドイメージ
2$ bar <arg>
3...(some output)...
4
5$ baz qux <arg>
6...(some output)...
7
8$ baz corge <arg>
9...(some output)...
  • 実装のファイル構成イメージは以下
my_cli
├── commands
│   ├── bar.py
│   └── baz.py
└── foo.py

つまづきポイント

  • Typerのコマンドは,登録されたコマンドが1つしかない場合,サブコマンドなしで実行できる
実装
 1import typer
 2
 3barapp = typer.Typer(add_completion=False)
 4
 5
 6@barapp.command()
 7def main(arg: str):
 8    typer.echo(f"This is bar command: {arg}")
 9
10
11if __name__ == "__main__":
12    barapp()
実行結果
 1$ python commands/bar.py --help
 2Usage: bar.py [OPTIONS] ARG
 3
 4Arguments:
 5  ARG  [required]
 6
 7Options:
 8  --help  Show this message and exit.
 9
10$ python commands/bar.py hoge
11This is bar command: hoge
  • しかし,このコマンドを,他のコマンドのサブコマンドとして登録すると,サブコマンドなしで実行できなくなる
実装
 1import typer
 2from commands import bar
 3
 4fooapp = typer.Typer(add_completion=False)
 5fooapp.add_typer(bar.barapp, name="bar")
 6fooapp.add_typer(baz.bazapp, name="baz")
 7
 8
 9if __name__ == "__main__":
10    fooapp()
実行結果
 1$ python foo.py bar --help
 2Usage: foo.py bar [OPTIONS] COMMAND [ARGS]...
 3
 4Options:
 5  --help  Show this message and exit.
 6
 7Commands:
 8  main
 9
10# `foo bar hoge` は実行不可
11$ python foo.py bar main hoge
12This is bar command: hoge

解決

  • fooapp.add_typer(...)でtyperごと登録するのはだめなので,fooapp.command(name=<サブコマンド名>)(<外部定義関数>)で外部ファイルの関数だけ登録する
実装
 1import typer
 2from commands import bar
 3
 4fooapp = typer.Typer(add_completion=False)
 5# fooapp.add_typer(bar.barapp, name="bar")
 6fooapp.command(name="bar")(bar.main)
 7fooapp.add_typer(baz.bazapp, name="baz")
 8
 9
10if __name__ == "__main__":
11    fooapp()
実行結果
 1$ python foo.py bar --help
 2Usage: foo.py [OPTIONS] ARG
 3
 4Arguments:
 5  ARG  [required]
 6
 7Options:
 8  --help  Show this message and exit.
 9
10$ python foo.py bar hoge
11This is bar command: hoge

実装例

Tagged: #Python #Typer #CLI