Architecture

How to structure a large Python CLI project

Choose frameworks, structure command trees, and design extensible, testable command-line systems that can scale with your product or team.

How to structure a large Python CLI project

Adopt the Strict src/ Layout

Large CLI applications fail when package imports collide with local module names. Isolate your codebase using a strict src/ layout to prevent accidental shadowing during testing. This architecture aligns with established Modern Python CLI Frameworks & Architecture standards. Place all business logic inside src/your_tool/. Keep the repository root strictly for configuration and CI pipelines.

your_tool/
├── pyproject.toml
├── README.md
├── tests/
└── src/
 └── your_tool/
 ├── __init__.py
 ├── cli.py
 ├── commands/
 │ ├── __init__.py
 │ ├── deploy.py
 │ └── analyze.py
 └── core/
 ├── __init__.py
 └── config.py

Configure Entry Points in pyproject.toml

Avoid manual if __name__ == '__main__' blocks in production tooling. Define console_scripts in your build configuration to delegate command parsing to the packaging system. This approach is mandatory when Structuring Multi-Command Python CLIs. It guarantees correct sys.path resolution and enables seamless virtual environment activation.

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "your-tool"
version = "1.0.0"
requires-python = ">=3.10"
dependencies = ["typer>=0.9.0", "rich>=13.0.0"]

[project.scripts]
your-tool = "your_tool.cli:app"

Minimal Command Router (Python 3.10+)

Use modern type hints and a centralized router to manage execution flow. The following pattern leverages typer for automatic help generation and deterministic subcommand dispatch. Keep cli.py intentionally thin. Delegate heavy logic to commands/ modules to maintain strict separation of concerns. This simplifies isolated unit testing across your codebase.

import typer
from your_tool.commands import deploy, analyze

app = typer.Typer(add_completion=False)
app.command()(deploy.main)
app.command()(analyze.main)

if __name__ == "__main__":
 app()

Resolve ModuleNotFoundError During Local Execution

Developers frequently encounter ModuleNotFoundError when executing scripts directly via python src/your_tool/cli.py. Python's import system does not recognize src/ as a package root in this context. Run pip install -e . at the repository root to resolve this. Invoke the CLI exclusively via its registered entry point: your-tool deploy --env prod. This forces Python to resolve imports through installed package metadata.