All Python templates

Python CLI Tools (Typer)

Build polished CLIs with Typer, Rich output, and clean argument parsing.

DevZone Tools980 copiesUpdated Mar 29, 2026Python
# CLAUDE.md — Python CLI Tools (Typer)

## Project layout

```
src/yourcli/
  __init__.py
  __main__.py        # python -m yourcli
  cli.py             # the Typer app
  commands/
    init.py
    deploy.py
  core/              # business logic, no Typer imports
pyproject.toml
```

- Entry point in `pyproject.toml`:
  ```toml
  [project.scripts]
  yourcli = "yourcli.cli:app"
  ```
- `cli.py` is thin — it parses input and calls into `core/`. All real work lives in `core/`.

## Typer basics

- One `Typer()` app per command tree. Sub-commands attach via `app.add_typer(sub_app, name="sub")`.
- Annotated types for parameters: `name: Annotated[str, typer.Option(help="...")]`. Use `typer.Argument` for positional, `typer.Option` for flags.
- Default values come from the parameter default, not from `typer.Option(default=...)`.
- Boolean flags get `--flag/--no-flag` automatically. Don't write your own.

## Output

- Use **Rich** for formatted output (tables, trees, syntax highlighting). It's already a Typer dep.
- Errors go to stderr (`typer.echo(msg, err=True)` or a `console = Console(stderr=True)`).
- Exit codes matter: `raise typer.Exit(code=1)` on failure. Don't `sys.exit` directly.
- Default to non-color when `stdout` isn't a TTY, or when `NO_COLOR` env var is set. Rich handles both automatically.

## Confirmation & prompts

- Destructive actions confirm: `typer.confirm("Proceed?", abort=True)`.
- Add `--yes` flags to skip confirmation in scripts. Default `False`.
- Don't prompt for required values silently — fail fast with a clear error if the flag is missing in non-interactive mode.

## Configuration

- Config order of precedence (highest to lowest): CLI flag → env var → config file → default.
- Use `pydantic-settings` for env-var/config loading. Validate types at the boundary.
- Config file location: `$XDG_CONFIG_HOME/<yourcli>/config.toml` (Linux/macOS), `%APPDATA%\<yourcli>\config.toml` (Windows). Use `platformdirs`.

## Logging

- `--verbose` / `-v` sets log level to DEBUG. `--quiet` / `-q` suppresses non-error output.
- Use `RichHandler` from `rich.logging`. It prints log entries pretty.
- Log file is optional; keep stdout clean for piping.

## Testing

- `from typer.testing import CliRunner`. Test commands with `runner.invoke(app, ["sub", "--flag"])`.
- Snapshot the output with `result.stdout` for golden-file tests.
- Don't import the CLI module inside `core/` tests — they should run without Typer.

## Don't

- Don't put business logic inside command functions. They're parsers, not orchestrators.
- Don't `print()` from library code that the CLI imports. Use `logging` and let the CLI configure handlers.
- Don't rely on the user's `cwd`. Take paths as arguments and resolve them explicitly.
- Don't add a flag for everything. Every flag is forever.

Other Python templates