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

kal*_*gne 17 python stdout piping command-line-arguments python-click

我想使用一些有用的函数作为命令.为此我正在测试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
Run Code Online (Sandbox Code Playgroud)

这样我就可以生成三个命令add_name,add_surnameadd_name_and_surname使用一个setup.py文件pip install --editable .然后我能够管道:

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

    added name
    added surname
Run Code Online (Sandbox Code Playgroud)

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

$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 
)
Run Code Online (Sandbox Code Playgroud)

我不知道为什么它不起作用,我需要这个add_name_and_surname命令来调用add_nameadd_surname不是作为命令而是作为函数,否则它会破坏我将函数用作库函数和命令的最初目的.

she*_*ron 25

当你打电话add_name(),并add_surname()从另一个功能直接,你居然叫他们的装饰版本,所以预计参数可能不如你定义它们(见的答案,如何从功能剥离装饰在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)
Run Code Online (Sandbox Code Playgroud)

然后,您可以直接调用这些函数,也可以通过setup.py创建的CLI包装器脚本调用它们.

这似乎是多余的,但事实上可能是正确的方法:一个函数代表你的业务逻辑,另一个(click命令)是一个"控制器",通过命令行暴露这个逻辑(可能是为了例如,也是一个通过Web服务公开相同逻辑的函数.

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

  • 关于传递点击上下文的答案似乎更适合这个问题。然而,这和剥离装饰器都被认为是脏的,不建议使用。最后,您基于控制器的替代方案更加“通用”和可靠。我会走这条路非常感谢! (3认同)
  • 如果您希望 CLI 版本与“core”函数具有相同的名称,请传递“name”参数,如上面的“@click.command(name='add_name')”中所示。 (2认同)

Vik*_*koo 20

由于点击装饰器,仅通过指定参数就不能再调用这些函数.该上下文类是你的朋友在这里,具体如下:

  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
Run Code Online (Sandbox Code Playgroud)

参考:http: //click.pocoo.org/6/advanced/#invoking-other-commands

  • 似乎是正确的答案,但我会记住一个事实,即文档不建议使用 Context.invoke 和 Context.forward。 (2认同)
  • 我找不到为什么它被气馁 (2认同)
  • 链接:[Click 通常不鼓励使用这种模式,但仍然有可能。](https://click.palletsprojects.com/en/8.0.x/advanced/#invoking-other-commands) (2认同)

CNu*_*ren 5

在某些情况下,您还可以使用成员函数调用 Click 函数callback。根据这个 GitHub 问题

假设您知道给定的命令是您编写的函数的直接包装器(而不是一组或其他类型的命令),您可以使用 command.callback 获取该函数。但是,调用它只会调用该函数,不会调用任何 Click 管道进行验证、回调等。

你的例子将变成:

(...)
def add_name_and_surname(content, to_stdout=False):
    result = add_surname.callback(add_name.callback(content))
    (...)
Run Code Online (Sandbox Code Playgroud)