[Python]Clickでコマンドラインアプリケーションを作る

click_logo

Python でコマンドラインプログラムを書くときのライヴラリに Click というのがある。(キャッチコピーは “Command Line Interface Creation Kit”)
“The Hitchhiker’s Guide to Python” の “Command-line Application” でも CLI 用のライブラリとして clint などとともに取り上げられている。

作者の @mitsuhiko が YouTube に “Building Command Line Applications with Click” という20分弱の動画を投稿している。

固定メッセージを返すシンプルなアプリケーションを少しつづ拡張していき、最後には git 風のサブコマンドを実装する。
勉強がてら書き起こしてみる。

動画が公開されたのは Click の初回リリース間もない頃だけれど、Click の基本設計は変わっていないので、2015年3月末リリースの 最新版 0.4 でも読み替え無しに動作した。

プロジェクトの作成

$ mkdir helloclick
$ cd helloclick/

virtualenv で環境構築する

$ virtualenv venv
New python executable in venv/bin/python
Installing setuptools, pip...done.
~/helloclick$ ls venv/
bin     include lib
jsmith@latok:~/helloclick$ . venv/bin/activate
~/helloclick$ which python
/Users/jsmith/helloclick/venv/bin/python

プログラムの作成

hello で呼び出せる CLI プログラムを作成。
CLI インストールする setup.py を用意。

# setup.py
from setuptools import setup

setup(
    name='HelloWorld',
    version='1.0',
    py_modules=['hello'],
    install_requires=[
        'Click',
    ],
    entry_points='''
        [console_scripts]
        hello=hello:cli
    '''
)

実体は hello.pycli 関数。

# hello.py
def cli():
    print 'Hello World!'

pip 経由でインストールする。
--editable .オプションをつけると working directory のプログラムを参照してくれるので、プログラムを修正後、再インストールしなくてすむ

$ pip install --editable .

インストールされていることを確認。

$ hello
Hello World!
$ which hello
/Users/jsmith/helloclick/venv/bin/hello

click でラップする

ここから Click を使ってより楽にコマンドラインプログラムを拡張していく。
cli 関数を @click.command()でラップします。

# hello.py
import click

@click.command()
def cli():
    print 'Hello World!'

実行してみる

$ hello
Hello World!
$ hello --help
Usage: hello [OPTIONS]

Options:
  --help  Show this message and exit.

通常の実行はこれまでと同じですが --help でヘルプメッセージが出力されるようになりました。

オプションを追加

出力メッセージをコントロールする --string オプションを追加します。
デフォルトは ‘World’ とします。

# hello.py
import click

@click.command()
@click.option('--string', default='World')
def cli(string):
    print 'Hello %s!' % string

実行してみる

$ hello
Hello World!
$ hello --help
Usage: hello [OPTIONS]

Options:
  --string TEXT
  --help         Show this message and exit.

$ hello --string Tom
Hello Tom!

ヘルプメッセージにも --string オプションが表示され、Hello に続く文字列もコントロールできるようになりました。

メッセージ出力で環境依存を吸収する

  • Python 2/3 の print ステートメント/関数問題
  • 出力時の文字コードのややこしい問題

を抽象化した echo 関数が Click には存在するので、これを使う。

# hello.py
import click

@click.command()
@click.option('--string', default='World')
def cli(string):
    click.echo('Hello %s!' % string)

ヘルプメッセージを補強

  • プログラムの説明(cli 関数の docstring)
  • 引数の意味(@click.option の help 引数)

をヘルプメッセージに表示します。

# hello.py
import click

@click.command()
@click.option('--string', default='World',
              help='This is  the thing that is greeted.')
def cli(string):
    """This script greets you."""
    click.echo( 'Hello %s!' % string)

実行してみる。

$ hello --help
Usage: hello [OPTIONS]

  This script greets you.

Options:
  --string TEXT  This is  the thing that is greeted.
  --help         Show this message and exit.

整数型のオプションを追加

整数を引数に取る --repeat オプションを追加します。
オプションの型はデフォルト値から推論されます。
明示的に

@click.option('--repeat', default=1, type=int, help='...')

というように書くこともできます。

# hello.py
import click

@click.command()
@click.option('--string', default='World',
              help='This is  the thing that is greeted.')
@click.option('--repeat', default=1,
              help='How many times you should be greeted.')
def cli(string, repeat):
    """This script greets you."""
    for x in xrange(repeat):
        click.echo( 'Hello %s!' % string)

実行してみる。

$ hello --help
Usage: hello [OPTIONS]

  This script greets you.

Options:
  --string TEXT     This is  the thing that is greeted.
  --repeat INTEGER  How many times you should be greeted.
  --help            Show this message and exit.
$ hello --repeat 3
Hello World!
Hello World!
Hello World!

引数に不正な型を指定するとバリデーションエラーになります。

$ hello --repeat asdasdf
Usage: hello [OPTIONS]

Error: Invalid value for "--repeat": asdasdf is not a valid integer

オプションのダッシュ(-)の扱い

@click.option('--foo-bar', default='baz') のようにダッシュ(-)が含まれる場合、アンダースコア(_)に置き換えられます。

@click.option('--foo-bar', default='baz')
def cli(foo_bar):
    ...

Click のパラメーターには

  • click.option($ ls -l のような用途)
  • click.argument($ ls a b c のような用途)

の2種類がある。

細かい違いはドキュメントを参照 http://click.pocoo.org/4/parameters/

click.argument を使って、実行結果を引数で渡したファイルに出力するようにしてみる。

