Switching to uv

The Rust-based Python package manager that replaced everything in my toolchain.

· 2 min read

I finally converted my last Python project to uv and I want to note why. Six months ago I was skeptical about yet another Python package manager. It turns out I was wrong.

Quick history: Python packaging has been a running joke for twenty years. pip, virtualenv, pipenv, poetry, pyenv, conda, pdm, rye — every few years someone takes another swing. They all solved part of the problem and none of them solved all of it. The steady state was that every Python project started with ten minutes of figuring out which tool the previous maintainer chose.

uv is written in Rust by the same people who made ruff. What makes it different isn’t the language — it’s that it’s fast enough to replace the whole stack. Resolving dependencies that take poetry 45 seconds takes uv under one. Creating a virtualenv is instant. Installing is 10x faster than pip because it parallelizes downloads and uses a global cache with hardlinks. It manages Python versions itself, so pyenv is gone too.

The commands I actually use now:

uv init                    # start a new project (replaces poetry new)
uv add requests             # add a dep (replaces poetry add / pip install)
uv run python script.py     # run with the project env (replaces activate)
uv sync                     # install from lockfile (replaces pip install -r)
uv python install 3.12      # install a python version (replaces pyenv)
uv tool install ruff        # install a CLI tool in isolation (replaces pipx)

Six commands. One binary. No shell activation nonsense. It speaks standard pyproject.toml so projects work with other tools if a collaborator isn’t on uv yet.

The reason I’m writing this down is that I don’t expect to switch Python tooling again. For the first time in fifteen years, the Python packaging story is just… good. Boring, even. That’s a huge change, and it happened in the span of about twelve months.

#Python #uv