Pythonで個人用CLIツールを作成する
前書き
Pythonを使って、個人用CLIツールを作成するときの手順と参考になるリソースのまとめです。
最終的に、こんなイメージで使えるようにします。(例: note
というコマンドを作成)
# 仮想環境に入る % source env/bin/activate # 自作ツールのpip install (env) % pip install --editable . # 自作ツールを使う (env) % note "abc def efg"
GitHub
サンプルコードをgithubにアップロードしました。
GitHub - Kyutatsu-sandbox/python-cli-for-blog: sample code for blog post.
参考リンク
setup.pyを書いてパッケージ化する方法
How To Package Your Python Code — Python Packaging Tutorial
pythonのsetup.pyについてまとめる - Qiita
コマンドの引数を扱う標準ライブラリArgparse公式チュートリアル
Argparse チュートリアル — Python 3.9.1 ドキュメント
ターミナルで文字に色をつけるためのPythonライブラリ
環境
バージョン | |
---|---|
MacOS Catalina | 10.15.6 |
Python3 | 3.9.0 |
※ 古い方のMacを使ったのでCatalinaに戻ってます。誤植ではありません。
setup.pyを作成する
まずは仮想環境に入ります。
% python3.9 -m venv env % source env/bin/activate
メインとなるpythonファイル(cli.py
)とセットアップ用のファイルsetup.py
を作成します。
(env) % touch {cli,setup}.py # このような構成になっていればOK (env) % tree -L 1 . ├── cli.py ├── env └── setup.py
まずcli.py
には、処理の入り口となる関数を定義します。コマンドを叩いたときに呼ばれる関数です。
- cli.py
from termcolor import colored, cprint def main(): print('STDOUT') return 'RETURN' def color(): cprint('STDOUT', 'green') text = colored('RETURN', 'red') return text
二つ関数を定義しました。color関数では、参考リンクに記述したライブラリtermcolor
を利用しています。
ANSIカラー文字列を付加するwrapperです。ターミナル上に出力される文字列に色をつけることができます。
次にsetup.py
を書きます。
from setuptools import setup setup( name='mycli-package', version='1.0.0', install_requires=['termcolor>=1.1.0'], entry_points={ "console_scripts": ['mycli = cli:main', 'mycli_color = cli:color'] } )
とりあえず自分で作成したツールを使う上では上記の設定だけ分かっていれば事足りると思います。 詳しい説明は、参考リンクとして挙げた記事などを参照してください。
少し補足します。
install_requires
依存パッケージです。ここに書いておいたパッケージが、自作ツールをpipで入れるときに一緒にインストールされます。
package==1.0.0
(バージョン1.0.0を必ずいれる) のようなバージョン指定もできますし、単にpackage
と言うふうに名称を書くだけでもOKです。
上記の例では1.1.0以上、としています。
entry_points
実際に、「コマンド(上例だとmycli
, mycli_color
)」と「実行される関数(上例だとmain
, color
)」をマッピングします。
console_scripts
というキーは決まっていて、CLIツールとして利用するコマンドをこのエントリーポイントへ書くことになっています(console_scripts Entry Point)。
pipでインストールする
ではコマンドを使ってみます。
まずは別の仮想環境を作成し、そちらに入ります。 (依存インストールなどがうまくいくことを確認するために行う手順です。ツール作成に必須ではありません)
% python3.9 -m venv another_env % source another_env/bin/activate # 出力なし。 (another_env) % pip freeze
先ほど作成したsetup.pyが存在するディレクトリへのパスを指定し、以下のようにしてインストールを行います。
(--editable
については後述)
# setup.pyがあるディレクトリ
(another_env) % pip install --editable /Users/yourname/XXXX/python-cli-tool-template
pip freezeで確認します。
依存ライブラリとして設定したtermcolor
も一緒にインストールされていることがわかります。
(another_env) % pip freeze # Editable Git install with no remote (mycli-package==1.0.0) -e /Users/yourname/XXXX/python-cli-tool-template termcolor==1.1.0
ではコマンドを使ってみましょう!
mycli
コマンドで黒色の文字、mycli_color
で色付き文字がプリントされていることがわかります。
retrunした値は標準エラーに出力される
結果を見ると気が付くと思いますが、printで出力した文字だけでなく、returnした値も表示されています。
returnした値は標準エラー扱いです。
標準出力(1)を/dev/null
に捨てる、もしくは標準エラー(2)を捨てると以下のようになります。
(another_env) kyutatsu@Kmbp Temp % mycli 1> /dev/null RETURN (another_env) kyutatsu@Kmbp Temp % mycli 2> /dev/null STDOUT
普通に使っているとどちらも表示されてしまいますが、以上のような挙動になっているので気をつけます。(詳しい理由と仕組みを知っている方がいれば教えていただけると幸いです)
--editable
オプションをつけるとコードへの変更が即反映される
先ほどツールを入れるときに指定した--editable
オプションですが、これは「develop mode」でのインストールになります。
ではdevelop modeとはなんぞやと言うことになりますが、順番に辿っていこうと思います。
実際の挙動
まずは挙動をみてみます。 先ほどインストールした自作ツールに変更を加えます。
- cli.py
def main(): print('@@@@@@@@@@@@@STDOUT@@@@@@@@@@') return 'RETURN'
保存してファイルを閉じ、mycli
コマンドを打ってみます。
(another_env) % mycli @@@@@@@@@@@@@STDOUT@@@@@@@@@@ RETURN
変更が反映されています。
では、--editableをつけなかった場合はどうなるのでしょうか。
まずcli.pyを元に戻します。
def main(): print('STDOUT') return 'RETURN'
別の環境でインストールを行います。
# 別の環境 % python -m venv not_editable % source not_editable/bin/activate # 何もないことを確認 (not_editable) % pip freeze
MuduleNotFoundErrorを回避する
注意 Editableモードならこのままインストールすれば良いのですが、通常インストールではpackageやmoduleの場所を指定する必要があります。
今回は、setup.pyと同じディレクトリにモジュール(cli.py
)を置いているので、以下のように追記します。このように書かないと、moduleを見つけることができずにエラーとなります。
- setup.py
setup( # .......(省略).......... py_modules = ['cli'], # .......(省略)..........
ではインストールしていきます。
% pip install /User/yourname/XXXX/python-cli-tool-template
ここでfreezeしてみると、先ほどとは出力が違っていることがわかります! 自作ツールについていたはずのコメントなどや、editableモードであることを示す記述がなくなり、通常のライブラリと同じようにprintされています。
% pip freeze mycli-package==1.0.0 termcolor==1.1.0
今ここで、cli.pyを編集してみます。
def main(): print('--------------STDOUT^---------------') return 'RETURN'
次にmycliコマンドを実行します。 先ほどと異なり、編集した内容が反映されません
反映されるには、パッケージを通常と同じように更新する必要があります
まずsetup.pyのバージョンもあげておきましょう。
- setup.py
from setuptools import setup setup( name='python-cli-tool-template', version='1.1.0', # バージョンあっぷ! # ....(省略)......... )
インストールします。
(not_editable)% pip install --upgrade ~/MyProjects/GitHubForBlog/python-cli-tool-template # 確認すると、バージョンが上がっているはず。 (not_editable) % pip freeze python-cli-tool-template==1.1.0
挙動も更新されました!
(not_editable) % mycli --------------STDOUT^--------------- RETURN
editableモードの説明をドキュメントからたどる
まずはpipのヘルプをみてみます。
% pip install --help | grep -A 2 editable -e, --editable <path/url> Install a project in editable mode (i.e. setuptools "develop mode") from a local project path or a VCS url.
(余談: grep
の -A 2
は該当する行+後ろ2行も表示、と言う意味です)
「editable mode」は setuptoolsの「develop mode」を意味していることがわかります。
そこでsetuptools のドキュメントから該当箇所を探します。
Building and Distributing Packages with Setuptools — setuptools 51.1.1 documentation
Deploy your project in “development mode”, such that it’s available on sys.path, yet can still be edited directly from its source checkout.
ソースを直接変更できると言うことが書かれています。
さらに詳しい箇所がありました。 “Development Mode” — setuptools 51.1.1 documentation
It works very similarly to setup.py install, except that it doesn’t actually install anything. Instead, it creates a special .egg-link file in the deployment directory, that links to your project’s source code.
つまりeditableモードでinstallをした場合にはinstall先の仮想環境には何もインストールされず、直接ソースコードとコマンドがマップされるみたいですね。
Argparseでコマンドに引数を渡す
公式チュートリアルがあるので紹介程度に留めますが、作成したコマンドの引数を処理するには標準ライブラリArgparseが利用できます。
Argparse チュートリアル — Python 3.9.1 ドキュメント
- 自動でヘルプ(
-h
)作成 - 同時に使えない引数の設定や、可変長引数などの設定
など、sys.args
を直接パースしていては苦しい処理が楽にかけます。
上記チュートリアルを完了したら、以下のadd_argument
関数のドキュメント部分を読めば大抵のことができると思います。
argparse --- コマンドラインオプション、引数、サブコマンドのパーサー — Python 3.9.1 ドキュメント
また、CLIツールを作成するフレームワークも存在します。本格的なCLIツールを作成したければそちらを使った方がいいかもしれません。 (自分は使ったことがないので、リンクの紹介のみです)