# hello.py
import click

@click.command()
@click.option('--string', default='World',
              help='This is  the thing that is greeted.')
@click.option('--repeat', default=1,
              help='How many times you should be greeted.')
@click.argument('out', type=click.File('w'), default='-',
                required=False)
def cli(string, repeat, out):
    """This script greets you."""
    for x in xrange(repeat):
        click.echo( 'Hello %s!' % string, file=out)

実行してみる

$ hello foo.txt
$ cat foo.txt
Hello World!

file argument の特定

file argument で指定したファイルは lazy にオープンされる。
例えば書き込み系処理の場合、実際に書き込みが発生するまではファイルオープンされない。

$ rm bar.txt
$ hello --repeat=asdf bar.txt
Usage: hello [OPTIONS] [OUT]

Error: Invalid value for "--repeat": asdf is not a valid integer
$ cat bar.txt
cat: bar.txt: No such file or directory

コマンドラインオプションでエラー発生のため、何も書き込みが発生しておらず、ファイルが作成されていません。

実際に正常系の書き込みをします。

$ hello --repeat=3 bar.txt
$ cat bar.txt
Hello World!
Hello World!
Hello World!

ファイル作成されました。

サブコマンド

git add には -f -m などいろいろなサブコマンドが定義されている。
これまで $ hello --message xxx としてきたのを $ hello say --message xxxようにサブコマンド sayを定義するには以下の様にする。

# hello.py
import click

@click.group()
def cli(config, verbose, home_directory):
    pass

@cli.command()
@click.option('--string', default='World',
              help='This is  the thing that is greeted.')
@click.option('--repeat', default=1,
              help='How many times you should be greeted.')
@click.argument('out', type=click.File('w'), default='-',
                required=False)
def say(config, string, repeat, out):
    """This script greets you."""
    for x in xrange(repeat):
        click.echo( 'Hello %s!' % string, file=out)

具体的には

  • CLI のエントリーポイント cli 関数を @click.group() でデコレート
  • サブコマンド say@cli.command でデコレート

している。

ヘルプメッセージの変化を確認してみる。

$ hello --help
Usage: hello [OPTIONS] COMMAND [ARGS]...

Options:
  --help                 Show this message and exit.

Commands:
  say  This script greets you.
$ hello say --help
Usage: hello say [OPTIONS] [OUT]

  This script greets you.

Options:
  --string TEXT     This is  the thing that is greeted.
  --repeat INTEGER  How many times you should be greeted.
  --help            Show this message and exit.

トップレベルコマンドをサブコマンドから利用する

@click.group() デコレーターはサブコマンドが呼ばれると必ず呼ばれる

トップレベル(@click.group)で定義されたオプション(--verbose など)をサブコマンド(@clic.command)から利用するには click.make_pass_decorator を活用する。

# hello.py
import click

class Config(object):

    def __init__(self):
        self.verbose = False

pass_config = click.make_pass_decorator(Config, ensure=True)

@click.group()
@click.option('--verbose', is_flag=True)
@click.option('--home-directory', type=click.Path())
@pass_config
def cli(config, verbose, home_directory):
    config.verbose = verbose
    if home_directory is None:
        home_directory = '.'
    config.home_directory = home_directory

@cli.command()
@click.option('--string', default='World',
              help='This is  the thing that is greeted.')
@click.option('--repeat', default=1,
              help='How many times you should be greeted.')
@click.argument('out', type=click.File('w'), default='-',
                required=False)
@pass_config
def say(config, string, repeat, out):
    """This script greets you."""
    if config.verbose:
        click.echo('We are in verbose mode')
    click.echo('Home directory is %s' % config.home_directory)
    for x in xrange(repeat):
        click.echo( 'Hello %s!' % string, file=out)

実行してみる

$ hello --home-directory /tmp say --string foo --repeat 3
Home directory is /tmp
Hello foo!
Hello foo!
Hello foo!

$ hello say
Home directory is .
Hello World!

$ hello --verbose say
We are in verbose mode
Home directory is .
Hello World!

$ hello --help
Usage: hello [OPTIONS] COMMAND [ARGS]...

Options:
  --verbose
  --home-directory PATH
  --help                 Show this message and exit.

Commands:
  say  This script greets you.
$ hello say --help
Usage: hello say [OPTIONS] [OUT]

  This script greets you.

Options:
  --string TEXT     This is  the thing that is greeted.
  --repeat INTEGER  How many times you should be greeted.
  --help            Show this message and exit.

ということで期待通り。

I hope you can build some very cool applications with Click this way.

Advertisements
Tagged with: , ,
Posted in python

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Archives
  • RT @__apf__: How to write a research paper: a guide for software engineers & practitioners. docs.google.com/presentation/d… /cc @inwyrd 4 months ago
  • RT @HayatoChiba: 昔、自然と対話しながら数学に打ち込んだら何かを悟れるのではと思いたち、専門書1つだけ持ってパワースポットで名高い奈良の山奥に1週間籠ったことがある。しかし泊まった民宿にドカベンが全巻揃っていたため、水島新司と対話しただけで1週間過ぎた。 それ… 4 months ago
  • RT @googlecloud: Ever wonder what underwater fiber optic internet cables look like? Look no further than this deep dive w/ @NatAndLo: https… 4 months ago
  • @ijin UTC+01:00 な時間帯で生活しています、、、 10 months ago
  • RT @mattcutts: Google's world-class Site Reliability Engineering team wrote a new book: amazon.com/Site-Reliabili… It's about managing produc… 1 year ago
%d bloggers like this: