Python Project Layout Conventions — Core Concepts
Why Layout Matters
A project layout is an implicit contract. When a developer clones a repo, the folder structure tells them where source code, tests, docs, and configuration live — without reading a single line of documentation. Break that contract and onboarding slows, CI scripts become fragile, and packaging introduces subtle bugs.
The Two Main Layouts
Src Layout
my-project/
├── src/
│ └── my_package/
│ ├── __init__.py
│ └── core.py
├── tests/
├── pyproject.toml
└── README.md
The src/ directory creates a physical barrier between your importable package and everything else. This means tests always import the installed version, catching packaging bugs early. The Python Packaging Authority (PyPA) recommends this pattern for libraries you plan to distribute.
Flat Layout
my-project/
├── my_package/
│ ├── __init__.py
│ └── core.py
├── tests/
├── pyproject.toml
└── README.md
Here the package sits at the root. It’s simpler, works fine for applications you never publish to PyPI, and requires less configuration. However, tests can accidentally import from the local directory instead of the installed package, masking packaging errors.
Supporting Files and Folders
Beyond the package itself, mature projects include:
- tests/ — mirrors the package structure for easy navigation
- docs/ — Sphinx or MkDocs source files
- scripts/ — one-off automation, database seeds, data migration helpers
- pyproject.toml — the single source of truth for build config, dependencies, and tool settings
- .github/ or .gitlab/ — CI/CD pipeline definitions
- Makefile or justfile — common developer commands like
make testorjust lint
Namespace Packages vs Regular Packages
If your organization ships multiple packages under one namespace (e.g., acme.auth, acme.billing), the src layout becomes essential. Each repo contains src/acme/<subpackage>/, and Python’s implicit namespace packages let them coexist after installation without an __init__.py in the acme directory.
Common Misconception
“The layout only matters for open-source libraries.” Internal tools benefit just as much. When your team maintains dozens of microservices, a shared layout convention means any developer can jump between repos and immediately know where tests, configs, and entry points live. The productivity gain compounds with every new repository.
How to Choose
| Factor | Src Layout | Flat Layout |
|---|---|---|
| Distributing on PyPI | Preferred | Acceptable |
| Catching packaging bugs early | Yes | Risk of false passes |
| Single-app repos | Works but adds one folder | Simpler |
| Namespace packages | Required | Difficult |
| Beginner friendliness | Slight learning curve | Intuitive |
If you are unsure, start with src layout. Converting from flat to src later means updating imports, CI paths, and build config — a chore best avoided.
Practical Tip
Run python -m build in a clean virtual environment after any layout change. If the resulting wheel installs and imports correctly in a separate environment, your layout is sound.
The one thing to remember: Pick src layout for libraries, flat layout for quick apps — then enforce that choice with a project template so every repo starts right.
See Also
- Python Api Design Principles Design Python functions and classes that feel natural to use — like a well-labeled control panel.
- Python Code Documentation Sphinx Turn Python code comments into a beautiful documentation website automatically.
- Python Docstring Conventions Write helpful notes inside your Python functions so anyone can understand them without reading the code.
- Python Semantic Versioning Read version numbers like a label that tells you exactly how risky an upgrade will be.
- Ci Cd Why big apps can ship updates every day without turning your phone into a glitchy mess — CI/CD is the behind-the-scenes quality gate and delivery truck.