← All Templates

Python CLI with Pytest

Rules for building Python CLI applications with Pytest testing and modern tooling.

python cli pytest click ruff

v1.0.0 · Updated Apr 9, 2026

CLAUDE.md — Save as CLAUDE.md in your project root.

Project Setup

Suggest Change
- Use `pyproject.toml` for all project config; no `setup.py` or `setup.cfg`
- Structure: `src/{package_name}/` for source, `tests/` at project root
- Use `python -m venv .venv` then `pip install -e ".[dev]"` for development
- Run npm run lint as `ruff check .` and npm run format as `ruff format .`
- Pin Python version in `pyproject.toml` requires-python field
- Use Read tool to check existing `pyproject.toml` before adding dependencies
- Keep dependencies minimal; prefer stdlib over third-party when reasonable

Testing Strategy

Suggest Change
- Follow RED-GREEN-REFACTOR: write failing test, implement, refactor
- Run tests with npm test as `pytest -x -q` for fast, token-efficient output
- Use `-x` flag to stop on first failure during development
- Organize tests mirroring source: `src/myapp/utils.py` -> `tests/test_utils.py`
- Use `pytest.fixture` for shared setup; prefer function-scoped fixtures
- Use `click.testing.CliRunner` for CLI integration tests
- Use `tmp_path` fixture for file system tests; never write to project directories
- Parametrize repetitive tests with `@pytest.mark.parametrize`
- Avoid mocking internals; mock only at system boundaries (network, filesystem, time)

CLI Patterns

Suggest Change
- Use Click for CLI framework; define commands with `@click.command()` decorators
- Group related commands with `@click.group()`
- Always add `--help` text to commands and options via `help=` parameter
- Use `click.echo()` for output, not `print()`; it handles encoding correctly
- Accept stdin with `click.get_text_stream('stdin')` for pipe-friendly CLIs
- Use `click.Path(exists=True)` for file arguments that must exist
- Add `--verbose` / `-v` flag using `click.option('--verbose', '-v', count=True)`
- Exit codes: 0 for success, 1 for user error, 2 for system error

Error Handling

Suggest Change
- Catch specific exceptions; never bare `except:`
- Use `click.ClickException` for user-facing errors; it formats output and sets exit code
- Log errors with `logging` module; configure in CLI entry point
- For file operations: always check existence before reading with Read tool
- Validate user input early at the CLI boundary; trust data after validation
- Use `contextlib.suppress()` for intentionally ignored exceptions
- Never silence KeyboardInterrupt or SystemExit

Packaging

Suggest Change
- Build with `python -m build` producing wheel and sdist
- Define entry points in `pyproject.toml` under `[project.scripts]`
- Use `__version__` in `__init__.py`; reference from `pyproject.toml` via dynamic versioning
- Include `py.typed` marker for type checking consumers
- Run Bash as `python -m build && twine check dist/*` before publishing
- Add `LICENSE` and `README.md` to package data