Help us understand the problem. What is going on with this article?

InvokeをPython環境でタスクランナーにする

More than 1 year has passed since last update.

JX通信社アドベントカレンダー2日目を担当します、TatchNicolasと申します。
今年(2018年)の8月に入社し、主にサーバサイドの開発を担当しています。

いきさつ

Pythonの環境分離にはいろいろな方法がありますが、私はJediでの補完が効かせやすく、環境の作り直しも簡単なPipenv(+Pyenv)を使っています。

しかしPipenvの独自コマンド定義機能であるscriptsでは、 Node.jsのpackage.jsonのように&&によるコマンドの連続実行が定義できません。
https://pipenv.readthedocs.io/en/latest/advanced/#custom-script-shortcuts

list表記を使うなどして複数コマンドの一括実行を実装してほしいという声もあるのですが、なかなか実現しなさそうな様子です。
(GitHub Issue "How to combine multiple command in one script?")

ならば古き良きMakefileで!とも考えたのですが、普段書き慣れていない構文を毎度調べる・思い出すのも大変です。
そこで、上記IssueのなかでInvokeなるものが言及されていましたので、紹介したいと思います。

Invokeとは

Pythonでいろいろなタスクを定義できるライブラリで、コマンドラインから操作するタスクを手軽に定義できます。

InvokeとPipenvと組み合わせて何が嬉しいか

  • タスクの実行環境自体をPipenvに内包できる
    • Djangoやgunicornのコマンドをその依存関係とセットでPipenvの環境内に持つことが出来る
  • Pythonの豊富なライブラリを使える
  • 書き慣れたPythonの構文で様々なタスクを定義できる
    • 文字列操作、ループやIFの条件を気軽に書ける
  • コマンドをpipenv run <task name>の形式から、inv <task name>の形式へと短くできる
    • pipenv shellで仮想環境をオンにしている前提です
    • 仮想環境の外からなら、pipenv run inv <task name>の形式で呼び出せます。
  • Invokeのタスクは別ファイルに定義されるので、Pipfileが散らからない

やってみる

記事公開時の実行環境

  • Ubuntu 18.04
  • Python 3.7.0
  • Pipenv 2018.7.1

Pipenvの導入、概要については公式ドキュメントをご参照ください。

invokeのインストール

Pipenvを使ってdev-packagesにインストールします。

$ pipenv install --dev invoke

タスクの定義

tasks.pyというファイルを作成し、その中にタスクを定義していきます。
ターミナル上のコマンドはinvoke.run()という関数の中に文字列で書きます。

tasks.py
import invoke


@invoke.task
def hello(c):
    invoke.run('echo "hello invoke!"')

タスクの実行

invoke fooで、上記tasks.pyで定義した関数名のタスクを実行できます。
試しにhelloを動かしてみます。

# 仮想環境の外から呼ぶ
$ pipenv run invoke 
hello invoke!

# 仮想環境の中から呼ぶ
$ invoke hello
hello invoke!

invoke fooinv fooと短く書けます。たった三文字なので、pipenv run fooよりもちょっと楽ですね。
以降は仮想環境に入っている想定で、また短くinvで例を書きます。

いろいろな使い方

先程のtasks.pyを編集してみます。

task.py
import invoke


@invoke.task
def hello(c, name):
    print(f'name is {type(name)}')
    invoke.run(f'echo "hello {name}!"')


@invoke.task
def run_webserver(c, port=8000):
    '''
    http.serverを実行します
    '''
    print(f'port is {type(port)}')
    invoke.run(f'python -m http.server {port}', pty=True)

コマンド一覧やヘルプの表示

inv --listで定義されているタスクの一覧を、inv --help <タスク名>で各タスクのヘルプを参照できます。

$ inv --list
Available tasks:

  hello
  run-webserver   http.serverを実行します

$ inv --help hello
Usage: inv[oke] [--core-opts] hello [--options] [other tasks here ...]

Docstring:
  none

Options:
  -n STRING, --name=STRING

$ inv hello --name John
name is <class 'str'>
hello John!

$ inv --help run-webserver
Usage: inv[oke] [--core-opts] run-webserver [--options] [other tasks here ...]

Docstring:
  http.serverを実行します

Options:
  -p INT, --port=INT

$ inv run-webserver -p 8888
port is <class 'int'>
Serving HTTP on 0.0.0.0 port 8888 (http://0.0.0.0:8888/) ...

タスクに引数を渡す

上記の例のとおり、定義したタスクの関数に引数を定義することで、タスクはコマンドライン引数を受け付けるようになります。
また、デフォルト値を指定すると型をよしなに変換してくれます。helloのオプションnameはデフォルト値を指定していないのでヘルプでもSTRINGを想定しており、run-webserverのオプションportはINTを想定しており、タスク内でもそう扱われていることが分かります。

invoke.run()pty=Trueを指定することでptyが有効になり、たとえばdocker-compose exec <コンテナ名> bashのようにそのままコンテナ内に入ったりできるようになります。

タブ保管用のスクリプトを生成する

Makefileはそのままでもタブ補完が使えますが、Invokeでもinvoke --print-completion-script zshを実行した結果を.zshrcに入れるなり、別ファイルに書き出してsourceするなりしてタブ補完が効くようになります。

タスクの依存関係

あるタスクの前に呼びたいタスクを指定することも出来ます。tasks.pyに追記してみます。

tasks.py
@invoke.task
def lint(c):
    invoke.run('pylint some_package')


@invoke.task
def test(c):
    invoke.run('coverage -m pytest')


@invoke.task(lint, test)
def package(c):
    invoke.run('python setup.py sdist bdist_wheel')

上記の例では、inv packageを実行すると、その前にlintとtestが走ります。

出力や終了コードを利用する

invoke.run()はstdout/stderrの内容、コマンドの終了コードなどを返却するので処理分けになどにも使えます。REPLで様子を見てみましょう。

>>> import invoke
>>> result=invoke.run('echo Hello!')
Hello!
>>> result.ok
True
>>> result.stdout
'Hello!\n'

まとめ

タスクが増えてきたらtasks.py内で関数を定義して処理を共通化したり、namespaceを使ってtasks.pyを複数ファイルに分割したりして、プロジェクトに合わせて拡張していくことができます。上手に使って、積極的に楽をしていきましょう。

たとえばAWSのboto3などと組み合わせてデフォルト値や条件を上手く設定すれば、プロジェクトごとに公式のAWS CLIよりも短く使いやすいコマンドラインツールにすることもできるでしょう。

jxpress
技術力で「ニュースの産業革命」を起こす。言語処理・データ解析分野の専門家が集まる、News Techベンチャー。
https://jxpress.net/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away