A Click stub for a multi-subcommand CLI
I pick Click over argparse almost every time now. The subcommand pattern is so clean that I can hand the resulting tool to a non-Python-ist and they can read the help and figure out what it does. This is the template I paste into every new admin script.
#!/usr/bin/env python3
"""Example multi-command CLI with shared options."""
from __future__ import annotations
import json
import sys
from pathlib import Path
import click
@click.group(context_settings={"help_option_names": ["-h", "--help"]})
@click.option("-v", "--verbose", count=True, help="Repeat for more output.")
@click.option("--config", type=click.Path(exists=True, path_type=Path),
default=Path("~/.myclirc").expanduser())
@click.pass_context
def cli(ctx: click.Context, verbose: int, config: Path) -> None:
"""Do a thing with several subcommands."""
ctx.obj = {"verbose": verbose, "config": config}
@cli.command()
@click.argument("name")
@click.pass_obj
def greet(obj, name: str) -> None:
"""Say hello."""
if obj["verbose"]:
click.echo(f"(config: {obj['config']})", err=True)
click.echo(f"hello, {name}")
@cli.command(name="dump-config")
@click.pass_obj
def dump_config(obj) -> None:
"""Print the resolved config as JSON."""
click.echo(json.dumps(
{"verbose": obj["verbose"], "config": str(obj["config"])},
indent=2,
))
if __name__ == "__main__":
sys.exit(cli())
ctx.obj is the right place to stash anything shared. click.Path(path_type=Path) returns pathlib.Path directly. See also /snippets/python-csv-stream-large/.