2016-10-17 5 views
2

便利な機能をコマンドとして使いたい。そのために私はclickライブラリをテストしています。その後、私はパイプにできる午前setup.pyファイルとpip install --editable .を使用してクリックコマンドから別のクリックコマンドを呼び出す

import click 
import os, sys 

@click.command() 
@click.argument('content', required=False) 
@click.option('--to_stdout', default=True) 
def add_name(content, to_stdout=False): 
    if not content: 
     content = ''.join(sys.stdin.readlines()) 
    result = content + "\n\tadded name" 
    if to_stdout is True: 
     sys.stdout.writelines(result) 
    return result 


@click.command() 
@click.argument('content', required=False) 
@click.option('--to_stdout', default=True) 
def add_surname(content, to_stdout=False): 
    if not content: 
     content = ''.join(sys.stdin.readlines()) 
    result = content + "\n\tadded surname" 
    if to_stdout is True: 
     sys.stdout.writelines(result) 
    return result 

@click.command() 
@click.argument('content', required=False) 
@click.option('--to_stdout', default=False) 
def add_name_and_surname(content, to_stdout=False): 
    result = add_surname(add_name(content)) 
    if to_stdout is True: 
     sys.stdout.writelines(result) 
    return result 

私は三つのコマンドadd_nameを生成することができています。この方法で、add_surnameadd_name_and_surname::私はその後click.commandとして飾ら私の3つの本来の機能を定義し

$ echo "original content" | add_name | add_surname 
original content 

    added name 
    added surname 

機能として別のクリックコマンドを使用して作成する場合、私は解決する必要があるわずかな問題があります:

$echo "original content" | add_name_and_surname 
Usage: add_name_and_surname [OPTIONS] [CONTENT] 

Error: Got unexpected extra arguments (r i g i n a l c o n t e n t 
) 

私はこれがうまくいかない理由を知りません。コマンドをadd_nameadd_surnameをコマンドとしてではなく関数として呼び出す必要があります。そうでなければ、関数をライブラリ関数とコマンドの両方として使用するという本来の目的を打ち消します。

答えて

4

クリックデコレータのために、引数を指定するだけで関数を呼び出すことはできません。 Contextクラスは、あなたの友人は、具体的には、ここにある:

  1. Context.invoke() - あなたは
  2. Context.forwardを()指定する引数を持つ別のコマンドを起動する - 現在のコマンドから引数を埋め

だから、add_name_and_surnameためのコードは次のようになります。

@click.command() 
@click.argument('content', required=False) 
@click.option('--to_stdout', default=False) 
@click.pass_context 
def add_name_and_surname(ctx, content, to_stdout=False): 
    result = ctx.invoke(add_surname, content=ctx.forward(add_name)) 
    if to_stdout is True: 
     sys.stdout.writelines(result) 
    return result 

リファレンス: http://click.pocoo.org/6/advanced/#invoking-other-commands

+0

正しい答えと思われますが、私はContext.invokeとContext.forwardを使ってアドバイスしていないことに留意します。 – kaligne

9

あなたが他の関数から直接add_name()add_surname()を呼び出すと、あなたがそれらを定義したとして期待される引数がないかもしれませんので、あなたが実際に(理由のいくつかの詳細についてHow to strip decorators from a function in pythonへの回答を参照)、これらの装飾が施されたバージョンを呼び出します。

私はあなたが装飾のない本来の機能を維持するように実装を変更することを提案し、例えば、彼らのために薄いクリック固有のラッパーを作成します。

def add_name(content, to_stdout=False): 
    if not content: 
     content = ''.join(sys.stdin.readlines()) 
    result = content + "\n\tadded name" 
    if to_stdout is True: 
     sys.stdout.writelines(result) 
    return result 

@click.command() 
@click.argument('content', required=False) 
@click.option('--to_stdout', default=True) 
def add_name_command(content, to_stdout=False): 
    return add_name(content, to_stdout) 

あなたはその後、直接これらの関数を呼び出すかを経由して、それらを呼び出すことができますsetup.pyによって作成されたCLIラッパースクリプト。

これは冗長に見えるかもしれませんが、実際には正しい方法です:1つの機能はビジネスロジックを表し、もう1つはコマンド(コマンド)を介してこのロジックを公開する「コントローラ」です。例えば、ウェブサービスを介して同じ論理を公開する機能など)を含むことができる。

実際、私はそれらを別々のPythonモジュールに置くことさえアドバイスしています。あなたの "コア"ロジックと、必要に応じて他のインターフェースに置き換えることができるクリック固有の実装です。

+0

これはまさに私が取るアプローチです。 –

+0

クリックのコンテキストを渡すことに関する回答は、質問に適しているようです。しかし、これとデコレーターを剥がすことはどちらも汚れていると言われており、使用を勧められていません。最終的には、コントローラをベースにした代替品は、より一般的で信頼性が高くなります。私はこの方法で多くのおかげで行くよ! – kaligne

+0

Clickは1つのコマンドを別のコマンドから呼び出すことを嫌うため、+1します。ビジネスロジックとcliコントローララッパーIMHOへの懸念のこの分離は理にかなっています。 – peepa

関連する問題