Convert a Poetry package to the src layout
The src
layout is commonly used in Python ecosystem nowadays (even the pytest docs recommend it), but I’m not really using it on my projects. I think I’ve never hit the pain points it solves, and last time I tried, it made my development workflow more complicated.
I’m not going into the whys, they are detailed in this excellent blog explaining its rationale in detail, where I read about it the first time.
In recent years, as it became more popular, the tooling has caught up, and I wanted to try it again to see if the situation improved.
Starting a new project
If you start a new project, Poetry supports it out of the box in their poetry new
command:
poetry new --src my-package
Although you might want to use a full blown template with other tools also configured (e.g. pytest).
Converting a project
I found it a bit less well documented how to convert a project, so here you go. Assuming the project uses Poetry, pytest and a layout as follows:
my-package
├── poetry.lock
├── pyproject.toml
├── my_package
│ ├── __init__.py
│ └── main.py
└── tests
├── __init__.py
└── test_main.py
Here are the steps I followed to convert to src/
layout, without changing the imports in my tests:
-
Create a
src/
folder. And I really mean folder, not package: do not create a__init__.py
in that folder. -
Move
my_package
intosrc/
. -
Change you
packages
section in yourpyproject.toml
:packages = [ - { include = "my_package" }, + { include = "my_package", from = "src" }, ]
-
Add
pythonpath
to your pytest option as follows:[tool.pytest.ini_options] + pythonpath = ["src"]
If using pytest version lower than 7, you’ll also need to install
pytest-srcpaths
:poetry add -D pytest-srcpaths
-
If your
pyproject.toml
references some paths, make sure to update them, e.g python-semantic-release:[tool.semantic_release] branch = "main" - version_variable = "my_package/__init__.py:__version__" + version_variable = "src/my_package/__init__.py:__version__"
If everything went well, you should have the following layout and running pytest
should work:
my-package
├── poetry.lock
├── pyproject.toml
├── src
│ └── my_package
│ ├── __init__.py
│ └── main.py
└── tests
├── __init__.py
└── test_main.py
Conclusion
This was much easier than I remembered! I think I’ll change all my projects to this layout, having this guide will help me reprodude it. Hopefulluy it’ll help you as well!