Lab: Click tool
Build ds-tool — a multi-subcommand CLI with scan, report, and clean subcommands, config file support, and rich output.
- Build a top-level Click group with at least three subcommands
- Read user configuration from a TOML file with CLI flags taking precedence
- Use rich output in command results
- Add a confirmation prompt before a destructive operation
This lab builds ds-tool (dataset tool) from scratch — a multi-subcommand CLI
with three commands: scan to discover files, report to display a Rich table
of results, and clean to delete matched files with a confirmation prompt.
The full tool is assembled in four steps. Run each demo independently to verify it works before moving on.
Step 1 — The group and scan subcommand
scan takes a directory and an extension, walks the directory, and collects
file metadata. The group holds shared state (the scan results) in the context:
Step 2 — The report subcommand
report reads from the shared context and displays a Rich table:
Step 3 — The clean subcommand with confirmation
clean deletes the scanned files. It always asks for confirmation before
deleting. The --yes flag skips the prompt for scripting:
Destructive operations must always have a confirmation step. The --yes flag
exists to allow automation and scripting, not to encourage skipping the
confirmation in interactive use. Document the --yes flag clearly so users
know it is for scripts.
Step 4 — Config file support
Add a default directory from ~/.config/ds-tool/config.toml so users do not
have to type the path every time:
import tomllib
import pathlib
def load_config() -> dict:
p = pathlib.Path.home() / ".config" / "ds-tool" / "config.toml"
if p.exists():
with open(p, "rb") as f:
return tomllib.load(f).get("defaults", {})
return {}
@ds.command()
@click.argument("directory", required=False)
@click.option("--ext", default=None)
@click.pass_obj
def scan(obj, directory, ext):
config = load_config()
directory = directory or config.get("directory", ".")
ext = ext or config.get("ext", ".csv")
...With a config file at ~/.config/ds-tool/config.toml containing:
[defaults]
directory = "/data/datasets"
ext = ".parquet"Running ds-tool scan with no arguments scans /data/datasets for .parquet
files. The CLI arguments still override the config values when provided.
What you built
ds-tool now has:
- A top-level group with three subcommands (
scan,report,clean) - Shared state through Click's context object
- Rich table output in the report command
- A confirmation prompt before destructive deletion
- Config file support with CLI flags taking precedence
This is the shape of most real-world CLI tools — the Click skills you used here transfer directly.
Where to go next
Next module: Testing CLIs — using Click's CliRunner to write reliable automated tests for every command path.