有没有办法通过全局标志禁用 Click 选项提示?

Is there a way to disable Click options from prompting via a global flag?

我正在使用 click to implement a command-line interface in Python. Click has a feature that can prompt for a value if one isn't specified。像这样:

@click.command()
@click.option('--name', prompt=True)
def hello(name):
    click.echo(f"Hello {name}!")

这在交互式使用 CLI 时非常好,但在非交互式 运行 期间提示是不好的(例如,可以挂起 shell 脚本)。我想添加一个 -q 标志,该标志将全局禁用对所有其他选项的提示。

有直接的方法吗?

你可以定义一个Command Group that accepts a -q flag, then attach all the commands that you would want to be affected by this -q flag as subcommands of that group. Save the -q existence/non-existence in the group function, and pass it around to the subcommands as part of the command Context.

https://click.palletsprojects.com/en/8.0.x/commands/#nested-handling-and-contexts

Each time a command is invoked, a new context is created and linked with the parent context. Normally, you can’t see these contexts, but they are there. Contexts are passed to parameter callbacks together with the value automatically.

import click

@click.group()
@click.option("-q", help="Disable all prompts", flag_value=True, default=False)
@click.pass_context
def cli(ctx, q):
    # Ensure that ctx.obj exists and is a dict
    ctx.ensure_object(dict)
    # Apply group-wide feature switches
    ctx.obj["q"] = q
    print(f"In group: {ctx.obj}")

@cli.command()
@click.option("--name", prompt=True)
def cmd1(name):
    click.echo(f"Hello {name}!")
$ python cli.py 
Usage: cli.py [OPTIONS] COMMAND [ARGS]...

Options:
  -q      Disable all prompts
...

Commands:
  cmd1

$ python cli.py cmd1 
In group: {'q': False}
Name: abc
Hello abc!

$ python cli.py cmd1 --name=xyz 
In group: {'q': False}
Hello xyz!

$ python cli.py -q cmd1 --name=xyz
In group: {'q': True}
Hello xyz!

在这里,请注意 -q 整个 CLI 的标志,而不仅仅是 cmd1,据我了解,.它不会影响 cmd1 ,但它存在于该组的所有命令的上下文中。

那么我们如何根据上下文中的 q 实际禁用提示?您可以定义自定义 click.Option class and override the prompt_for_value(ctx) 方法。

import click

DEFAULTS = {
    "name": "automatically-set-name",
}

class DynamicPromptOption(click.Option):
    def prompt_for_value(self, ctx):
        q = ctx.obj.get("q")
        print(f"In option {self.name}: q={q}")
        if q:
            return DEFAULTS[self.name]
        return super().prompt_for_value(ctx)

在这里,当 -q 被传递时(从技术上讲,当 qTrue-ish 时),您跳过对 prompt_for_value 的基本实现调用,并且只是 return 一些自动默认值(在我上面的示例中,可能来自全局常量或值字典)。

然后,确保在 @click.option 装饰器上设置 cls=<custom class> 参数以使用自定义 Option class.

@cli.command()
@click.option("--name", prompt=True, cls=DynamicPromptOption)
def cmd1(name):
    click.echo(f"Hello {name}!")
$ python cli.py cmd1 
In group: {'q': False}
In option name: q=False
Name: abc
Hello abc!

$ python cli.py -q cmd1 
In group: {'q': True}
In option name: q=True
Hello automatically-set-name!

好的是交互使用应该不受影响:

$ python cli.py cmd1 
In group: {'q': False}
In option name: q=False
Name: abc
Hello abc!

$ python cli.py cmd1 --name=xyz
In group: {'q': False}
Hello xyz!

$ python cli.py cmd1 --name
In group: {'q': False}
Error: Option '--name' requires an argument.

如果 -q 标志不是硬性要求,并且目标是基本上覆盖自动化目的的提示行为,典型的解决方案是 从环境变量中获取选项值。我认为这是一个更简单的解决方案 ,它确实实现了 -q 标志。

https://click.palletsprojects.com/en/8.0.x/options/#values-from-environment-variables

A very useful feature of Click is the ability to accept parameters from environment variables in addition to regular parameters. This allows tools to be automated much easier. For instance, you might want to pass a configuration file with a --config parameter but also support exporting a TOOL_CONFIG=hello.cfg key-value pair for a nicer development experience.
...
To enable this feature, the auto_envvar_prefix parameter needs to be passed to the script that is invoked. Each command and parameter is then added as an uppercase underscore-separated variable. If you have a subcommand called run taking an option called reload and the prefix is WEB, then the variable is WEB_RUN_RELOAD.

import click

@click.group()
def cli():
    pass

@cli.command()
@click.option("--name", prompt=True, show_envvar=True)
def cmd1(name):
    click.echo(f"Hello {name}!")

if __name__ == "__main__":
    cli(auto_envvar_prefix="NON_INTERACTIVE")
$ python cli.py cmd1 --help
Usage: cli.py cmd1 [OPTIONS]

Options:
  --name TEXT  [env var: NON_INTERACTIVE_CMD1_NAME]
  --help       Show this message and exit.

$ python cli.py cmd1 
Name: abc
Hello abc!

$ python cli.py cmd1 --name=xyz
Hello xyz!

$ export NON_INTERACTIVE_CMD1_NAME=xyz 
$ python cli.py cmd1
Hello xyz!

$ unset NON_INTERACTIVE_CMD1_NAME
$ python cli.py cmd1
Name: abc
Hello abc!

您不需要在 CLI 代码中处理 -q,而是从任何自动化系统或 运行 您的 CLI 中实现它。然后,您可以为不同的默认值集创建不同的 .env 文件,然后根据需要读入它们(如果您想交互式地使用 CLI,也可以不读入):

$ cat defaults.env
NON_INTERACTIVE_CMD1_NAME=XYZ
NON_INTERACTIVE_CMD2_VERSION=1.0.0
NON_INTERACTIVE_CMD3_PORT=3000

$ cat automate.sh
#!/usr/bin/env bash

# 
set -a
source defaults.env
set +a

python cli.py cmd1

$ ./automate.sh 
Hello XYZ!

$ python cli.py cmd1
Name: xyz
Hello xyz!