命令と組

Clickの最も重要な特性は,任意のネスト命令行ユーティリティの概念である.これは Command そして Group (実際には MultiCommand )。

コールバックコール

通常命令に対しては,その命令が実行されればコールバックを実行する.スクリプトが唯一のコマンドである場合、パラメータコールバックが阻止されない限り、それは常にトリガされる。例えば、誰かが通ると、このような状況が発生する。 --help スクリプトまで)。

グループとマルチ命令については,状況が異なるように見える.この場合,子命令を発火させる(この行為を変更しない限り),コールバックをトリガする.実際、これは、内部命令が実行されると、外部命令も実行されることを意味する。

@click.group()
@click.option('--debug/--no-debug', default=False)
def cli(debug):
    click.echo(f"Debug mode is {'on' if debug else 'off'}")

@cli.command()  # @cli, not @click!
def sync():
    click.echo('Syncing')

これはこう見えます

$ tool.py
Usage: tool.py [OPTIONS] COMMAND [ARGS]...

Options:
  --debug / --no-debug
  --help                Show this message and exit.

Commands:
  sync

$ tool.py --debug sync
Debug mode is on
Syncing

伝達パラメータ

コマンドとサブコマンドの間でパラメータを厳密に分離するステップをクリックします。これは、特定のコマンドのオプションおよびパラメータを指定しなければならないこと その後 コマンド名自体は、しかし その前に 他の命令名もあります

所定の使用 --help 選択します。もし私たちが tool.py 名前のサブコマンドが含まれています sub それがそうです。

  • tool.py --help プログラム全体のヘルプ(リストサブ命令)を返す.

  • tool.py sub --help 対に戻ります sub サブ命令。

  • でも. tool.py --help sub おごります。 --help メインプログラムのパラメータとする.クリックして、次のコールバックを呼び出します --help Clickがサブコマンドを処理できる前に、ヘルプを印刷してプログラムを中止します。

入れ子処理と文脈

前の例から見たように、Basicコマンド群は、syncコマンド自体に渡すのではなく、そのコールバックに渡されるデバッグパラメータを受け取る。SYNCコマンドは自分のパラメータのみを受け取る.

これによりツールは完全に独立して動作することができるが,1つの命令は入れ子の命令とどのように対話するのだろうか?この質問の答えは Context それがそうです。

コマンドが呼び出されるたびに、新しいコンテキストが作成され、親コンテキストにリンクされる。一般的に、あなたはこのような文脈を見ることができないが、それらはそこにある。コンテキストは値とともに自動的にパラメータコールバックに渡される.命令は自分自身をマークすることで pass_context() 装飾師です。この場合、コンテキストは第1のパラメータとして伝達される。

コンテキストは,プログラムの目的に利用可能なプログラム指定オブジェクトを携帯することも可能である.これは、次のようなスクリプトを構築できることを意味します。

@click.group()
@click.option('--debug/--no-debug', default=False)
@click.pass_context
def cli(ctx, debug):
    # ensure that ctx.obj exists and is a dict (in case `cli()` is called
    # by means other than the `if` block below)
    ctx.ensure_object(dict)

    ctx.obj['DEBUG'] = debug

@cli.command()
@click.pass_context
def sync(ctx):
    click.echo(f"Debug is {'on' if ctx.obj['DEBUG'] else 'off'}")

if __name__ == '__main__':
    cli(obj={})

オブジェクトが提供された場合、各コンテキストは、そのオブジェクトをそのサブオブジェクトに前方に渡すが、どのレベルでもコンテキストのオブジェクトをカバーすることができる。両親に連絡して context.parent 使用できます。

それ以外にも,オブジェクトを下向きに渡すのではなく,アプリケーションがグローバル状態を修正することを阻止することは何もない.例えば、全体を1つだけ反転させることができます DEBUG 変数を使用しています

装飾命令.

前の例で見たように、装飾器はコマンドを呼び出す方法を変更することができます。裏で実際に起きていることはコールバックはいつも Context.invoke() 命令を自動的に正しく呼び出す方法(コンテキストを渡すことによって、またはコンテキストを渡さないこと)。

あなたがカスタマイズ装飾器を作りたい時、これは非常に有用だ。例えば、一般的なモードは、状態を表すオブジェクトを構成し、その後、コンテキストに格納し、その後、カスタム修飾器を使用してこのタイプの最新のオブジェクトを検索し、第1のパラメータとして渡すことである。

例えば pass_obj() 装飾器は、以下のように実現することができる。

from functools import update_wrapper

def pass_obj(f):
    @click.pass_context
    def new_func(ctx, *args, **kwargs):
        return ctx.invoke(f, ctx.obj, *args, **kwargs)
    return update_wrapper(new_func, f)

♪the Context.invoke() 命令はこの関数を正しい方法で自動的に呼び出すので,この関数は使用される. f(ctx, obj) あるいは…。 f(obj) それ自体が装飾されているかどうかによって pass_context() それがそうです。

これは非常に複雑な入れ子アプリケーションを構築するための非常に強力な概念です 複雑なアプリケーション より多くの情報を得ることができます

命令なしのグループ呼び出し

デフォルトでは、サブコマンドが転送されない限り、GROUPまたはMULTIコマンドは呼び出されません。実際命令を提供しないと自動的に通過します --help デフォルトの場合。この行動は伝達することで invoke_without_command=True 団体にあげます。この場合、ヘルプページを表示するのではなく、コールバックが常に呼び出される。コンテキストオブジェクトには,呼び出しがサブコマンドに転送されるかどうかに関する情報も含まれる.

例:

@click.group(invoke_without_command=True)
@click.pass_context
def cli(ctx):
    if ctx.invoked_subcommand is None:
        click.echo('I was invoked without subcommand')
    else:
        click.echo(f"I am about to invoke {ctx.invoked_subcommand}")

@cli.command()
def sync():
    click.echo('The subcommand')

実際にどのように機能しているのか

$ tool
I was invoked without subcommand
$ tool sync
I am about to invoke sync
The subcommand

カスタムマルチコマンド

使用を除いて click.group() また、カスタマイズされたマルチコマンドを構築することができます。プラグインからゆっくりとコマンドをロードすることをサポートしたい場合、これは有用です。

カスタムマルチコマンドは、ListとLoadメソッドのみを実現する必要があります:

import click
import os

plugin_folder = os.path.join(os.path.dirname(__file__), 'commands')

class MyCLI(click.MultiCommand):

    def list_commands(self, ctx):
        rv = []
        for filename in os.listdir(plugin_folder):
            if filename.endswith('.py') and filename != '__init__.py':
                rv.append(filename[:-3])
        rv.sort()
        return rv

    def get_command(self, ctx, name):
        ns = {}
        fn = os.path.join(plugin_folder, name + '.py')
        with open(fn) as f:
            code = compile(f.read(), fn, 'exec')
            eval(code, ns, ns)
        return ns['cli']

cli = MyCLI(help='This tool\'s subcommands are loaded from a '
            'plugin folder dynamically.')

if __name__ == '__main__':
    cli()

これらのカスタムクラスは、装飾符と共に使用することもできる。

@click.command(cls=MyCLI)
def cli():
    pass

複数の命令を統合する

カスタム多命令の実現に加えて,複数の命令を1つのスクリプトに統合することも興味深い.1つの入れ子を別のものにするので、一般には推奨されていないが、統合方法は、より良いシェル体験を得るために有用である場合がある。

この統合システムのデフォルト実装は CommandCollection 級友たち。これは、他の複数のコマンドのリストを受け取り、これらのコマンドを同じレベルで利用可能にする。

例示的な用法:

import click

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

@cli1.command()
def cmd1():
    """Command on cli1"""

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

@cli2.command()
def cmd2():
    """Command on cli2"""

cli = click.CommandCollection(sources=[cli1, cli2])

if __name__ == '__main__':
    cli()

どのように見えるのでしょうか

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

Options:
  --help  Show this message and exit.

Commands:
  cmd1  Command on cli1
  cmd2  Command on cli2

1つのコマンドが複数のソースに存在する場合、第1のソースが勝利する。

マルチコマンドリンク

Changelog

バージョン 3.0 で追加.

複数のサブコマンドを一度に呼び出すことを許可することが有用である場合がある.例えば、setuptoolsパッケージをインストールする前によく知っている場合があります。 setup.py sdist bdist_wheel upload 以下の命令の命令チェーンを呼び出す. sdist その前に bdist_wheel その前に upload それがそうです。Click 3.0から始めて,実現は非常に簡単である.あなたがすべきことは chain=True あなたへの多くの命令:

