UV: The Python Tooling That Makes pip Feel Ancient
Python’s packaging story has always been a mess. You need pip for packages, venv for environments, pyenv for Python versions, pipx for CLI tools, and pip-tools for lockfiles. Five tools to do what Node does with one.
UV changes that. Built by Astral (the creators of Ruff), it’s a single Rust-based tool that replaces the entire Python packaging stack. The speed difference isn’t incremental. Installing packages that took 5-10 seconds with pip now completes in under a second.
Here’s the complete cheat sheet for developers ready to make the switch.
Installation and Setup
# macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows PowerShell
irm https://astral.sh/uv/install.ps1 | iex
# Or via pip/pipx (if you must)
pip install uv
pipx install uv
Verify and update:
uv --version
uv self update # Update uv to latest
Project Management: The Core Workflow
This is where UV shines. What used to require multiple tools and manual coordination now happens with single commands.
| Traditional Approach | UV Command | What It Does |
|---|---|---|
mkdir project && cd project && python -m venv .venv | uv init | Initialize project in current directory |
| Manual package setup with setup.py | uv init --lib --package mylib | Create packageable library |
pyenv local 3.11 | uv init --python 3.11 | Specify Python version upfront |
pip install requests | uv add requests | Add production dependency |
pip install -r requirements-dev.txt | uv add --dev pytest ruff | Add development dependencies |
pip uninstall requests | uv remove requests | Remove dependency |
pip freeze > requirements.txt | uv lock | Generate lockfile (uv.lock) |
pip install -U requests | uv lock --upgrade | Upgrade all dependencies |
pip show requests | uv tree | Show dependency tree |
The key insight: uv add doesn’t just install packages. It updates your pyproject.toml, resolves dependencies, and regenerates the lockfile in one atomic operation.
Running Code Without Activation
Forget source .venv/bin/activate. UV handles environment activation implicitly.
# Traditional approach
source .venv/bin/activate
python script.py
deactivate
# UV approach
uv run python script.py
uv run pytest
uv run ruff check .
The uv run command automatically uses the correct virtual environment for your project. No activation. No deactivation. No wondering which environment you’re in.
Python Version Management
UV replaces pyenv with built-in Python management:
# List available Python versions
uv python list
# Install specific versions (downloads if needed)
uv python install 3.10 3.11 3.12
# Pin project to specific version (creates .python-version)
uv python pin 3.11
# Run with specific Python version
uv run --python 3.10 python --version
# Create venv with specific Python
uv venv --python 3.12
Why this matters: Missing Python versions are installed automatically on demand. No more “pyenv install 3.11.4 && pyenv local 3.11.4” dance.
Virtual Environment Operations
Creating virtual environments is 80x faster than python -m venv:
# Create virtual environment
uv venv # Creates .venv in current directory
uv venv path/to/.venv # Create at specific path
# Sync environment with lockfile
uv sync # Install dependencies from lockfile
uv sync --extra dev # Include extra dependency groups
uv sync --frozen # Fail if lockfile is outdated (CI use)
# Traditional activation still works if needed
source .venv/bin/activate # Unix/macOS
.venv\Scripts\activate # Windows
Single-File Scripts with Inline Dependencies
This feature alone is worth the switch. UV can manage dependencies for standalone scripts:
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "pandas>=2.0",
# "requests>=2.32",
# ]
# ///
import pandas as pd
import requests
# Your code here
Run it directly:
uv run analysis.py
UV reads the inline metadata, creates an isolated environment, installs dependencies, and runs the script. No requirements.txt. No virtual environment setup. Just run it.
Managing script dependencies:
uv init --script analysis.py # Add metadata block
uv add --script analysis.py pandas # Add dependency to script
uv remove --script analysis.py pandas # Remove dependency
Tool Management (Replaces pipx)
For CLI tools you want available globally:
# Run tools without installing (like npx)
uv tool run black file.py
uvx black file.py # Shorthand alias
# Install tools globally
uv tool install ruff
uv tool install --with plugins black
# List and manage installed tools
uv tool list
uv tool upgrade ruff
uv tool upgrade --all
uv tool uninstall ruff
# Run tool from specific package
uv tool run --from textual textual-demo
The difference from pip install --user: Each tool gets its own isolated environment. No dependency conflicts between tools.
Building and Publishing
# Build distributions
uv build
# Publish to PyPI
uv publish
# Version management
uv version # Show current version
uv version --bump minor # Bump minor version
uv version --bump patch # Bump patch version
uv version --bump beta # Create beta release
uv version --bump stable # Promote to stable
Speed Comparison
| Operation | pip/pip-tools | Poetry | UV | Speedup |
|---|---|---|---|---|
| Install simple package | 5-10s | 3-5s | <1s | 10-100x |
| Create virtualenv | 3-5s | Built-in | <0.1s | 80x |
| Resolve dependencies | 10-30s | 5-15s | 1-3s | 5-10x |
| Lockfile generation | 10-20s | 5-10s | 2-5s | 3-5x |
The speed comes from Rust, parallel downloads, and an aggressive global cache that shares packages across projects.
Migration Paths
From pip + requirements.txt
# 1. Initialize uv project
uv init --python 3.11
# 2. Import existing requirements
uv add -r requirements.txt
# 3. Import dev requirements
uv add --dev -r requirements-dev.txt
# 4. Generate lockfile
uv lock
# 5. Commit both files
git add pyproject.toml uv.lock
From Poetry
UV reads pyproject.toml directly. Most Poetry projects work without changes:
# Install all dependencies including dev
uv sync --all-extras
# Convert poetry scripts
# poetry run test → uv run pytest
# poetry run lint → uv run ruff check
From pip-tools
# Compile requirements (replaces pip-compile)
uv pip compile requirements.in -o requirements.txt
# Sync compiled requirements (replaces pip-sync)
uv pip sync requirements.txt
CI/CD Integration
GitHub Actions
- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Setup Python and install deps
run: |
uv python pin 3.11
uv sync --frozen # Use lockfile, fail if outdated
- name: Run tests
run: uv run pytest
Docker
FROM ghcr.io/astral-sh/uv:python3.11-bookworm-slim
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev
COPY . .
CMD ["uv", "run", "python", "-m", "myapp"]
Troubleshooting
# Verbose output for debugging
uv --verbose run python script.py
# Nuclear option: clear everything
uv cache clean
rm -rf .venv uv.lock
# Verify Python version
uv python list
uv run python --version
# Check dependency conflicts
uv tree
uv lock --locked # Fail if lockfile needs update
Quick Reference
# New project workflow
uv init myproject --python 3.11 --app
uv add requests pandas
uv add --dev pytest ruff
# Development workflow
uv run python -m pytest
uv run ruff check .
uv run python script.py
# Production deployment
git add pyproject.toml uv.lock
uv sync --frozen --no-dev
# Tools and scripts
uvx black file.py
uv tool install mycli
uv run --script analysis.py
The Bottom Line
UV isn’t just faster pip. It’s a complete rethinking of Python tooling that eliminates the cognitive overhead of managing five different tools for what should be simple operations.
The key changes to internalize:
- Use
uv runeverywhere. Stop activating virtual environments. - Commit
uv.lockto git. Reproducible builds across machines and CI. - Use
uv tool installfor global CLI tools. Isolated environments, no conflicts. - Use inline script dependencies. Single-file scripts become portable.
- Use
--frozenin CI. Fail fast if lockfile is outdated.
The migration path is straightforward. UV reads existing pyproject.toml files and can import from requirements.txt. Start with one project, experience the speed difference, and you won’t go back.
Switching your Python workflow to UV? I’d love to hear how it goes. Reach out on LinkedIn.