Python CLI — Command-Line Interface Tools
CLIs make your Python scripts reusable and automatable from the terminal. A well-designed CLI is essential for DevOps tools, data pipelines, and developer utilities.
Learning Objectives
- Build CLIs with argparse (standard library)
- Create rich CLIs with click (decorators)
- Build type-safe CLIs with typer
- Add subcommands, options, and validation
argparse — Standard Library
import argparse
# Create parser
parser = argparse.ArgumentParser(
description="Process files and generate reports",
epilog="Example: python process.py data.csv -o report.html -v"
)
# Positional arguments
parser.add_argument("input", help="Input file path")
# Optional arguments
parser.add_argument("-o", "--output", default="output.txt",
help="Output file (default: output.txt)")
parser.add_argument("-v", "--verbose", action="store_true",
help="Enable verbose output")
parser.add_argument("-n", "--count", type=int, default=1,
help="Number of times to repeat")
parser.add_argument("--format", choices=["csv", "json", "html"],
default="csv", help="Output format")
# Parse and use
args = parser.parse_args()
print(f"Input: {args.input}")
print(f"Output: {args.output}")
print(f"Verbose: {args.verbose}")
click — Decorator-Based CLI
import click
@click.command()
@click.argument("filename")
@click.option("--count", "-n", default=1, help="Number of times to repeat")
@click.option("--verbose", "-v", is_flag=True, help="Verbose output")
@click.option("--format", type=click.Choice(["csv", "json", "html"]), default="csv")
def process(filename, count, verbose, format):
"""Process a file and generate output."""
for i in range(count):
if verbose:
click.echo(f"Processing {filename} ({i+1}/{count})")
click.echo(f"Done! Format: {format}")
# Subcommands
@click.group()
def cli():
pass
@cli.command()
@click.argument("name")
def greet(name):
"""Say hello to someone."""
click.echo(f"Hello, {name}!")
@cli.command()
@click.argument("name")
def goodbye(name):
"""Say goodbye to someone."""
click.echo(f"Goodbye, {name}!")
if __name__ == "__main__":
cli()
# Usage: python app.py greet Alice
# python app.py goodbye Bob
typer — Modern CLI with Type Hints
import typer
from typing import Optional
from enum import Enum
app = typer.Typer()
class Format(str, Enum):
csv = "csv"
json = "json"
html = "html"
@app.command()
def process(
filename: str,
count: int = typer.Option(1, "--count", "-n"),
verbose: bool = typer.Option(False, "--verbose", "-v"),
format: Format = typer.Option(Format.csv),
):
"""Process a file and generate output."""
for i in range(count):
if verbose:
typer.echo(f"Processing {filename} ({i+1}/{count})")
typer.echo(f"Done! Format: {format.value}")
@app.command()
def hello(name: str, formal: bool = False):
"""Say hello to someone."""
if formal:
typer.echo(f"Good day, {name}.")
else:
typer.echo(f"Hey, {name}!")
if __name__ == "__main__":
app()
Comparison
| Feature | argparse | click | typer |
|---|---|---|---|
| Setup | Verbose | Decorators | Type hints |
| Learning curve | Low | Medium | Low |
| Subcommands | Manual | Built-in | Built-in |
| Auto-help | Yes | Yes | Yes |
| Shell completion | No | Yes | Yes |
| Best for | Simple CLIs | Complex CLIs | Modern CLIs |
Key Takeaways
- Use argparse for simple CLIs (no dependencies)
- Use click for complex CLIs with many options
- Use typer for type-hint-based CLIs
- Always add
--helpto all commands - Validate inputs early and give clear error messages
- Use
click.echo()instead ofprint()for CLI output - Add shell completion for better UX