ʕ·ᴥ·ʔ Dmitry's Internet Website

An Opinionated Guide to Python Projects

Updated on: 2026-10-18.

This post is meant to be an opinionated guide to setting up and developing a Python project. The goal is to give a paved path that avoids common snags.

Let uv manage Python

Python project management has become drastically simpler with the introduction of uv (which consolidated many tools and made everything way faster). There are a few main things it will handle for you: setting up a project structure, installing Python, and installing the dependencies.

Much of your use of uv can reduce down to simply using uv run to execute everything.

1# Run a script
2uv run file.py
3# Run tests
4uv run pytest
5# Start a Python shell
6uv run python
7# Run a bash script with the Python environment active
8uv run ./mybash.sh

(You can use alias ur="uv run" to type less, but I find I generally don’t need it.)

This all works because uv run X will automatically:

This takes care of all Python and environment management issues (as long as your project is configured correctly).2

Use uv init to get a standard project layout

If you’re starting a brand new project, uv init will create the boilerplate for you, along with a template pyproject.toml.

 1[project]
 2name = "package"
 3version = "0.1.0"
 4description = "Add your description here"
 5readme = "README.md"
 6authors = [
 7    { name = "Dmitry Shemetov", email = "..." }
 8]
 9requires-python = ">=3.15"
10dependencies = []
11
12[project.scripts]
13package = "package:main"
14
15[build-system]
16requires = ["uv_build>=0.12.17,<0.13.0"]
17build-backend = "uv_build"

This is the layout the above command prepares:3

1.
2├── pyproject.toml
3├── README.md
4└── src
5    └── package
6        └── __init__.py

With these contents:

1# __init__.py
2def main() -> None:
3    print("Hello from package!")

As your project develops, you will probably want to add:

Use main as your program entry point

(If your project is a library and doesn’t need an entry point, you can use uv init --lib or simply delete the [project.scripts] section in pyproject.toml.)

How do we actually call our program though? We need an entry point. The sample project above defines it in [project.scripts] in pyproject.toml to be the main function, so

1❯ uv run package
2Hello from package!

This is preferable to executing files directly with uv run python script.py or uv run file.py because the command doesn’t depend on file paths (and relative imports can break when files are run directly).

Generally, it’s handy to have some ways to customize your program’s behaviors. This is where typer can help you to quickly make a CLI by taking much of the boilerplate out of the process of capturing arguments.

Use absolute imports

When importing sibling modules into a module, you can either use relative or absolute imports. My preference is to stick to absolute imports, because this consistency makes repo-wide import searches comprehensive.

1# absolute import in package/module.py
2from package.subpackageA import moduleA
3
4# absolute import in package/subpackageA/moduleA.py
5from package import module

If imports are giving you troubles, some helpful commands for debugging:

1# Print the actual copy of the package imported.
2uv run python -c 'import package; print(package.__file__)'
3# Print the directories Python searches for imports, in order.
4uv run python -c 'import sys; print(*sys.path, sep="\n")'

Standard dependencies

Some useful tools I use in most projects:

1dependencies = [
2    "typer>=0.27.3",
3]
4
5[dependency-groups]
6dev = [
7    "pytest>=9.1.1",
8    "ruff>=0.17.1",
9]

Along with my standard ruff settings:

1[tool.ruff]
2line-length = 120
3lint.extend-select = [
4    "I", # isort
5    "NPY", # numpy
6]
7format.docstring-code-format = true

Further reading

These are handy references if you ever need to go deeper:

Footnotes


  1. If you’re entering an existing project, uv will read either .python-version or pyproject.toml to understand which version of Python your project needs (or else default to something recent). You can also see which Pythons uv has installed on your system with uv python list. ↩︎

  2. If you’re working on an older Python project, uv sync might not work directly. You might need to do uv init --bare (to make sure you have a pyproject.toml), then uv add -r requirements.txt to install and record in uv.lock. ↩︎

  3. We just use uv’s default. The benefits of the src/ layout over the “flat” layout are relatively minor - see here. ↩︎

#python #software