How I manage Python in 2024

No more frustration

How I manage Python in 2024

In the last 5 years, Python development has improved leaps and bounds. I am primarily a JavaScript developer, so I have been pleased to see the growth in tools that provide version and dependency management, formatting, linting and type safety.

TL;DR: I use mise for Python version management and virtual environments, poetry or uv for dependency management, ruff for formatting and linting, and pydantic for runtime schemas.

Python version management

I use mise from https://mise.jdx.dev/lang/python.html. Python is included as a core plugin. Not only can Mise be used for Python, but also for Go, Node.js, and other languages and runtimes.

mise respects .python-version and .tool-versions files that may already be in your team’s repo. It also introduces .mise.toml which provides more features. For example, you can use it to create a virtualenv automatically when changing to a directory (see here for more information).

.mise.toml
[tools]
python = "3.11"
[env]
_.python.venv = { path = ".venv", create = true } # create the venv if it doesn't exist

Replaces:

Virtual environments

Using virtual environments is important to avoid polluting your system Python with installed packages (If you are a JavaScript developer, you can think of them like node_modules directories). The issue is ameliorated somewhat if you use mise to manage versions, but nevertheless it is useful to have a different environment for each project. Mise also helps in that regard due to the aforementioned ability to automatically create virtual environments.

There is nothing magical about the way mise creates virtual environments. You can use Python to create them manually: python -m venv .venv.

Replaces:

Dependency management

I oscillate between uv or poetry depending on the use case.

uv is a is a drop-in replacement for pip. It is much faster to install packages. It can also create virtual environments with uv venv as a convenience. uv may change its API surface in the future as it merges with another tool (see the blog post). Like pip, dependencies can installed from or frozen into a requirements.txt file.

poetry is a little more structured. If you’re coming from the JavaScript world like me, it functions as a package manager similar to npm. It has a pyproject.toml file that is similar to package.json. It also has a lock file, poetry.lock, similar to package-lock.json. It also interplays with virtual environments. From the documentation:

Poetry will always work isolated from your global Python installation. To achieve this, it will first check if it’s currently running inside a virtual environment. If it is, it will use it directly without creating a new one. But if it’s not, it will use one that it has already created or create a brand new one for you.

A pyproject.toml file might look like this:

pyproject.toml
[tool.poetry]
name = "poetry-demo"
version = "0.1.0"
description = ""
authors = ["Sébastien Eustace <sebastien@eustace.io>"]
readme = "README.md"
packages = [{include = "poetry_demo"}]
[tool.poetry.dependencies]
python = "^3.7"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

It’s possible to use poetry and mise together using the mise-poetry plugin. poetry will then manage the dependencies and the virtual environment, while mise will manage the Python version.

poetry + mise is my preferred combination in 2024 when I am working on a structured project (it’s great to be able to poetry add something and have it update the pyproject.toml), otherwise I will reach for uv and mise.

Replaces:

Formatting and linting

ruff (created by the same folks as uv) can format, lint and organize imports in code. It has a VSCode extension. I also use Pylance for type checking and analysis. You can enable typechecking in VSCode by adding the following to your .vscode/settings.json:

{
"python.analysis.typeCheckingMode": "basic"
}

Replaces:

Runtime schemas

pydantic is a data validation library. It can validate data coming in, and serialize data going out. It is to Python what zod is to TypeScript.

Replaces: