Python Click Environment Variables: Options and Defaults
python click environment variables: Learn how to configure Click CLI options from environment variables, including defaults, auto mapping, and precedence.
python click environment variables requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
Environment variables are a standard way to inject configuration into a process without hardcoding values. For a Python Click CLI, they let the same command behave differently across development, staging, and production environments. The core mechanism is the envvar parameter on options, but Click also offers automatic mapping and a clear precedence order. This article explains how to use these features correctly and where they commonly break.
Why Environment Variables Matter in Click
A Click command often needs values that vary by deployment: API keys, database URLs, feature flags. Hardcoding them into the script is fragile, and passing every value as a command-line argument becomes tedious. Environment variables solve this by letting the surrounding shell or orchestration system provide values. Click integrates with this pattern directly through the envvar option parameter, which makes an option read from a specified environment variable when no command-line value is given.
Declaring an Option That Reads from an Environment Variable
The simplest usage is to pass envvar to click.option. When the environment variable is set, Click uses its value for the option. The user can still override it with the command-line flag.
import click @click.command() @click.option('--api-key', envvar='MYAPP_API_KEY') def cli(api_key): click.echo(f"Using API key: {api_key}")
If MYAPP_API_KEY is set, running the command without --api-key picks it up. If the user passes --api-key, that value wins. If neither is provided, the option is None unless a default is set.
Using Environment Variables as Defaults
You can combine envvar with default to provide a fallback. The environment variable, when present, overrides the default. This is useful for optional configuration with a sensible fallback.
@click.option('--port', default=8080, envvar='MYAPP_PORT', type=int)
Here MYAPP_PORT is read as a string and converted to an integer because of type=int. If the variable is unset, the default 8080 is used. This pattern is common for ports, timeouts, and other numeric settings.
Automatic Mapping with auto_envvar_prefix
Specifying envvar for every option can become repetitive. Click provides auto_envvar_prefix on a command to automatically map options to environment variables. The variable name is the prefix, an underscore, and the option name in uppercase. Hyphens in option names become underscores.
@click.command() @click.option('--host') @click.option('--port', type=int) def cli(host, port): click.echo(f"{host}:{port}") if __name__ == '__main__': cli(auto_envvar_prefix='MYAPP')
With this setup, MYAPP_HOST and MYAPP_PORT are used automatically. This works for all options on the command, including those defined on subcommands, as long as the prefix is passed to the top-level command. It is a convenient way to standardize configuration naming across a CLI.
Precedence: Command Line, Environment, Default
Click's precedence is explicit: command-line arguments override environment variables, which override defaults. This is the behavior you get with envvar and auto_envvar_prefix. If you need a different order, you must implement custom logic, for example by reading the environment variable yourself and passing it as a default.
import os @click.command() @click.option('--debug', default=os.environ.get('MYAPP_DEBUG', False), is_flag=True) def cli(debug): click.echo(f"Debug: {debug}")
Here the default is computed at import time, which means the environment variable is read when the module loads. This is less flexible than envvar because it does not respect the command-line override in the same way. In practice, stick with Click's built-in precedence unless you have a specific reason to deviate.
Type Conversion and Boolean Handling
Environment variables are always strings. Click applies type conversion based on the type parameter. For integers, floats, and paths, this works transparently. Booleans are trickier. Using type=bool treats any non-empty string as True, and an empty string as False. This can be surprising when a user sets MYAPP_DEBUG=0 expecting False.
@click.option('--debug', envvar='MYAPP_DEBUG', type=bool)
If MYAPP_DEBUG=0, the value is True because the string '0' is non-empty. To handle common boolean representations, use click.Choice or a custom callback. For a flag that should be True only when the variable is set to a specific value, consider flag_value with envvar.
@click.option('--verbose', flag_value=True, default=False, envvar='MYAPP_VERBOSE')
This makes --verbose set the value to True, but the environment variable must contain exactly 'True' or 'true' to match. Click does not normalize case, so be explicit about the expected format.
Common Pitfalls: Empty Strings and Missing Variables
An environment variable set to an empty string is not the same as unset. Click treats an empty string as a provided value, which can cause validation errors or unexpected type conversion failures. For example, MYAPP_PORT= with type=int raises a BadParameter error because an empty string cannot be converted to an integer.
To treat an empty string as missing, you can use a custom callback that checks for emptiness and substitutes a default. Alternatively, ensure the environment does not export empty variables. When a variable is entirely absent, Click falls back to the default or leaves the option as None if no default is given.
Testing and Production Considerations
When testing a Click command, you can set environment variables using monkeypatch or os.environ. The CliRunner from Click's testing module supports an env parameter that sets environment variables for the isolated invocation.
from click.testing import CliRunner def test_cli_envvar(): runner = CliRunner() result = runner.invoke(cli, [], env={'MYAPP_HOST': 'example.com'}) assert result.exit_code == 0
In production, environment variables are a common way to inject secrets, but they are visible in the process environment and can leak through debugging tools or crash reports. Avoid putting highly sensitive data in environment variables if a config file with restricted permissions is an option. Also, be aware that auto_envvar_prefix can inadvertently expose internal option names; document the expected variables clearly.
For maintainability, centralize environment variable names in a constants module or use a configuration loader that maps them to Click options. This reduces duplication and makes the CLI's configuration surface explicit. When a variable is required for the command to work, set required=True on the option so Click enforces its presence, whether from the environment or the command line.