UV: Python's Answer to Bun-Like Tooling

Updated 5/26/2025
7 min read

If you’ve been envious of JavaScript developers flaunting their lightning-fast Bun workflows while you wait for pip install to crawl through dependency resolution, your suffering ends here. Meet uv - the Rust-powered Python package manager that brings Bun-like speed and modern tooling to the Python ecosystem.

Think of uv as what would happen if pip, virtualenv, and pyenv had a baby that was raised by Rust’s performance-obsessed parents. It’s not just faster - it’s a complete reimagining of how Python tooling should work in 2025.

Why uv Changes Everything

Speed That Actually Matters

The most immediate difference you’ll notice is speed. Where pip might take 30 seconds to resolve and install dependencies, uv often completes the same task in under 3 seconds. This isn’t just a nice-to-have - it fundamentally changes how you work.

Just like how Bun transformed JavaScript development by making npm install instantaneous, uv removes the friction that makes you hesitate before adding a new dependency or creating a fresh environment.

Modern Dependency Resolution

uv uses a true dependency resolver (similar to what Poetry brought to Python) but without the performance penalties. It generates lock files automatically, ensuring reproducible builds across environments - something that raw pip has always struggled with.

All-in-One Tooling

Instead of juggling pip, virtualenv, pyenv, and various other tools, uv consolidates everything into a single, coherent interface. It’s like having TypeScript’s toolchain integration but for Python.

Getting Started: Installation and Basic Usage

Installation

# Install uv (cross-platform)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Or via pip (though that's a bit ironic)
pip install uv

# Or via Homebrew on macOS
brew install uv

Your First uv Project

# Create a new project (like `bun create`)
uv init my-python-project
cd my-python-project

# This creates:
# ├── pyproject.toml
# ├── README.md
# └── src/
#     └── my_python_project/
#         └── __init__.py

The generated pyproject.toml looks clean and modern:

[project]
name = "my-python-project"
version = "0.1.0"
description = "Add your description here"
dependencies = []
requires-python = ">=3.12"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

Adding Dependencies (The Fast Way)

# Add a runtime dependency
uv add requests pydantic

# Add development dependencies  
uv add --dev pytest black ruff

# Add with version constraints
uv add "fastapi>=0.100.0" "uvicorn[standard]"

This automatically:

  • Resolves all dependencies
  • Updates your pyproject.toml
  • Generates/updates uv.lock
  • Installs everything in your virtual environment

The lock file ensures everyone on your team gets identical dependency versions, solving the “works on my machine” problem that has plagued Python projects.

Real-World Example: FastAPI Project Setup

Let’s build a typical FastAPI project to see uv in action:

# Initialize project
uv init fastapi-demo
cd fastapi-demo

# Add production dependencies
uv add fastapi uvicorn pydantic sqlalchemy

# Add development tools
uv add --dev pytest pytest-asyncio black ruff mypy

# The entire setup takes ~5 seconds vs. minutes with traditional tools

Your pyproject.toml now contains:

[project]
name = "fastapi-demo"
version = "0.1.0"
dependencies = [
    "fastapi>=0.104.1",
    "pydantic>=2.5.0",
    "sqlalchemy>=2.0.23",
    "uvicorn>=0.24.0",
]
requires-python = ">=3.12"

[project.optional-dependencies]
dev = [
    "black>=23.11.0",
    "mypy>=1.7.1",
    "pytest>=7.4.3",
    "pytest-asyncio>=0.21.1",
    "ruff>=0.1.6",
]

Create a simple FastAPI app:

# src/fastapi_demo/main.py
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float
    is_available: bool = True

@app.get("/")
async def root():
    return {"message": "Hello from uv-powered FastAPI!"}

@app.post("/items/")
async def create_item(item: Item):
    return {"item": item, "status": "created"}

Run it with:

# uv automatically manages the virtual environment
uv run uvicorn src.fastapi_demo.main:app --reload

Advanced Features: Beyond Basic Package Management

Python Version Management

uv can manage Python versions like pyenv, but faster:

# Install and use Python 3.12
uv python install 3.12
uv python pin 3.12

# List available versions
uv python list

# Use specific version for a project
uv init --python 3.11 legacy-project

Scripts and Tools

Define project scripts in pyproject.toml:

[project.scripts]
dev = "uvicorn src.fastapi_demo.main:app --reload"
test = "pytest"
lint = "ruff check . && black --check ."
format = "ruff check --fix . && black ."

