Python の Click、結構便利で簡単に CLI ツールが作れて LGTM ですね(← 使い方あってる?)。
その Click を使ってサブコマンドを実装するときに、ファイルを分割して実装するのに少々苦戦したので、そのことについて適当な例を使って実装の流れを書いてみようと思う。
間違いの指摘や、ここもっとこうした方がいいよ等の助言がありましたら、コメント頂けると幸いです。
ちなみに、サブコマンドを複数ファイルに分割して実装しようと思ったのは、「関心事は分離してそれぞれのファイルに分けた方が良いかなぁ」と思ったからである。
TL;DR
@click.command()
def cmd():
pass
from subcmd import cmd as subcmd
@click.group()
def cmd():
pass
cmd.add_command(subcmd)
バージョン
- Python: 3.8.0
- Click: 7.0
サンプルとして実装するもの
良い例が思いつかなかったので、足し算と引き算ができる calculate
コマンドを作ってみようと思う。
$ calculate addition 4 3
7
$ calculate subtraction 4 3
1
ディレクトリ構成
多少順番を変えたり省略したりしているが、下記のような感じである。
ご覧の様(?)に Pipenv を使っている(そろそろ Poetry か何かに乗り換えようかと思っているが、それはまた別のお話)。
calculate
├─ calculate
│ ├─ __init__.py
│ ├─ __main__.py
│ └─ cli
│ ├─ __init__.py
│ ├─ command.py
│ ├─ addition
│ │ ├─ __init__.py
│ │ └─ command.py
│ └─ subtraction
│ ├─ __init__.py
│ └─ command.py
├─ Pipfile
├─ Pipfile.lock
└─ setup.py
下準備
setup.py を書く
Pipenv を好んで使っている理由に、 pipenv install -de .
を実行して仮想環境に開発中のツールを編集可能な状態でインストールすることができる点がある(他のパッケージ管理ツールでもできるのかな?)。
そうすることで、仮想環境をアクティベートしたときにコマンドを実行できる。
コマンドを実行するためのエントリーポイントを setup.py に記述する。
下記の例では必要最低限しか記述していない。
from setuptools import find_packages, setup
setup(
name="calculate",
version="0.0.1", # 適当
entry_points={
"console_scripts": ["calculate=calculate.__main__:main"],
}
)
パッケージをインストールする
$ pipenv install click
$ pipenv install -de .
コマンドの実装
では、順番に実装を進めていこうと思う。
まずはエントリーポイントを用意する。
def main():
# TODO: コマンドを呼び出す
pass
# `python -m calculate` という形で呼び出されたときのことを考慮
if __name__ == "__main__":
main()
これで、calculate
コマンドを実行したときに、__main__.py
にある main
関数が呼び出される。
今はまだコマンドを実装していないので、main
関数の中身は空っぽにしておく。
次に main
関数で呼び出す最初のコマンドを実装する。
import click
@click.group()
def cmd():
pass
@click.group()
でデコレートすることで、cmd
に対してサブコマンドを追加できるようになる。
calculate
コマンドに相当する関数が実装できたので、先ほどの __main__.py
の main
関数から呼び出すようにする。
from .cli.command import cmd
def main():
cmd()
# `python -m calculate` という形で呼び出されたときのことを考慮
if __name__ == "__main__":
main()
これで実行してみると、さも CLI ツールのような出力が得られる。
$ calculate
Usage: calculate [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
やったね🎉
おっと、まだ目的は達成していなかった。
サブコマンドの実装
CLI ツールとしての基盤(?)が出来上がったので、次に足し算サブコマンドを実装していく。
import click
@click.command(name="addition")
@click.argument("augend", type=click.INT)
@click.argument("addend", type=click.INT)
def cmd(augend, addend):
click.echo(augend + addend)
@click.command
に name
を渡しているが、こうすることでコマンドに任意の名前を付けることができる。
@click.argument
では type
を渡すことで、バリデーションチェックを行ってくれるようになる。
click.echo
で結果を出力している。
ちなみに、augend は足される数、 addend は足す数、という意味である(TDD 本で知った)。
これで、足し算サブコマンドが実装できたので、calculate
コマンドに追加してみる。
import click
from .addition.command import cmd as addition_cmd
@click.group()
def cmd():
pass
cmd.add_command(addition_cmd)
グループにサブコマンドを追加するには、add_command
メソッドを呼び出し、その引数にサブコマンドとして追加する関数を与えるだけである。
ここまで長々と書いてきたが、これだけである。
このメソッドを見つけるまでにとても時間がかかった。。。
引き算サブコマンドの実装も同様である。
コマンドを実装して、
import click
@click.command(name="subtraction")
@click.argument("minuend", type=click.INT)
@click.argument("subtrahend", type=click.INT)
def cmd(minuend, subtrahend):
click.echo(minuend - subtrahend)
add_command
で追加する。
import click
from .addition.command import cmd as addition_cmd
from .subtraction.command import cmd as subtraction_cmd
@click.group()
def cmd():
pass
cmd.add_command(addition_cmd)
cmd.add_command(subtraction_cmd)
これで実行してみると、足し算、引き算の結果が得られる。
$ calculate
Usage: calculate [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
addition
subtraction
$ calculate addition 4 3
7
$ calculate subtraction 4 3
1
今度こそ、やったね🎉🎉
余談
@click.command(name=<another_name>)
の name
には、記号も与えることができるみたい。
なので、四則演算を実装した場合、次のように書ける。
$ calculate + 4 3
$ calculate - 4 3
$ calculate "*" 4 3
$ calculate / 4 3
*
はワイルドカードと認識されるので、クォーテーションで囲まないといけないし、/
は問題なく動くがシェルによってはルートと認識して色がつく。
需要があるかわからないけど、ちょっと面白いなと思ったので載せておきます。
試してないけど、他の記号も色々使えるはず。
まとめ
順番に実装の流れを書いていったので長くなったが、求めていた結果を得るために必要なものは、add_command
だった。
間違いの指摘や、ここもっとこうした方がいいよ等の助言がありましたら、コメント頂けると幸いです。