browniebroke.com

Convert a Poetry package to the src layout

January 19, 2022 • 3 min read
Edit on Github

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:

  1. Create a src/ folder. And I really mean folder, not package: do not create a __init__.py in that folder.

  2. Move my_package into src/.

  3. Change you packages section in your pyproject.toml:

      packages = [
    -     { include = "my_package" },
    +     { include = "my_package", from = "src" },
      ]
  4. 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
  5. 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!

Liked it? Please share it!

© 2024, Built with Gatsby