@click.group(chain=True)
def cli():
    pass


@cli.command('sdist')
def sdist():
    click.echo('sdist called')


@cli.command('bdist_wheel')
def bdist_wheel():
    click.echo('bdist_wheel called')

このように呼ぶことができます

$ setup.py sdist bdist_wheel
sdist called
bdist_wheel called

マルチコマンドリンクを使用する場合は,1つのコマンド(最後の1つ)しか使用できない. nargs=-1 議論があったからですリンクのマルチ命令の下にマルチ命令をネストすることも不可能である.それに加えて、それらの働き方には何の制限もない。彼らはいつものようにオプションと論点を受け入れることができる。リンク命令では,オプションとパラメータの間の順序が制限される.現在限りである --options argument 注文を許可します。

もう一つの注意事項: Context.invoked_subcommand 属性は複数の命令には少し役に立たない,それが与えられるからである '*' 複数の命令が呼び出されると値となる.これは必要であり,子命令の処理が次々と発生するため,コールバックをトリガする際には,処理の確実な子命令を用いることはできない.

注釈

現在、チェーン命令を入れ子にすることはできません。この問題はClickの未来のバージョンで修復されるだろう。

マルチコマンドパイプライン

Changelog

バージョン 3.0 で追加.

マルチコマンドリンクの非常に一般的な用法は,1つの命令に前の命令を処理させた結果である.これを促進する様々な方法がある。最も明らかな方法は,文脈オブジェクトに値を格納し,それを関数間で処理することである.これは pass_context() その後、コンテキストオブジェクトが提供され、サブコマンドは、そのデータをそこに格納することができる。

これを実現するもう1つの方法は,処理関数を返すことでパイプを設定することである.子命令が呼び出された場合には,そのすべてのパラメータを処理し,どのように処理を行うかのプランを提案すると考えられる.この時点で、処理関数を返して返す。

戻る関数はどこに置きますか?チェーンマルチコマンドは登録コールバックをご利用いただけます MultiCommand.resultcallback() これはこれらの関数をすべてチェックして呼び出します

この点をより具体的に説明するために、以下の例を考える。

@click.group(chain=True, invoke_without_command=True)
@click.option('-i', '--input', type=click.File('r'))
def cli(input):
    pass

@cli.resultcallback()
def process_pipeline(processors, input):
    iterator = (x.rstrip('\r\n') for x in input)
    for processor in processors:
        iterator = processor(iterator)
    for item in iterator:
        click.echo(item)

@cli.command('uppercase')
def make_uppercase():
    def processor(iterator):
        for line in iterator:
            yield line.upper()
    return processor

@cli.command('lowercase')
def make_lowercase():
    def processor(iterator):
        for line in iterator:
            yield line.lower()
    return processor

@cli.command('strip')
def make_strip():
    def processor(iterator):
        for line in iterator:
            yield line.strip()
    return processor

一気にたくさん話したので、一歩一歩やりましょう。

  1. 1つ目は1つのことをすることです group() これはリンク可能です。また,サブ命令が定義されていなくてもClick呼び出しを指示する.そうしなければ,空パイプ呼び出しは実行結果コールバックではなくヘルプページを生成する.

  2. 私たちがした次のことは私たちのグループに結果コールバックを登録することだ。このコールバックは、サブコマンドのすべての返り値のリストであり、その後、私たちのグループ自体と同じキーワードパラメータであるパラメータ呼び出しを使用します。これは、文脈オブジェクトを使用することなく、そこで入力ファイルに容易にアクセスできることを意味する。

  3. この結果コールバックでは、入力ファイル中のすべての行のための反復器を作成し、次いで、すべてのサブコマンドから返されたすべてのコールバックをトラバースし、最後にすべての行をstdoutに印刷する。

その後,任意の数のサブ命令を登録することができ,各サブ命令は1つのプロセッサ関数を返して行フローを修正することができる.

注意すべき点は,コールバックを実行するたびにClickがコンテキストを閉じることである.これは例えばファイルタイプが processor ファイルがそこで閉じられているので、機能はあります。この制限はリソース処理をはるかに複雑にするため、変更される可能性は低い。この場合には、ファイルタイプではなく、手動でファイルを開くことをお勧めします open_file() それがそうです。

