C.W.K.
Stream
Lesson 02 of 04 · published

click — Decorator-Style CLIs

~18 min · click, cli, decorator

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

click — third-party but ubiquitous

click is the most popular third-party CLI library. Decorator-style: @click.command(), @click.option("--name"), @click.argument("target") on a function. The function's parameters get passed the parsed values. Cleaner-reading than argparse for many cases, especially with subcommands.

Commands and groups

@click.group() creates a parent command that has subcommands. Each subcommand is a @click.command() registered via @parent.command(). Hierarchies are easy: tool.py users add, tool.py users delete, tool.py reports show. The CLI structure mirrors the function definitions naturally.

Options and arguments

@click.argument for positionals (required by default). @click.option for flags and named options. type=int, type=click.Choice([...]), type=click.Path(exists=True) for type/validation. multiple=True for repeatable flags. prompt=True for interactive input. The click types are richer than argparse's bare type=int.

click.echo and color

click.echo("...") wraps print with platform-specific newline handling and the ability to be silenced/redirected. click.secho("warning", fg="yellow") for colored output (auto-disabled when output isn't a terminal). click.confirm, click.prompt for interactive question/answer. Small things that add up to a polished UX.

Principle: click is the right default when you want decorator style and a polished UX (color, prompts, progress bars). argparse is the right default when you want zero dependencies. typer (next lesson) layers type-hints on top of click and is a strong third option.

Code

click — minimal·python
# pip install click
import click

@click.command()
@click.argument("name")
@click.option("--count", default=1, type=int, help="How many greetings.")
@click.option("--shout", is_flag=True)
def hello(name, count, shout):
    """Greet someone NAME times."""
    msg = f"Hello, {name}!"
    if shout:
        msg = msg.upper()
    for _ in range(count):
        click.echo(msg)

if __name__ == "__main__":
    hello()

# python tool.py alice --count 3 --shout
# HELLO, ALICE!
# HELLO, ALICE!
# HELLO, ALICE!
Groups and subcommands·python
import click

@click.group()
def cli():
    """My CLI tool."""
    pass

@cli.command()
@click.argument("name")
def add(name):
    """Add a user."""
    click.echo(f"adding {name}")

@cli.command()
@click.argument("name")
def remove(name):
    """Remove a user."""
    click.echo(f"removing {name}")

if __name__ == "__main__":
    cli()

# python tool.py add alice
# python tool.py remove bob
# python tool.py --help            shows subcommands
# python tool.py add --help        shows subcommand help
Rich types — Path, Choice, multiple·python
import click

@click.command()
@click.argument("input", type=click.Path(exists=True))
@click.option("--mode", type=click.Choice(["dev", "prod"]), default="dev")
@click.option("--tag", multiple=True, help="can be specified multiple times")
def cli(input, mode, tag):
    click.echo(f"input: {input}")
    click.echo(f"mode: {mode}")
    click.echo(f"tags: {list(tag)}")

# python tool.py /etc/hostname --mode prod --tag a --tag b
# input: /etc/hostname
# mode: prod
# tags: ['a', 'b']

# Path(exists=True) auto-validates
Color and interactive prompts·python
import click

@click.command()
def interactive():
    name = click.prompt("Your name")
    age = click.prompt("Your age", type=int)

    if click.confirm(f"You're {name}, age {age}?", default=True):
        click.secho(f"Hello {name}!", fg="green", bold=True)
    else:
        click.secho("Cancelled.", fg="red")

# Auto-handles tty vs piped: colors disabled when stdout isn't a terminal

External links

Exercise

Build a click CLI with two subcommands: greet (takes a name and --count int) and farewell (takes a name and --formal flag). Use @click.group() for the parent. Add help text. Test by running python tool.py greet alice --count 3 and python tool.py farewell bob --formal. Verify python tool.py --help shows both subcommands.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.