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
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.