配管処理においても改善されたより複雑な例については、ご確認ください imagepipe multi command chaining demo Clickリポジトリにあります。パイプライン内部構造が良好なパイプラインベースの画像編集ツールを実現する。

デフォルト値を上書きする

デフォルトの場合、パラメータのデフォルト値は default 定義時に提供されるフラグであるが、これはデフォルト値をロードすることができる唯一の位置ではない。もう一つの場所は Context.default_map 文脈に関するものですこれにより、従来のデフォルト値を上書きするために、プロファイルからデフォルト値をロードすることができます。

別のパッケージにいくつかのコマンドが挿入されている場合、デフォルト設定に満足していない場合、これは非常に有用です。

各サブコマンドの任意のネストされたデフォルトマッピング:

default_map = {
    "debug": True,  # default for a top level option
    "runserver": {"port": 5000}  # default for a subcommand
}

デフォルト地図はスクリプトが呼び出されたときに提供することができ,いつでも命令で上書きすることができる.例えば、トップコマンドは、プロファイルからデフォルト値をロードすることができる。

例示的な用法:

import click

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

@cli.command()
@click.option('--port', default=8000)
def runserver(port):
    click.echo(f"Serving on http://127.0.0.1:{port}/")

if __name__ == '__main__':
    cli(default_map={
        'runserver': {
            'port': 5000
        }
    })

行動中:

$ cli runserver
Serving on http://127.0.0.1:5000/

上下文デフォルト値

Changelog

バージョン 2.0 で追加.

Click 2.0から、スクリプトを呼び出す際にコンテキストのデフォルト設定を上書きするだけでなく、コマンドを宣言する修飾器でデフォルト設定を上書きすることもできます。例えば、カスタムを定義する前の例が与えられる。 default_map 今、これは装飾器でも完成できます。

この例は、上述した例と同様の動作を行う。

import click

CONTEXT_SETTINGS = dict(
    default_map={'runserver': {'port': 5000}}
)

@click.group(context_settings=CONTEXT_SETTINGS)
def cli():
    pass

@cli.command()
@click.option('--port', default=8000)
def runserver(port):
    click.echo(f"Serving on http://127.0.0.1:{port}/")

if __name__ == '__main__':
    cli()

これはまた実際の動作の例です

$ cli runserver
Serving on http://127.0.0.1:5000/

コマンド返却値

Changelog

バージョン 3.0 で追加.

Click 3.0で導入された新しい機能の1つは,コマンドコールバックの返り値を完全にサポートすることである.これは,従来困難であった一連の機能を実現している.

実質的には,どの命令のコールバックも現在返却値を返すことができる.この返り値は、いくつかの受信機にバブルされる。この点の用例の1つは以下の例で示されている マルチコマンドリンク チェーンマルチコマンドは、すべての返り値を処理するコールバックを持つことができることを示している。

Clickでコマンドの返却値を使用する場合には、以下のことを知る必要があります。

  • コマンドコールの返り値は通常 BaseCommand.invoke() 方法です。このルールの例外は Group s:

    • グループでは、返り値は、通常、呼び出されたサブコマンドの返却値である。このルールの唯一の例外は,パラメータやパラメータがない場合にグループコールを呼び出すと,返り値がグループコールの返り値であることである. invoke_without_command 有効になりました。

    • リンクにグループが設定されていれば,返り値はすべてのサブコマンドの結果のリストである.

    • グループの返り値は MultiCommand.result_callback それがそうです。これは,チェーンモードではすべての返り値のリストで呼び出されるか,チェーンコマンドでない場合には単一の返り値が呼び出される.

  • 返り値は Context.invoke() そして Context.forward() 方法です。これはあなたの内部で他のコマンドを追加する必要がある場合に非常に有用だ。

  • Clickは返り値に対して何の無理な要求もなく,自身も返り値を使用しない.これにより、カスタム装飾子またはワークフロー(マルチコマンドリンク例に示すように)に返却値を用いることができる。

  • Clickスクリプトがコマンドラインアプリケーションとして呼び出された場合(通過 BaseCommand.main() )この返り値は無視される。 standalone_mode 禁止されています。この場合、泡が通過します。