UV: The Python Tooling That Makes pip Feel Ancient

· 3 min read ·
·
Python Developer Tools CLI Productivity

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 ApproachUV CommandWhat It Does
mkdir project && cd project && python -m venv .venvuv initInitialize project in current directory
Manual package setup with setup.pyuv init --lib --package mylibCreate packageable library
pyenv local 3.11uv init --python 3.11Specify Python version upfront
pip install requestsuv add requestsAdd production dependency
pip install -r requirements-dev.txtuv add --dev pytest ruffAdd development dependencies
pip uninstall requestsuv remove requestsRemove dependency
pip freeze > requirements.txtuv lockGenerate lockfile (uv.lock)
pip install -U requestsuv lock --upgradeUpgrade all dependencies
pip show requestsuv treeShow 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

Operationpip/pip-toolsPoetryUVSpeedup
Install simple package5-10s3-5s<1s10-100x
Create virtualenv3-5sBuilt-in<0.1s80x
Resolve dependencies10-30s5-15s1-3s5-10x
Lockfile generation10-20s5-10s2-5s3-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:

  1. Use uv run everywhere. Stop activating virtual environments.
  2. Commit uv.lock to git. Reproducible builds across machines and CI.
  3. Use uv tool install for global CLI tools. Isolated environments, no conflicts.
  4. Use inline script dependencies. Single-file scripts become portable.
  5. Use --frozen in 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.