click β CLI Builder#
What it is#
click (Command Line Interface Creation Kit) lets you build CLIs with Python decorators rather than manual argument parsing. It handles --options, positional arguments, subcommand groups, prompts, env vars, help text, and error messages automatically.
Install#
pip install click
Quick example#
# greet.py
import click
@click.command()
@click.option("--name", default="World", help="Name to greet")
@click.option("--count", default=1, type=int, help="How many times")
def hello(name, count):
for _ in range(count):
click.echo(f"Hello, {name}!")
if __name__ == "__main__":
hello()
python greet.py --name Alice --count 3
python greet.py --help
Output:
Hello, Alice!
Hello, Alice!
Hello, Alice!
Usage: greet.py [OPTIONS]
Options:
--name TEXT Name to greet
--count INTEGER How many times
--help Show this message and exit.
When / why to use it over argparse#
| Feature | click | argparse |
|---|---|---|
| Decorator syntax | β | β (imperative) |
| Subcommand groups | β simple | β οΈ verbose |
| Prompts / confirmation | β built-in | β manual |
| Env var reading | β built-in | β manual |
| Testing utilities | β
CliRunner | β |
| Standard library | β requires install | β |
Use argparse only when zero-dependency is a hard requirement. Otherwise, click is almost always the better choice.
Common pitfalls#
[!WARNING]
click.echovsclick.echo()instead ofprint()in click commands. It handles text encoding across platforms correctly and works with theCliRunnertest utility.
[!WARNING] Pythonβs
if __name__ == "__main__": main()is required β click commands are not standalone callables. Decorate with@click.command()and then call from the guard block, or use an entry point inpyproject.toml.
[!TIP] Use
@click.option("--verbose", "-v", is_flag=True)for boolean flags. Flags set toTruewhen present andFalsewhen absent.
Richer example β command group#
# app.py
import click
import json
from pathlib import Path
@click.group()
def cli():
"""Data management tool."""
pass
@cli.command()
@click.argument("filename", type=click.Path(exists=True))
@click.option("--field", "-f", required=True, help="JSON field to extract")
def extract(filename, field):
"""Extract a field from a JSON file."""
data = json.loads(Path(filename).read_text())
value = data.get(field, "<not found>")
click.echo(value)
@cli.command()
@click.argument("name")
@click.option("--upper/--no-upper", default=False, help="Uppercase the name")
def greet(name, upper):
"""Greet someone."""
msg = f"Hello, {name}!"
click.echo(msg.upper() if upper else msg)
if __name__ == "__main__":
cli()
python app.py greet Alice --upper
python app.py --help
Output:
HELLO, ALICE!
Usage: app.py [COMMAND] [ARGS]...
Data management tool.
Commands:
extract Extract a field from a JSON file.
greet Greet someone.
Common option types#
| Type | Declaration | Notes |
|---|---|---|
| String | type=str (default) | |
| Integer | type=int | |
| Float | type=float | |
| Boolean flag | is_flag=True | --verbose sets True |
| Choice | type=click.Choice(["a","b"]) | Validates and shows in help |
| Path (exists) | type=click.Path(exists=True) | Validates file/dir exists |
| Path (writable) | type=click.Path(writable=True) | |
| File | type=click.File("r") | Opens a file, handles - for stdin |
Testing with CliRunner#
from click.testing import CliRunner
from app import greet
def test_greet():
runner = CliRunner()
result = runner.invoke(greet, ["Alice", "--upper"])
assert result.exit_code == 0
assert "HELLO, ALICE!" in result.output