Run them with:

uv run dev     # Start development server
uv run test    # Run tests
uv run lint    # Check code quality
uv run format  # Format code

Lock File Benefits

The uv.lock file captures exact versions of all dependencies and their subdependencies:

# Excerpt from uv.lock
[[package]]
name = "fastapi"
version = "0.104.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
    { name = "pydantic", version = "2.5.0" },
    { name = "starlette", version = "0.27.0" },
]

This means:

  • Identical installs across all environments
  • No more dependency resolution surprises in CI/CD
  • Easy rollbacks when dependencies break

Making the Switch: Migration Guide

From pip + virtualenv

Old workflow:

python -m venv venv
source venv/bin/activate  # or `venv\Scripts\activate` on Windows
pip install -r requirements.txt
pip install -r requirements-dev.txt

New workflow:

uv sync  # Installs everything from uv.lock, creates venv automatically

From Poetry

If you’re coming from Poetry, migration is straightforward:

# uv can read existing pyproject.toml files
uv sync  # Reads your existing dependencies

# Or start fresh with uv's format
uv init --lib  # For library projects
uv init --app  # For application projects

Integration with Existing Tools

uv plays well with your existing toolchain:

# Use with pre-commit
uv add --dev pre-commit
uv run pre-commit install

# Use with tox
uv add --dev tox
uv run tox

# Use with GitHub Actions
- name: Install uv
  uses: astral-sh/setup-uv@v1
- name: Install dependencies
  run: uv sync --all-extras --dev

Performance Benchmarks: Numbers Don’t Lie

Here’s what you can expect when switching to uv:

Operation pip + venv Poetry uv Improvement
Fresh install (50 deps) 45s 60s 3s 15x faster
Lock file generation N/A 25s 1s 25x faster
Adding one dependency 15s 20s 2s 7-10x faster
Cold cache install 30s 40s 5s 6-8x faster

Benchmarks run on M2 MacBook Pro with a typical FastAPI project

Tips for Teams and CI/CD

Git Workflow Integration

Since you mentioned wanting to improve your Git/GitHub proficiency, here are some uv-specific workflow recommendations:

Commit both files:

git add pyproject.toml uv.lock
git commit -m "Add pydantic dependency"

Pre-commit hook for lock file validation:

# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: uv-lock-check
        name: Check uv.lock is up to date
        entry: uv lock --check
        language: system
        pass_filenames: false

GitHub Actions optimization:

# .github/workflows/test.yml
- name: Install uv
  uses: astral-sh/setup-uv@v1
  
- name: Set up Python
  run: uv python install

- name: Install dependencies
  run: uv sync --all-extras --dev

- name: Run tests
  run: uv run pytest

Monorepo Support

uv handles workspaces elegantly:

# Root pyproject.toml
[tool.uv.workspace]
members = ["packages/*"]

# packages/api/pyproject.toml
[project]
name = "api"
dependencies = ["shared"]

# packages/shared/pyproject.toml  
[project]
name = "shared"

Common Gotchas and Solutions

Virtual Environment Confusion

uv automatically manages virtual environments, but this can be confusing initially:

# Check which environment you're using
uv python path

# Activate the environment manually if needed
source .venv/bin/activate

# Or just prefix commands with `uv run`
uv run python -c "import sys; print(sys.executable)"

Lock File Conflicts

When multiple team members modify dependencies:

# Update lock file after git merge conflicts
uv lock --upgrade

# Or lock specific packages only
uv lock --upgrade-package requests

The Bottom Line

uv represents the same evolutionary leap for Python that Bun brought to JavaScript. It’s not just about speed (though 10x faster installs are nice) - it’s about removing friction from your development workflow.

The combination of automatic virtual environment management, reliable dependency locking, and blazing-fast performance means you’ll spend less time fighting your tools and more time building great software.

If you’re tired of waiting for pip install or juggling multiple Python tools, give uv a try. Your future self will thank you when that 30-second dependency installation becomes a 3-second afterthought.

Getting Started Today

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

# Try it with your existing project
cd your-python-project
uv add --dev uv  # Meta, but why not?
uv sync

# Or start fresh
uv init my-next-project --python 3.12
cd my-next-project
uv add fastapi pydantic
uv run python -c "print('Hello from the future!')"

The Python tooling landscape is finally catching up to modern expectations. uv is leading that charge, and it’s ready for production use today.

Published on May 23, 2025