Articles

uv: The Fast Python Package Manager Worth Switching To

uv replaces pip, virtualenv, pip-tools, and more in one Rust-powered tool. Learn how to install, manage dependencies, and run Python projects with uv.

Chisato Chisato · · 5 min read
Source-code brackets

Managing Python environments used to mean juggling four or five separate tools — pip to install packages, venv or virtualenv to isolate them, pip-tools to compile lockfiles, pyenv to manage Python versions, and pipx to install command-line tools globally. uv, built by Astral (the team behind the Ruff linter), collapses all of that into a single binary, and it does so at a speed that makes the old way feel sluggish by comparison.

What uv replaces

uv is not a wrapper around existing tools. It is a ground-up reimplementation in Rust that targets the same use cases across the Python workflow:

Old toolWhat uv replaces it with
pipuv pip install, uv pip compile
pip-toolsuv pip compile / uv lock
virtualenv / venvuv venv
pyenvuv python install / uv python pin
pipxuvx / uv tool run
Poetry / PDM (project workflow)uv init, uv add, uv sync

Not every use case is identical — uv is not trying to replicate Poetry’s publishing workflow, for instance — but for dependency management, lockfiles, environments, and tool running, the coverage is thorough.

Why it’s fast

Two architectural choices drive the speed:

Written in Rust. Dependency resolution and package I/O in Python tooling have historically been done in Python itself. A Rust implementation removes a large constant-factor overhead, especially for resolution algorithms that need to walk large dependency graphs.

Global content-addressable cache. uv caches packages by content hash globally across your machine, not per-project. Once a version of a package has been downloaded, it is never downloaded again — even in a fresh virtual environment. Parallel network requests and concurrent extraction compound this further. The net result is that cold installs often land an order of magnitude or more faster than pip for typical project sizes, and warm installs approach near-instant.

Step 1: Install uv

The quickest route on macOS and Linux:

curl -LsSf https://astral.sh/uv/install.sh | sh

On Windows, use the PowerShell installer from the uv docs. Once installed, verify with:

uv --version

No separate Python installation is required to run uv itself. However, if you do not have Python on your system, uv can fetch it for you (covered below).

Step 2: Start a new project

uv init scaffolds a project with a pyproject.toml and a hello.py placeholder:

uv init my-project
cd my-project

The generated pyproject.toml follows the modern PEP 517/518 format. uv does not invent a proprietary format — it writes and reads standard Python project metadata.

Step 3: Add dependencies

uv add requests httpx

This does three things at once: it resolves the dependency graph, installs into the project’s virtual environment (creating one if it does not exist), and writes a uv.lock file. The lockfile captures the exact resolved versions of every package in the full dependency tree — direct dependencies and transitive alike — so that uv sync on another machine produces an identical environment.

To add a development-only dependency:

uv add --dev pytest ruff

To remove a package:

uv remove requests

Step 4: Sync the environment

On a fresh clone, or after pulling changes that updated uv.lock:

uv sync

This brings the virtual environment exactly into alignment with the lockfile. No more “works on my machine” environment drift. The pattern should feel familiar if you have used Docker for environment reproducibility — the lockfile plays the role the Dockerfile plays in Docker for beginners.

Step 5: Run code without activating the environment

uv run python my_script.py

uv run automatically uses the project’s virtual environment. You can also run a module:

uv run pytest
uv run python -m http.server

This is useful in scripts and CI pipelines where you want to avoid sourcing activation scripts explicitly.

Step 6: Manage virtual environments directly

If you need to manage environments outside of a project context — for example, in a monorepo or a one-off script:

uv venv .venv
source .venv/bin/activate
uv pip install numpy pandas

The uv pip sub-commands are intentionally pip-compatible. You can swap uv pip install for pip install in most contexts with no changes to your mental model. This is the lowest-friction migration path for teams that want to speed up installs without rethinking their entire workflow.

Step 7: Install and manage Python versions

uv python install 3.12
uv python install 3.11 3.10

uv downloads and manages Python interpreters independently of your system Python. To tell a project which version to use:

uv python pin 3.12

This writes a .python-version file that uv respects when creating environments. No more maintaining a separate pyenv or dealing with system Python collisions.

Step 8: Run CLI tools without installing them

uvx (alias for uv tool run) lets you run a Python CLI tool ephemerally — it is downloaded, run, and discarded, never polluting your global or project environment:

uvx ruff check .
uvx black --check my_script.py
uvx cowsay hello

For tools you use regularly and want to keep available globally:

uv tool install ruff
uv tool install black

Installed tools are accessible on your PATH and managed by uv, cleanly separate from any project environments — the role pipx has traditionally filled.

The pyproject.toml + uv.lock workflow

The recommended workflow for any collaborative project is:

  1. Commit both pyproject.toml and uv.lock to version control.
  2. Run uv sync to reproduce the environment on any machine.
  3. Run uv add / uv remove to change dependencies — never edit the lockfile by hand.

The lockfile is designed to be human-readable and to produce minimal diffs when dependencies change. This is the same philosophy that Bun’s lockfile and npm’s package-lock.json follow — the Bun vs Node comparison is worth reading if you want a parallel picture of how JavaScript tooling has consolidated in the same direction.

Adopting uv incrementally

You do not need to migrate your whole workflow at once. The uv pip interface means you can start with:

uv pip install -r requirements.txt

…and see the speed difference immediately. From there, you can adopt uv sync and uv.lock when you are ready. uv is designed so that each piece can be adopted independently.

The takeaway

uv’s pitch is simple: one tool, one cache, dramatically faster installs, and a first-class lockfile workflow that makes reproducible Python environments easy to maintain. The Rust foundation is an implementation detail that produces a genuinely better developer experience. Whether you are starting a new project or looking to speed up your CI pipeline, uv is the Python package manager worth adopting now.

Takina Takina · · 7 min read

Rust Adopts LLM Policy: What's Allowed for AI Code

Five rust-lang/rust teams ratified an LLM policy: models can analyze and review, but not author contributions. Here's what's permitted, banned, and why.

#Rust #AI #Developer Tools
Chisato Chisato · · 5 min read

Build Your Own Redis in 200 Lines of Python

Build a Redis clone in Python that the real redis-cli can talk to — a TCP server, the RESP protocol, key expiry, and an in-memory store in under 200 lines.

#Databases #Developer Tools #Python