在 Python 中单击如何查看其父项具有所需参数的子命令的 --help?

In Python Click how do I see --help for Subcommands whose parents have required arguments?

我的程序使用 Click 进行命令行处理。它有一个带有必需参数的主命令。此命令具有采用可选参数的子命令。不同的子命令采用不同的选项,但它们都需要来自父命令的相同参数。我想让命令行看起来像这样:

python myprogram.py argument-value subcommand1 --option-1=value

我可以像这样使用Click写这个

import click

@click.group()
@click.argument("argument")
@click.pass_context
def main(context, argument):
    """ARGUMENT is required for both subcommands"""
    context.obj = {"argument": argument}


@click.command()
@click.option("--option-1", help="option for subcommand 1")
@click.pass_context
def subcommand1(context, option_1):
    print("subcommand 1: %s %s" % (context.obj["argument"], option_1))


@click.command()
@click.option("--option-2", help="option for subcommand 2")
@click.pass_context
def subcommand2(context, option_2):
    print("subcommand 2: %s %s" % (context.obj["argument"], option_2))


main.add_command(subcommand1)
main.add_command(subcommand2)

if __name__ == "__main__":
    main()

顶层帮助信息就是我要的

python myprogram.py --help
Usage: myprogram.py [OPTIONS] ARGUMENT COMMAND [ARGS]...

  ARGUMENT is required for both subcommands

Options:
  --help  Show this message and exit.

Commands:
  subcommand1
  subcommand2

如果我传入所需的参数,我可以获得有关子命令的帮助。

python myprogram.py dummy-argument subcommand1 --help
Usage: myprogram.py subcommand1 [OPTIONS]

Options:
  --option-1 TEXT  option for subcommand 1
  --help           Show this message and exit.

但是,我想获得子命令的帮助,而不需要用户传递一个伪参数。我希望能够 运行 python myprogram.py subcommand1 --help 并看到与上面相同的输出,但我只是得到顶层的帮助文本。

python myprogram.py subcommand1 --help
Usage: myprogram.py [OPTIONS] ARGUMENT COMMAND [ARGS]...

  ARGUMENT is required for both subcommands

Options:
  --help  Show this message and exit.

Commands:
  subcommand1
  subcommand2

有没有办法得到我想要的行为?我意识到 Click 非常重视让它的每个命令都是独立的,但这似乎是一种常见的情况。

您的要求中存在固有的歧义,因为子命令名称可能与公共参数的有效值相同。

因此,需要一些消除歧义的方法。我在下面提出一种可能的解决方案。

当找到与子命令名称匹配的参数值时,建议的解决方案将搜索 --help 的存在。如果找到它,则假定正在为子命令请求帮助,并将自动填充 dummy-argument

自定义 Class:

import click

class PerCommandArgWantSubCmdHelp(click.Argument):

    def handle_parse_result(self, ctx, opts, args):
        # check to see if there is a --help on the command line
        if any(arg in ctx.help_option_names for arg in args):

            # if asking for help see if we are a subcommand name
            for arg in opts.values():
                if arg in ctx.command.commands:

                    # this matches a sub command name, and --help is
                    # present, let's assume the user wants help for the
                    # subcommand
                    args = [arg] + args

        return super(PerCommandArgWantSubCmdHelp, self).handle_parse_result(
            ctx, opts, args)

使用自定义 Class:

要使用自定义 class,请将 cls 参数传递给 @click.argument() 装饰器,例如:

@click.argument("argument", cls=PerCommandArgWantSubCmdHelp)

这是如何工作的?

之所以可行,是因为 click 是一个设计良好的 OO 框架。 @click.argument() 装饰器通常实例化一个 click.Argument 对象,但允许使用 cls 参数覆盖此行为。因此,在我们自己的 class 中继承 click.Argument 并覆盖所需的方法是一件相对容易的事情。

在这种情况下,我们超越 click.Argument.handle_parse_result() 并寻找子命令名称后跟 --help 的模式。找到后,我们然后修改参数列表以获得模式点击需要以想要显示子命令帮助的方式解析它。

测试代码:

@click.group()
@click.argument("argument", cls=PerCommandArgWantSubCmdHelp)
@click.pass_context
def main(context, argument):
    """ARGUMENT is required for both subcommands"""
    context.obj = {"argument": argument}


@click.command()
@click.option("--option-1", help="option for subcommand 1")
@click.pass_context
def subcommand1(context, option_1):
    print("subcommand 1: %s %s" % (context.obj["argument"], option_1))


@click.command()
@click.option("--option-2", help="option for subcommand 2")
@click.pass_context
def subcommand2(context, option_2):
    print("subcommand 2: %s %s" % (context.obj["argument"], option_2))


main.add_command(subcommand1)
main.add_command(subcommand2)

if __name__ == "__main__":
    commands = (
        'subcommand1 --help',
        'subcommand2 --help',
        'dummy-argument subcommand1 --help',
    )

    for cmd in commands:
        try:
            print('-----------')
            print('> ' + cmd)
            main(cmd.split())
        except:
            pass

测试结果:

-----------
> subcommand1 --help
Backend TkAgg is interactive backend. Turning interactive mode on.
Usage: test.py subcommand1 [OPTIONS]

Options:
  --option-1 TEXT  option for subcommand 1
  --help           Show this message and exit.
-----------
> subcommand2 --help
Usage: test.py subcommand2 [OPTIONS]

Options:
  --option-2 TEXT  option for subcommand 2
  --help           Show this message and exit.
-----------
> dummy-argument subcommand1 --help
Usage: test.py subcommand1 [OPTIONS]

Options:
  --option-1 TEXT  option for subcommand 1
  --help           Show this message and exit.                

另一种可能的方法是定义一个自定义装饰器,添加通用 argument/option 并在每个子命令上使用该装饰器。这样,click 发出 subcommand --help 时就不会要求 argument/option 了。

click's GitHub Issue 295 中概述了一种可能的实施方式。示例代码展示了如何添加一个通用装饰器,它可以添加不同但共享的选项。但是它确实解决了 OP 的问题。

我做到了

def create(config):
if config is None:
    click.echo('ERROR: Configuration File not found!')
    main(datalake(create(['--help'])))