从单击命令调用另一个单击命令

Call another click command from a click command

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

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

这样我就可以使用 setup.py 文件和 pip install --editable . 生成三个命令 add_nameadd_surnameadd_name_and_surname 然后我可以管道:

$ 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_name_and_surname 命令来调用 add_nameadd_surname 不是作为命令而是作为函数,否则它违背了我的初衷使用函数作为库函数和命令。

当您直接从另一个函数调用 add_name()add_surname() 时,您实际上调用了它们的修饰版本,因此预期的参数可能与您定义的不同(参见 [= 的答案13=] 了解有关原因的一些细节)。

我建议修改您的实现,使原始函数保持未修饰状态,并为它们创建薄的特定于点击的包装器,例如:

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 包装器脚本调用它们。

这可能看起来多余,但实际上可能是正确的做法:一个函数代表您的业务逻辑,另一个(点击命令)是 "controller" 通过命令行公开此逻辑(有例如,也可以是通过 Web 服务公开相同逻辑的函数)。

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

由于点击装饰器,函数不能再仅通过指定参数来调用。 Context class 是你的朋友,具体来说:

  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

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

@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

我有另一个包,所以,我想在这个包中点击上面的命令

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)

所以,它有效。

python somepackage2 check clean params1 --info

在某些情况下,您还可以使用 callback 成员函数调用 Click 函数。根据 this GitHub issue:

Assuming you know the given command is a direct wrapper for a function you wrote (and not a group or other type of command), you can get at the function with command.callback. However, calling it will just call the function, it won't invoke any of the Click pipeline for validation, callbacks, etc.

你的例子将变成:

(...)
def add_name_and_surname(content, to_stdout=False):
    result = add_surname.callback(add_name.callback(content))
    (...)