Python's UV Package Manager: Why I Switched From pip


Python’s packaging story has been a pain point for decades. pip, virtualenv, pipenv, poetry, conda — the ecosystem has produced more dependency management tools than most languages have web frameworks. When I first heard about UV, my reaction was “not another one.” After six months of daily use, my reaction is “finally.”

What Is UV?

UV is a Python package installer and resolver written in Rust by the team at Astral (the same people behind the Ruff linter). It’s designed as a drop-in replacement for pip and pip-tools, with dramatic speed improvements and better dependency resolution.

The speed difference is not subtle. Installing Django with pip takes about 8 seconds on my machine. UV does it in under 1 second. For projects with hundreds of dependencies, UV completes in seconds where pip takes minutes.

Installation and Basic Usage

Install UV with a single command:

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

Or via pip (ironic, I know):

pip install uv

The basic commands mirror pip’s interface:

uv pip install flask
uv pip install -r requirements.txt
uv pip freeze > requirements.txt

If you’ve used pip, you already know how to use UV for basic tasks. The commands are intentionally familiar.

Virtual Environment Management

UV includes built-in virtual environment management that replaces both venv and virtualenv:

uv venv                    # Create .venv in current directory
uv venv myenv              # Create named environment
uv pip install flask       # Install into active venv

This isn’t revolutionary on its own, but having the venv tool and package installer in a single fast binary reduces friction. No more remembering whether you need python -m venv or virtualenv or python3 -m venv.

The Project Management Mode

Where UV really shines is its project management capabilities. Running uv init creates a pyproject.toml and manages your entire project lifecycle:

uv init myproject
cd myproject
uv add flask sqlalchemy
uv add --dev pytest black
uv run python app.py
uv run pytest

The uv add command modifies pyproject.toml and updates the lockfile. The uv run command ensures the virtual environment is created and up-to-date before executing your command. If you’ve used npm or cargo, this workflow feels natural.

The lockfile (uv.lock) captures the exact resolved dependency tree, making builds reproducible across machines. This is what Poetry tried to do, but UV does it faster and with fewer edge cases in my experience.

Dependency Resolution

UV’s resolver is significantly better than pip’s. It handles version conflicts more intelligently and fails with clear error messages when resolution is impossible. Pip’s backtracking resolver can hang for minutes on complex dependency trees before giving up; UV resolves the same trees in seconds.

The resolver also supports override constraints, which let you force specific versions when you know better than the metadata:

uv pip install --override overrides.txt -r requirements.txt

This is useful when a package declares an overly restrictive upper bound on a dependency that you know works fine with a newer version.

Python Version Management

UV can also manage Python installations, replacing tools like pyenv:

uv python install 3.12
uv python install 3.11
uv python list

Having Python version management, virtual environment management, and package installation in a single tool eliminates an entire category of “which tool does what” confusion that plagues Python newcomers.

Compatibility and Limitations

UV is compatible with the standard Python packaging ecosystem. It reads requirements.txt, pyproject.toml, and setup.py files. It publishes to PyPI. It respects the same environment variables and configuration files that pip uses.

The main limitation I’ve encountered is with packages that have complex build requirements. Some scientific computing packages with C extensions or unusual build systems occasionally have issues. These are becoming rarer as UV matures, but if you’re working heavily with numpy, scipy, or similar packages, test your specific workflow before fully committing.

Migration Path

If you’re using pip with requirements.txt, the migration is simple: replace pip with uv pip in your commands and CI scripts. Everything else stays the same.

If you’re using Poetry, the migration requires converting your pyproject.toml from Poetry’s format to standard PEP 621 format. UV provides a migration guide, and the changes are mostly mechanical.

If you’re using conda for scientific computing, UV isn’t a direct replacement. Conda manages non-Python dependencies (C libraries, CUDA toolkits) that UV doesn’t handle. You can use UV within a conda environment, but conda remains necessary for the system-level dependencies.

My Recommendation

For new Python projects, use UV from the start. The uv init workflow is the most streamlined project setup Python has ever had.

For existing projects, try replacing pip with uv pip in your local development and CI. It’s a low-risk change that gives you immediate speed improvements. If that goes well, consider adopting uv add and uv.lock for full project management.

Python’s packaging ecosystem has been fragmented for too long. UV doesn’t solve every problem, but it consolidates the most common workflows into a single, fast, well-designed tool. That’s progress worth adopting.