setup.py, argparse, GitHub Actions あたりの技術を学ぶのに、Python でコマンドラインアプリを作ると良さそうだと思ったのでやってみました。ここでは個々の技術を詳しく解説しないので、記事中で紹介する公式ドキュメントや解説記事を参照してください。
成果物
To-Do リストアプリ を作りました。と言っても、
todo add "ほげ"
todo complete "ほげ"
todo remove "ほげ"
などで To-Do を追加、完了、削除するだけのものです。また、
todo complete --all
todo remove -a
で全て完了や全て削除ができます。To-Do リストは ~/.todo.json
に保存され、
$ todo show
□ ほげ
☑ ほげほげ
のように表示できます。
コマンドラインインターフェイスの作成
argparse を使ってコマンドライン引数の処理を定義します。
argparse は機能が多過ぎて把握しきれないですが、今回使ったのは以下です。コード全体はこちら。
parser = argparse.ArgumentParser()
# git commit とか docker run などの commit や run のようなもの
# をサブコマンドと名付けて追加
parser.add_argument('subcommand', help='add, complete, or remove')
# サブコマンドより後ろに来る引数を content とする。
# nargs は引数の数で、1つの場合は nargs=1
# nagrgs='*' とすると0以上の可変長引数になる
# nargs='+' は1以上の可変長引数
# nargs='?' は0または1個
# https://docs.python.org/ja/3/library/argparse.html#nargs
parser.add_argument('content', help='Content of To-Do', nargs='?')
# --all オプション (短縮形 -a) を追加
# 引数を取るオプションではなく、フラグなので、`store_true=True` を指定
parser.add_argument('-a', '--all', action='store_true', help='remove or complete all To-Dos')
なお、サブコマンドが show
のときや、remove --all
などのときは引数 'content' は必要ないのですが、指定できてしまいます。指定できないように argparse で書く方法が分からなかったため、引数が指定されても内部で無視するようにしています。
これを用いて、指定された引数に応じた処理を呼び出せば良いです(処理については特筆すべき知見はないのでソースコード参照)。
パッケージ化
setup.py
を書いてコマンドラインアプリを自動でインストールされるようにします。
書いた setup.py
はこちらにあります (書き方は pip や numpy のものを参考にしました) が、この中で肝心なのは以下の部分かと思います。
entry_points={
"console_scripts": [
"todo=todo.main:main"
]
},
このようにすることで、python3 setup.py install
したときに OS の PATH にコマンドがインストールされます。私の環境では以下のようになりました1。
$ which todo
/home/linuxbrew/.linuxbrew/bin/todo
$ cat $(which todo)
#!/home/linuxbrew/.linuxbrew/opt/python/bin/python3.7
# EASY-INSTALL-ENTRY-SCRIPT: 'todo==0.1.0','console_scripts','todo'
__requires__ = 'todo==0.1.0'
import re
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(
load_entry_point('todo==0.1.0', 'console_scripts', 'todo')()
)
これで、どのディレクトリにいようとも、ターミナルで todo と叩くと todo パッケージの main モジュール内の main という関数が呼ばれて、書いた処理が実行されます。
pip でインストール
先程は setup.py
を実行してインストールしましたが、以下のように pip を用いて Git リポジトリからもインストールできます。汎用性のあるものコマンド、パッケージであれば PyPI に登録するのも良いでしょう。
pip3 install git+https://github.com/pn11/python-command-line-sample
GitHub Actions を用いた自動テスト
いつの間にか2 GitHub に追加されていた Actions というのを使ってみたいと思ったのが本記事執筆のきっかけです3。今回は無難に自動テストをしてみます。
とりあえず、ToDo リストを全部消してから追加するだけのクソみたいなテストを書きました。
import todo.main
def test_add():
todo.main._remove_todo('', all_flag=True)
todo.main._add_todo('foo')
dic = todo.main._load_todos()
assert dic['foo'] == 'Not Yet'
これを GitHub Actions を使って nose で実行してみたいと思います。
Action 用 YAML 作成
リポジトリの Actions のタブに行くと、以下のような画面になるので、とりあえず Python package
でやってみます。
以下のような画面になるので、何も考えずとりあえず Start Commit
を押してみます。
テストに失敗します。
YAML を見てみると以下のようになっています。
name: Python package
on: [push]
jobs:
build:
runs-on: ubuntu-latest
strategy:
max-parallel: 4
matrix:
python-version: [2.7, 3.5, 3.6, 3.7]
steps:
- uses: actions/checkout@v1
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Lint with flake8
run: |
pip install flake8
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
pip install pytest
pytest
何となく、steps:
以下の
- name: やること
run: |
コマンド1
コマンド2
みたいなところをいじれば良さそうです。ということで、以下のような処理を書きました。
- name: Install Package
run: |
python setup.py install
- name: Test with nose
run: |
pip install nose
nosetests
setup.py
で To-Do のパッケージをインストールした後、nose もインストールしてテストを実行します。
これをコミットすると無事にテストが通りました。実行ディレクトリとかは特に意識しませんでしたが、リポジトリのルートで実行されているようです。また、何となく nose を使いましたが、特にライブラリに依存したテストではないので元のまま pytest でも動くはず?
今後はコミットする度にテストが走ります。テストに通ると マークがついて良い感じです。
まとめ
以上、argparse でコマンド作成、setup.py でパッケージ化、GitHub Actions で自動テストをやってみました。今後はこの方法でクソコマンドを量産していきたい所存です。
その他
この記事では取り上げませんでしたが、コマンドラインツール作成の際には argparse 以外にも、click 等を使うのも良いかも知れません。
- Clickで手軽にPythonのコンソールアプリケーションを作る - Qiita
- Awesome Python:素晴らしい Python フレームワーク・ライブラリ・ソフトウェア・リソースの数々 - Qiita
-
Linuxbrew で入れた Python を使用しています。 ↩
-
[速報]GitHub Actionsが正式版に。GitHub内でビルド/テスト/デプロイなど実行、CI/CDを実現。GitHub Universe 2019 - Publickey ↩
-
MS に買収されてからどんどん新機能が追加されていてしゅごい。。 ↩