5
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Click(Python) のサブコマンドをファイルを分割して実装する

Posted at

Python の Click、結構便利で簡単に CLI ツールが作れて LGTM ですね(← 使い方あってる?)。
その Click を使ってサブコマンドを実装するときに、ファイルを分割して実装するのに少々苦戦したので、そのことについて適当な例を使って実装の流れを書いてみようと思う。
間違いの指摘や、ここもっとこうした方がいいよ等の助言がありましたら、コメント頂けると幸いです。

ちなみに、サブコマンドを複数ファイルに分割して実装しようと思ったのは、「関心事は分離してそれぞれのファイルに分けた方が良いかなぁ」と思ったからである。

TL;DR

subcmd.py
@click.command()
def cmd():
    pass
cmd.py
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 に記述する。
下記の例では必要最低限しか記述していない。

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 .

コマンドの実装

では、順番に実装を進めていこうと思う。
まずはエントリーポイントを用意する。

__main__.py
def main():
    # TODO: コマンドを呼び出す
    pass

# `python -m calculate` という形で呼び出されたときのことを考慮
if __name__ == "__main__":
    main()

これで、calculate コマンドを実行したときに、__main__.py にある main 関数が呼び出される。
今はまだコマンドを実装していないので、main 関数の中身は空っぽにしておく。

次に main 関数で呼び出す最初のコマンドを実装する。

cli.commnad.py
import click

@click.group()
def cmd():
    pass

@click.group() でデコレートすることで、cmd に対してサブコマンドを追加できるようになる。
calculate コマンドに相当する関数が実装できたので、先ほどの __main__.pymain 関数から呼び出すようにする。

__main__.py
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 ツールとしての基盤(?)が出来上がったので、次に足し算サブコマンドを実装していく。

cli.addition.command.py
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.commandname を渡しているが、こうすることでコマンドに任意の名前を付けることができる。
@click.argument では type を渡すことで、バリデーションチェックを行ってくれるようになる。
click.echo で結果を出力している。
ちなみに、augend は足される数、 addend は足す数、という意味である(TDD 本で知った)。

これで、足し算サブコマンドが実装できたので、calculate コマンドに追加してみる。

cli.command.py
import click
from .addition.command import cmd as addition_cmd

@click.group()
def cmd():
    pass

cmd.add_command(addition_cmd)

グループにサブコマンドを追加するには、add_command メソッドを呼び出し、その引数にサブコマンドとして追加する関数を与えるだけである。
ここまで長々と書いてきたが、これだけである。
このメソッドを見つけるまでにとても時間がかかった。。。

引き算サブコマンドの実装も同様である。
コマンドを実装して、

cli.subtraction.command.py
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 で追加する。

cli.command.py
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 だった。

間違いの指摘や、ここもっとこうした方がいいよ等の助言がありましたら、コメント頂けると幸いです。

5
8
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
5
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?