click vs argparse
Both build CLI tools. click does it with less code:
# argparse — 12 lines for a simple command
import argparse
parser = argparse.ArgumentParser(description="Say hello")
parser.add_argument("name", help="Your name")
parser.add_argument("--count", type=int, default=1, help="Times to repeat")
args = parser.parse_args()
for _ in range(args.count):
print(f"Hello, {args.name}!")
# click — same result, cleaner
import click
@click.command()
@click.argument("name")
@click.option("--count", default=1, help="Times to repeat")
def hello(name, count):
for _ in range(count):
click.echo(f"Hello, {name}!")
Install: pip install click
Basic Commands
import click
@click.command()
@click.argument("filename")
@click.option("--output", "-o", default="output.txt", help="Output file")
@click.option("--verbose", "-v", is_flag=True, help="Show details")
def process(filename, output, verbose):
"""Process FILENAME and write results to OUTPUT."""
if verbose:
click.echo(f"Processing {filename}...")
# Your logic here
click.echo(f"Done → {output}")
if __name__ == "__main__":
process()
Run: python script.py data.csv --output results.txt -v
Auto-generated help:
Usage: script.py [OPTIONS] FILENAME
Process FILENAME and write results to OUTPUT.
Options:
-o, --output TEXT Output file
-v, --verbose Show details
--help Show this message and exit.
Arguments vs Options
@click.command()
@click.argument("src") # Required, positional
@click.argument("dst", default=".") # Optional, positional
@click.option("--force", is_flag=True) # Optional, named flag
@click.option("--mode", type=click.Choice(["fast", "safe"]), default="safe")
def copy(src, dst, force, mode):
click.echo(f"Copying {src} → {dst} (mode={mode}, force={force})")
Arguments: positional, usually required
Options: named with --flag, usually optional
Type Validation
@click.command()
@click.option("--count", type=int, default=1) # Must be integer
@click.option("--ratio", type=float) # Must be float
@click.option("--mode", type=click.Choice(["a", "b"])) # Must be one of
@click.option("--file", type=click.Path(exists=True)) # Must exist
@click.option("--out", type=click.Path()) # Path (doesn't check)
@click.option("--range", type=click.IntRange(1, 10)) # Int within range
def process(count, ratio, mode, file, out, range):
pass
click validates and shows a clear error if the wrong type is provided.
Prompts and Confirmation
@click.command()
@click.option("--name", prompt="Your name", help="Who to greet")
@click.option("--password", prompt=True, hide_input=True, confirmation_prompt=True)
def setup(name, password):
click.echo(f"Hello, {name}!")
@click.command()
@click.argument("path")
def delete(path):
click.confirm(f"Delete {path}?", abort=True) # Stops if user says no
click.echo(f"Deleted {path}")
Multiple Commands (CLI Groups)
import click
@click.group()
def cli():
"""My project CLI."""
pass
@cli.command()
@click.argument("name")
def add(name):
"""Add a new item."""
click.echo(f"Added: {name}")
@cli.command()
def list():
"""List all items."""
click.echo("item1\nitem2\nitem3")
@cli.command()
@click.argument("name")
def delete(name):
"""Delete an item."""
click.confirm(f"Delete {name}?", abort=True)
click.echo(f"Deleted: {name}")
if __name__ == "__main__":
cli()
Usage:
python cli.py add "my item"
python cli.py list
python cli.py delete "my item"
python cli.py --help
Colors and Formatting
@click.command()
def status():
click.echo(click.style("✅ Success", fg="green"))
click.echo(click.style("⚠️ Warning", fg="yellow"))
click.echo(click.style("❌ Error", fg="red", bold=True))
# Progress bar
items = list(range(100))
with click.progressbar(items, label="Processing") as bar:
for item in bar:
process(item)
Passing Context Between Commands
@click.group()
@click.option("--debug", is_flag=True)
@click.pass_context
def cli(ctx, debug):
ctx.ensure_object(dict)
ctx.obj["debug"] = debug
@cli.command()
@click.pass_context
def run(ctx):
if ctx.obj["debug"]:
click.echo("Debug mode on")
click.echo("Running...")
Making it Installable
Add to pyproject.toml or setup.py:
[project.scripts]
mytool = "mypackage.cli:cli"
After pip install -e .:
mytool add "item" # Works globally
When to Use click vs argparse
| Scenario | Use |
|---|---|
| Simple script, no deps |
argparse (stdlib) |
| Complex CLI with subcommands | click |
| Beautiful output, progress bars | click |
| Distributable tool | click |
Further Reading
- Python argparse: Build CLI Tools in 10 Minutes
- Python Virtual Environments: venv, pip, requirements.txt
Get the Full Pipeline
This article is part of the Python AI Publishing Pipeline series — a complete system to write, validate, and publish technical ebooks with Python and Claude.
📋 Free checklist: 7 steps to ship a Python ebook — PDF, no email required.
🚀 Full pipeline + source code: germy5.gumroad.com/l/xhxkzz — $9.99, 30-day money-back guarantee.
If this was useful, the ❤️ button helps other developers find it.
Building a Python content pipeline? I sell the complete automation system as a one-time download — Dev.to API, Claude API, launchd, Gumroad. Check it out ($9.99)
Top comments (0)