32. CLI Argument Parsing

Chapter 32 of 36 · 15 min

Beyond click basics, sophisticated CLI tools need subcommands, chained options, validation, and user feedback.

Advanced argument handling:

import click
from click import Context

@click.group()
def cli():
    """Document intelligence CLI."""
    pass

@cli.command()
@click.argument("sources", nargs=-1, type=click.Path())  # Multiple args
@click.option("--format", "-f", 
              type=click.Choice(["json", "yaml", "csv"]), 
              default="json",
              help="Output format")
@click.option("--threshold", "-t", 
              type=float, 
              default=0.7,
              help="Confidence threshold (0.0-1.0)")
@click.option("--workers", "-w", 
              type=int, 
              default=4,
              help="Parallel workers")
def analyze(sources, format, threshold, workers):
    """Analyze documents from multiple sources."""
    # Validate threshold
    if not 0.0 <= threshold <= 1.0:
        click.echo("Error: threshold must be between 0.0 and 1.0", err=True)
        raise click.Abort()
    
    click.echo(f"Analyzing {len(sources)} sources...")
    click.echo(f"Format: {format}, Threshold: {threshold}, Workers: {workers}")
    
    for source in sources:
        click.echo(f"  - {source}")

@cli.command()
@click.option("--config", "-c", type=click.Path(exists=True), 
              help="Config file (YAML)")
@click.pass_context
def run(ctx, config):
    """Run pipeline with optional config."""
    if config:
        click.echo(f"Loading config from: {config}")
    else:
        click.echo("Using default config")
    
    click.echo("Running pipeline...")

# Add a second-level group
@cli.group(name="model")
def model_group():
    """Model management commands."""
    pass

@model_group.command(name="list")
def model_list():
    """List available models."""
    models = ["gpt-4", "gpt-3.5-turbo", "claude-3-opus"]
    for m in models:
        click.echo(f"  - {m}")

@model_group.command()
@click.argument("model_name")
def download(model_name):
    """Download a model."""
    with click.progressbar(length=100, label=f"Downloading {model_name}") as bar:
        for _ in range(100):
            bar.update(1)

if __name__ == "__main__":
    cli()

Prominent features to notice: nargs=-1 accepts variable positional arguments. click.Choice() validates against a set of allowed values. click.progressbar() shows progress for long operations. Nested command groups (model list, model download) organize complex tools.

Local verification checkpoint

Run the smallest example from this chapter in a local workspace and record the package version, runtime, data path, and observed output. If the result depends on model size, vector count, CPU/GPU backend, or available memory, note that constraint beside the exercise so the lesson remains reproducible.

EXERCISE

Create a CLI that accepts: an output directory, a list of input files (variable arguments), a --model option with a default, and a --dry-run flag that validates everything but doesn't process. Implement validation that checks for file existence and directory writability before proceeding.