skip to content

click β€” CLI Builder

Build command-line interfaces with click using decorators. Covers commands, options, arguments, groups, prompts, and progress bars.

3 min read 8 snippets yesterday quick read

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#

Featureclickargparse
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.echo vs print β€” use click.echo() instead of print() in click commands. It handles text encoding across platforms correctly and works with the CliRunner test 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 in pyproject.toml.

[!TIP] Use @click.option("--verbose", "-v", is_flag=True) for boolean flags. Flags set to True when present and False when 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#

TypeDeclarationNotes
Stringtype=str (default)
Integertype=int
Floattype=float
Boolean flagis_flag=True--verbose sets True
Choicetype=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)
Filetype=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