Python And The 'src-vs-flat' Layout Debate
I recently needed to setup a new Python project and in the course of doing so, learned about uv1 , “an extremely fast Python package and project manager”. uv is great. It is easy to install (just a single executable!), and it takes care of all the essential elements of a Python project. Unfortunately, I immediately came across one major problem–uv uses the wrong default for project layouts.
This is the default layout of a project created by uv:
% uv init uv-default-project
% tree uv-default-project
uv-default-project
├── README.md
├── main.py
└── pyproject.toml
This is the default layout of a project created by Poetry2, a comprehensive tool for building, packaging, and publishing Python code:
% poetry new poetry-default-project
% tree poetry-default-project
poetry-default-project
├── README.md
├── pyproject.toml
├── src
│ └── poetry_default_project
│ └── __init__.py
└── tests
└── __init__.py
The former layout, chosen by uv, is known as flat layout. Poetry, in contrast, uses src layout. These terminologies comes from their usage in the Python Packaging User Guide.
The flat layout seems simpler and better, but has a number of issues. During development, it puts everything within the project into Python’s environment, which is a bad practice. It also does not clearly differentiate between working code and test code, which can be problematic during build or distribution. With modern tooling, there are little downsides to using src layout.
As far as I can tell, the decision for uv to make flat layout the default was made around Aug 27th, 20243. While the decision for Poetry to prefer src layout came in Feb of 20254.
Why did the uv maintainers choose flat layout?
I got some feedback on Twitter that it’d be nice if uv init created something that you could then uv run and immediately see feedback, similar to how cargo new gives you a “Hello, world!” application. You shouldn’t need to figure out how to change the project to make it runnable, etc.
However, they could have accomplished the same thing by choosing “packaged application” as default:
% uv init --package uvpackage
% tree uvpackage
uvpackage
├── README.md
├── pyproject.toml
└── src
└── uvpackage
└── __init__.py
% cd uvpackage && uv run uvpackage
Using CPython 3.13.2 interpreter at: /opt/homebrew/opt/python@3.13/bin/python3.13
Creating virtual environment at: .venv
Built uvpackage @ file:///Users/jcheng/tmp/uv-poetry/uvpackage
Installed 1 package in 1ms
Hello from uvpackage!
The packaged application option would’ve used the src layout and achieved the state goal of making it easy to run a new project. It would’ve had the security and robustness benefits of the src layout5. It would’ve been the better choice. If you are using uv
today, as I do, the fix is simple–always use uv init --package
to create you project. Then, add the following steps manually:
touch src/<package>/py.typed # This ensures mypy works consistently
mkdir tests && touch tests/__init__.py
Why do I care about this? The debate between src vs flat layout has gone on for more than a decade in the Python community. Where there is no consensus, you had better understand the implication of your choice. It might not be wrong for the uv maintainers to prefer flat layout over src. But it would be foolish of you to go with the defaults without acknowledging the consequences.
I’d be remiss to not include this article which illustrates the on-going battle between src-vs-flat, where, at the end, the author concluded with the triumph of src layout in the Python community.
I used to scoff when I saw Python projects that put their packages into a separate src directory…. To my surprise, cryptography – one of Python’s most modern projects – adopted a src directory … a significant number of projects moved to src since this article has been published… 2021: NASA landed another robot on Mars, and they use src directories in their Python code. My job here is done.
Perhaps not. The src-vs-flat layout debate lives on.
The PR from uv to make flat layout the default https://github.com/astral-sh/uv/pull/6689. The motivation behind the change can be found in https://github.com/astral-sh/uv/issues/6471. ↩︎
The PR from Poetry which to make src layout the default–https://github.com/python-poetry/poetry/pull/10135, and the rationale–https://github.com/python-poetry/poetry/issues/9390 ↩︎
This article explains why src layout should be preferred. The use of src layout is also recommended by pytest. You should use it if you care about testing your code. ↩︎