关于python:从click命令调用另一个click命令

Call another click command from a click command

我想使用一些有用的功能作为命令。 为此,我正在测试click库。 我定义了三个原始函数,然后将其装饰为click.command

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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 +"\
\\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 +"\
\\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

这样,我可以使用setup.py文件和pip install --editable .生成三个命令add_nameadd_surnameadd_name_and_surname,然后可以通过管道进行操作:

1
2
3
4
5
$ echo"original content" | add_name | add_surname
original content

    added name
    added surname

但是,使用不同的单击命令作为函数时,我需要解决一个小问题:

1
2
3
4
5
$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_name_and_surname命令来调用add_nameadd_surname而不是作为命令而是作为函数,否则它违背了我最初将函数用作库函数和命令的目的。


当您直接从另一个函数调用add_name()add_surname()时,实际上是调用了它们的修饰版本,因此预期的参数可能与定义它们时不同(请参见如何从python中的函数中剥离修饰符的答案)。有关原因的详细信息)。

我建议修改您的实现,以使原始功能保持不变,并为它们创建特定于单击的瘦包装器,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def add_name(content, to_stdout=False):
    if not content:
        content = ''.join(sys.stdin.readlines())
    result = content +"\
\\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包装器脚本来调用它们。

这似乎是多余的,但实际上可能是正确的方法:一个功能代表您的业务逻辑,另一个功能(单击命令)是一个"控制器",可通过命令行公开此逻辑(出于某种原因,例如,也是通过Web服务公开相同逻辑的函数)。

实际上,我什至建议将它们放在单独的Python模块中-您的"核心"逻辑和特定于点击的实现,如果需要,可以将其替换为任何其他接口。


由于单击装饰器,不能仅通过指定参数来调用函数。
Context类是您的朋友,特别是:

  • Context.invoke()-使用您提供的参数调用另一个命令
  • Context.forward()-填充当前命令的参数
  • 因此,您的add_name_and_surname代码应如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @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


    我发现这些解决方案更加复杂。我希望下面的这个函数可以从另一个包的另一个地方调用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @click.command(help='Clean up')
    @click.argument('path', nargs=1, default='def')
    @click.option('--info', '-i', is_flag=True,
                  help='some info1')
    @click.option('--total', '-t', is_flag=True,
                  help='some info2')
    def clean(path, info, total):
    #some definition, some actions

    #this function will help us
    def get_function(function_name):
    if function_name == 'clean':
        return clean

    我有另一个软件包,所以,我想单击此软件包中的上面的命令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import somepackage1 #here is clean up click command
    from click.testing import CliRunner


    @check.command(context_settings=dict(
               ignore_unknown_options=True,
              ))
    @click.argument('args', nargs=-1)
    @click.pass_context
    def check(ctx, args):
        runner = CliRunner()

        if len(args[0]) == 0:
            logger.error('Put name of a command')

        if len(args) > 0:
            result = runner.invoke(somepackage1.get_function(args[0]), args[1:])
            logger.print(result.output)
        else:
            result = runner.invoke(somepackage1.get_function(args[0]))
        logger.print(result.output)

    因此,它有效。

    1
    python somepackage2 check clean params1 --info