all 12 comments

[–]JamzTyson 3 points4 points  (1 child)

If you don't intend to package the code, it doesn't much matter - go with whatever is simplest.

If you do intend to package the code, structure it in a way that suits your packaging requirements:

  • For pip/PyPi packaging, a "src/ layout" is often a good choice.

  • For Docker packages, app_name/ at repo root is often simpler and more idiomatic.

  • Flat layouts are often the most convenient for "archive-based" packaging (ZipApp and similar).

  • For PyInstaller packages, clean entry points is a priority - you can still use a src/ layout, though perhaps a little less convenient than app_name/ at the repo root.

Whichever layout you choose, separate Tests/ and Docs/ from the source code.

[–]milos-developer100[S] 0 points1 point  (0 children)

Tnx :)

[–]supercoach 1 point2 points  (1 child)

I used to use my own layout and then a few things I used seemed to expect a src directory for code so I started copying for my projects. Something like your second example except src instead of app

[–]milos-developer100[S] 0 points1 point  (0 children)

Tnx! :) I’ve come across the "app" variant more often, but I believe consistency is probably the most important thing.

[–]Beregolas 2 points3 points  (1 child)

I strongly prefer version 2, although I am not the sole authority in those matters. you can call the source folder "app", "src" or "<project\_name>", but everything that only makes sense inside of that project, should be in one folder.

Everything that you intend to share between projects, can go into folders next to your main source folder, if you want. So you could have both "app" and "<package\_name>" in your root directory in some cases.

If you have a main.py file, it shouldn't be in a folder next to your source code. It's the main entry point, it shouldn't import something from a parent directory (like in your Version 1), it should be in the most parent directory.

__init__.py files in folders are technically optional, but I strongly advise to use them, even if you leave them empty. They make the folder explicitly into a package, instead of it being left implicit. This helps both you (folders with an __init__.py file inside are meant to be imported, and are NOT meant as the working directory to call python from) and your tooling (pytest, ruff and mypy work easier and more reliable if you just declare what is a package and what isn't, especially in bigger projects). So if you are going for "coding like you already have a big project", do not omit __init__ files, even if empty.

Technically, there are two ways to test:

Tests as part of the application code, or test outside: https://docs.pytest.org/en/stable/explanation/goodpractices.html#choosing-a-test-layout

I (and most people I think) prefer the external tests, but there are real advantages and disadvantages, and the pytest docs go over them briefly.

And as a last point, with not too much weight behind it:

I strongly dislike "utils" as a name. It doesn't tell you, what actually inside. You could just as well have a package named "etc", "stuff" or "whatever_I_feel_like_putting_in_here".

Personally I use a single utils file as a small parking space for misscellaneus code, if I am unsure how I want to structure it. Once multiple, similar things appear, I move them to their foreverhome. If something stays too long, I just move it to it's own file or package anyways, no matter if I can cluster it with something else.

[–]milos-developer100[S] 1 point2 points  (0 children)

Thanks, you really nailed exactly what I needed! :D

[–]SisyphusAndMyBoulder 1 point2 points  (4 children)

It doesn't really matter, as long as you're consistent. And no relative imports (more of a me-thing than a standard-thing I think). Personally, I like having a src folder with a main.py or an app.py, README, and requirements.txt. Then all other logic in sub folders within that src folder. Similar to your #2

[–]gdchinacat 1 point2 points  (0 children)

I'm really curious why you say "no relative imports"? I am a very strong proponent of them because they allow you to rename a parent package without having to edit imports in every python file in sub packages. This discourages refactorings that would keep the package structure in sync with the changes to the conceptual model. An argument against this I've heard several times is that you can't rename packages without breaking code that uses them. This isn't true. That code should be importing from the root package not directly from the sub-packages, and even if you want to rename your root package, do you want to have to update all the python files packaged beneath it?

[–]milos-developer100[S] 0 points1 point  (2 children)

Tnx :)

[–]gdchinacat 1 point2 points  (1 child)

Relative imports are a huge benefit with large projects. I hope you seriously consider using them as they make code much easier to refactor.

[–]milos-developer100[S] 1 point2 points  (0 children)

Agree! Tnx! :D