Good CLIs work interactively for humans and non-interactively for scripts and CI.
A value is looked up in order of precedence: the command line, then an environment variable, then the default_map (config), then the option's default. If the option has prompt= and the value did not come from the command line or environment, Click asks interactively and offers the default as the suggested answer.
Prompts on options: @click.option("--name", prompt=True) asks Name: only if the option was not given. prompt="Your name" customises the text. For secrets use hide_input=True and confirmation_prompt=True, or the shortcut @click.password_option().
Confirmation: @click.confirmation_option(prompt="Drop all tables?") adds a --yes flag and aborts unless confirmed. Inside code, click.confirm("Continue?", abort=True) does the same, and click.prompt("Port", type=int, default=8080) asks for a typed value anywhere.
Environment variables:
envvar="APP_TOKEN"on an option reads it from the environment (also a list of names).auto_envvar_prefix="APP"incontext_settings(or inmain()) makes every option readAPP_<OPTION>, e.g.--db-urlfromAPP_DB_URL; for subcommands the command name is included, as inAPP_DEPLOY_REGION.show_envvar=Truedocuments the variable in--help.
Values from environment variables go through the same type conversion and validation as command-line values.
For scripts and CI, make sure every prompt can be bypassed with an option or variable; a prompt in a non-interactive run reads from empty stdin and aborts. ctx.get_parameter_source("name") tells you where a value came from.
Example
import click
from click.testing import CliRunner
@click.command(context_settings={"auto_envvar_prefix": "APP"})
@click.option("--region", default="eu-west", show_envvar=True)
@click.option("--token", envvar="API_TOKEN", prompt=True, hide_input=True)
@click.confirmation_option(prompt="Deploy to production?")
def deploy(region, token):
click.echo(f"deploying to {region} with token {token[:2]}***")
runner = CliRunner()
r = runner.invoke(deploy, input="s3cret\ny\n")
print(r.output)
# Token:
# Deploy to production? [y/N]: y
# deploying to eu-west with token s3***
r = runner.invoke(deploy, ["--yes"], env={"APP_REGION": "us-east", "API_TOKEN": "abc"})
print(r.output) # deploying to us-east with token ab***Remember
- Precedence: command line, environment variable, then a prompt (if set), default_map, default
- prompt=, hide_input, password_option and confirmation_option for interactive input
- envvar= and auto_envvar_prefix connect options to the environment
Watch out for
- Prompting in a CI job without an option or env var to supply the value, which aborts the run.