Skip to content
On this page

    UV: The Python Tooling That Makes pip Feel Ancient

    After years of juggling pip, venv, pyenv, and pipx, UV consolidates everything into one tool that's 10-100x faster. Here's the complete cheat sheet.

    3 min read

    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.