Luke Oliff.

uv Is the Python Packaging Tool That Finally Clicked

·Engineering·3 min read·Luke Oliff
TL;DR

uv 0.12.9 landed on 1 September 2026 with CPython 3.15.0rc2 in its managed builds, so uv python install 3.15 gets you the release candidate without touching system Python. uv sync and uv run replace the whole venv-and-pip dance.

uv is Astral’s Rust-written package and project manager for Python, and after a year of ignoring it I moved everything over in an afternoon. The uv python packaging workflow collapses pip, pip-tools, virtualenv, pyenv and pipx into two commands you run all day: uv sync and uv run. Version 0.12.9, out on 1 September 2026, even ships CPython 3.15.0rc2 in its managed builds, so you can test the release candidate without installing it yourself.

I was sceptical. Another packaging tool, in a space that already has ten. But this one earns its place.

What uv changes about Python packaging

The old flow had ceremony. Create a venv, activate it, remember to activate it, pip install, freeze a requirements file that drifts from what you actually installed, install a second tool to pin transitive dependencies properly.

uv keeps a pyproject.toml and a uv.lock, and reconciles the two on every command. You do not activate anything. You do not think about the environment at all, which is the point. Add a dependency with uv add httpx and the lockfile updates. Clone the repo fresh and uv sync rebuilds the exact environment from the lock in seconds, because the resolver is written in Rust and caches aggressively.

uv sync and uv run, the two commands

uv sync reads pyproject.toml, resolves, and materialises a .venv that matches the lockfile exactly. Nothing more to remember.

uv sync
uv run pytest
uv run python -m myapp

uv run executes a command inside that environment without an activate step. If the environment is stale, uv syncs it first. So uv run pytest on a fresh checkout just works, no “did I activate the venv” moment. This is the uv python packaging habit that stuck fastest for me: I stopped typing source .venv/bin/activate and never missed it.

To pin the interpreter a project uses, uv python pin 3.13 writes a .python-version file, and every uv run after that honours it. The Python versions docs cover the resolution order if you need the detail.

The bundled-CPython trick for testing prereleases

This is the part that sold me. uv manages its own CPython builds from Astral’s python-build-standalone distributions, and each uv release refreshes the catalogue of versions it can fetch. uv 0.12.9 added CPython 3.15.0rc2, so testing the release candidate is one line:

uv python install 3.15
uv run --python 3.15 python -V
# Python 3.15.0rc2

No system install, no compiling, no risk to whatever Python your OS depends on. uv python list shows what is available and what you already have. When 3.15.0 final lands on 1 October, a later uv release picks it up and uv python install 3.15 grabs the stable build instead.

uv prefers stable versions by default and only reaches for a prerelease when no stable version satisfies the constraint, so this does not surprise you in normal use. You opt into the RC by asking for it. I run my test suite against the next Python this way now, weeks before it ships, and it costs me nothing to keep around.

The thing I keep coming back to: uv made the boring parts invisible. The environment, the lockfile, the interpreter download, all of it happens under sync and run, and I get to think about the code instead.