A practical guide covering argument parsing, configuration management,
output formatting, testing, and distribution for Python CLI tools.
Use Click over argparse. Click provides a decorator-based API that scales
better for complex CLIs with multiple command groups.
@click.group()
@click.option("--verbose", "-v", is_flag=True)
@click.pass_context
def cli(ctx, verbose):
ctx.ensure_object(dict)
ctx.obj["verbose"] = verbose
@cli.command()
@click.argument("name")
@click.option("--count", default=1, type=int)
def greet(name, count):
for _ in range(count):
click.echo(f"Hello {name}")Key principles:
@click.group() for multi-command CLIs@click.option() for optional params, @click.argument() for required positionaltype=click.Choice(...) and type=click.Path(exists=True) for validationclick.pass_context or custom decoratorsLayer configuration with this priority order (highest first):
1. Command-line flags — explicit user intent
2. Environment variables — deployment/CI configuration
3. Config file — project-level defaults
4. Hardcoded defaults — sensible fallbacks
Use TOML for config files — it's the Python ecosystem standard (PEP 680).
Support multiple output formats for different use cases:
| Format | Use Case |
|---|---|
| Table | Human-readable terminal output |
| JSON | Piping to jq, programmatic consumption |
| CSV | Spreadsheet import, data analysis |
| YAML | Configuration file generation |
Always use --output-format or -f as the standard flag name.
click.ClickException for user-facing errors (prints cleanly, exits 1)click.Abort for interrupted operations (Ctrl+C)sys.exit(code) with specific exit codes for machine consumptionClick provides CliRunner for testing commands in isolation:
from click.testing import CliRunner
from myapp.main import cli
def test_greet():
runner = CliRunner()
result = runner.invoke(cli, ["greet", "World"])
assert result.exit_code == 0
assert "Hello World" in result.outputTest categories:
Package your CLI with pyproject.toml:
[project.scripts]
mycli = "mypackage.main:main"Distribution options:
pip install mycli (widest reach)*By Datanest Digital*