Prompts, confirmation and environment variables

Click · Practical · 10 min

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" in context_settings (or in main()) makes every option read APP_<OPTION>, e.g. --db-url from APP_DB_URL; for subcommands the command name is included, as in APP_DEPLOY_REGION.
  • show_envvar=True documents 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.

Check yourself

  1. An option has envvar='APP_PORT' and default=8000. The user runs `tool --port 9000` with APP_PORT=7000 set. Which value wins?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: click.palletsprojects.com/en/stable/prompts/