src layout vs flat layout: which to use and why
A packaging mistake can pass every local pytest run and still break in production. The choice between flat layout and src layout decides whether your tests catch it before users do.
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.
How the flat layout masks packaging bugs
When you run pytest from the project root, Python adds . (the current directory) to sys.path. In a flat layout, import mypackage resolves to the local directory, not the installed package. Tests run against the raw source files.
This works fine until something breaks in production but not in your tests. Common failures that flat layout hides:
- A file missing from the package’s
includeconfiguration still imports from the local directory, so tests pass while the published wheel is broken. - A misconfigured package name in
pyproject.tomlonly fails after installation, not during development. - Data files (templates, config files) included by local path in development but absent from the built wheel cause errors only on the user’s machine.
- A renamed module that still resolves locally because the stale file lingers in the working directory after a refactor.
The src layout closes this gap. Because src/ is not on sys.path by default, import mypackage raises ModuleNotFoundError unless the package is installed. Running uv sync installs it in editable mode, so tests run against the installed version and catch these mistakes before publication.
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) |
Flat | No | Web apps and internal tools you run directly |
uv init --lib |
Src | Yes | Reusable libraries |
uv init --package |
Src | Yes | Distributable CLI tools |
Applications you run directly with uv run do not need a build backend and benefit from the flat layout’s simplicity. Libraries and CLI tools you publish to PyPI benefit from the src layout’s packaging guarantees.
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 tests to validate the installed package, not raw source files
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 catches packaging mistakes before they reach users, and uv init --lib or uv init --package set up the configuration for you.