src layout vs flat layout: which to use and why
A packaging mistake can pass local pytest runs and break after installation. The src layout reduces accidental imports from the checkout, but testing an editable installation is not the same as testing a built wheel.
Compare the two layouts
Flat layout places the package directory directly at the project root:
-
- pyproject.toml
- README.md
-
- __init__.py
- core.py
-
- test_core.py
Src layout nests the package under a src/ directory:
-
- pyproject.toml
- README.md
-
-
- __init__.py
- core.py
-
-
- test_core.py
The only structural difference is the src/ wrapper. Every other file, including pyproject.toml and tests/, stays at the project root in both cases.
See why flat layout can hide import mistakes
Running python -m pytest from the project root adds the current directory to sys.path; the pytest console script does not do that by itself. pytest’s default import mode can also change the import path while collecting tests. In a flat layout, an import of mypackage can therefore resolve to the checkout even when an installed distribution exists.
That can hide a wrong package name or a module imported only because it is present in the repository. The src layout keeps the importable code outside the project root. With a correctly configured editable installation, such as one managed by uv sync, tests normally import through the installed package mapping rather than an accidental root-directory import.
An editable installation still exposes development files. It does not prove that a wheel includes every module or data file. Before publishing, build a wheel and install it in a clean environment to verify its imports and package data. The setup.py migration guide shows a separate wheel-install check.
See what uv init writes for each project type
uv init chooses a layout based on what you are building:
| Command | Layout | Build system | Use case |
|---|---|---|---|
uv init (default) |
Src | Yes | Apps and CLI tools, with a console script |
uv init --lib |
Src | Yes | Reusable libraries |
uv init --no-package |
Flat | No | Scripts and internal tools you run directly |
The default is the src layout. Libraries and CLI tools you publish to PyPI benefit from separating source imports from the project root. --no-package keeps the project layout flat, which suits scripts you never distribute.
See uv init: project types, flags, and examples for a full breakdown of each flag.
Choose the right layout
Use the src layout when:
- Building a library or CLI you plan to publish to PyPI
- You want development imports to go through an editable installation
Use the flat layout when:
- Building an application you run directly with
uv runand never publish - Adding uv to an existing project with its own established structure
When uncertain, prefer the src layout. It makes accidental root imports less likely, and it is what uv init sets up by default. Test the built wheel separately before publishing.