Migration Guide

From pip to uv

A practical guide for data scientists and ML engineers — concepts first, commands second.

Contents
  1. 01 Why uv exists
  2. 02 Mental model shift
  3. 03 Installation
  4. 04 Project structure
  5. 05 Core commands
  6. 06 Daily workflow
  7. 07 Migrating from pip
  8. 08 Python version management
  9. 09 Jupyter & DS tools
  10. 10 Gotchas & tips

The problem uv solves

⚡ Speed

pip resolves dependencies one-by-one in Python. uv is written in Rust and resolves everything in parallel — typically 10–100× faster on fresh installs.

🔒 Reproducibility

pip + requirements.txt can drift across machines. uv uses a lockfile (uv.lock) that pins every transitive dependency to an exact version and hash.

🧰 One Tool

pip, virtualenv, pyenv, pip-tools — uv replaces all of them. One binary, one mental model.

📦 Modern packaging

uv understands pyproject.toml natively — the new standard for Python projects, replacing setup.py and requirements.txt.

How to think about uv

💡

Key insight: In the pip world, the environment IS the project. In the uv world, the project defines what the environment should be. uv creates and manages the environment for you from a declarative spec.

The three files you need to know

my-project/
  pyproject.toml ← What your project needs (you edit this)
  uv.lock ← Exact pinned versions of everything (auto-generated, commit this)
  .venv/ ← The actual environment (auto-managed, don't commit)
  notebooks/
  src/

Concept mapping: pip → uv

The dependency declaration split

Getting uv installed

bash — macOS / Linux
# One-liner installer (recommended)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Or via Homebrew (macOS)
brew install uv

# Verify
uv --version
PowerShell — Windows
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Starting or adopting a project

Start a brand new project

bash
uv init my-project
cd my-project

This creates a pyproject.toml with sensible defaults. Look inside it — it's readable and editable by hand.

What pyproject.toml looks like

pyproject.toml
[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
    "numpy>=1.24",
    "pandas>=2.0",
    "scikit-learn",
    "jupyter",
]

# Optional: dev/test tools kept separate
[dependency-groups]
dev = [
    "pytest",
    "ruff",
]

Commands and what they actually do

Command What it does pip equivalent
uv init Scaffold a new project with pyproject.toml manual setup
uv add numpy Add a dependency → updates pyproject.toml + uv.lock + installs it pip install numpy + manual edit
uv add --dev pytest Add to dev group only pip install pytest + manual edit
uv remove numpy Remove a dependency and clean up lockfile pip uninstall + manual edit
uv sync Make the .venv exactly match uv.lock (install missing, remove extra) pip install -r requirements.txt
uv sync --group dev Sync + include the dev dependency group pip install -r dev-requirements.txt
uv lock Regenerate uv.lock from pyproject.toml without installing pip-compile
uv run python script.py Run a command inside the project's environment (auto-syncs first) source .venv/bin/activate && python script.py
uv run jupyter lab Launch Jupyter inside the environment source .venv/bin/activate && jupyter lab
uv python install 3.12 Download and manage Python version pyenv install 3.12
uv python pin 3.12 Set default Python version for this project pyenv local 3.12
uv pip install numpy Drop-in pip replacement (no lockfile, for scripts/quick use) pip install numpy

uv run is your friend. Instead of activating the virtualenv manually, just prefix your commands with uv run. It activates, syncs if needed, runs, and exits cleanly. No more "why is the wrong python in my PATH" confusion.

How a typical day looks

1
Clone or open project
Pull the repo. It has pyproject.toml and uv.lock checked in.
git clone https://github.com/org/project && cd project
2
Sync the environment
uv reads the lockfile and builds an identical .venv. Takes seconds.
uv sync                    # production deps only
uv sync --group dev        # include dev tools too
3
Add a new package
You decide you need lightgbm. Add it — pyproject.toml and uv.lock are updated automatically.
uv add lightgbm
4
Work — run scripts, notebooks, tests
Use uv run to run things inside the environment without activating it.
uv run jupyter lab
uv run python train.py
uv run pytest
5
Commit the lockfile
Always commit uv.lock. This is what makes the environment reproducible for teammates and CI.
git add pyproject.toml uv.lock
git commit -m "add lightgbm"

Moving an existing pip project to uv

bash — step by step
# Step 1 — in your existing project dir, initialize uv
uv init --no-package     # --no-package = don't treat this as a library

# Step 2 — import your existing requirements.txt
uv add -r requirements.txt

# Step 3 — (optional) import dev requirements
uv add --group dev -r requirements-dev.txt

# Step 4 — verify everything works
uv sync
uv run python -c "import pandas; print(pandas.__version__)"
!

Don't delete requirements.txt yet. If other tools (CI, Dockerfiles, teammates) depend on it, keep it around. You can also export: uv export --format requirements-txt > requirements.txt

Updating packages

TaskCommand
Update a single packageuv add numpy --upgrade
Update all packagesuv lock --upgrade then uv sync
See what's outdateduv tree --outdated
Show dependency treeuv tree

Managing Python versions (replacing pyenv)

bash
# See what Python versions are available
uv python list

# Install a specific version
uv python install 3.12

# Pin this project to Python 3.12
uv python pin 3.12      # creates .python-version file

# Create environment with a specific Python
uv sync --python 3.12

Tip: If you set requires-python = ">=3.11" in pyproject.toml, uv will automatically find or download a compatible Python when syncing. You rarely need to think about this manually.

Using Jupyter and the data science stack

Setup for a typical DS project

bash
# Start a new DS project
uv init my-analysis && cd my-analysis

# Add the core stack
uv add numpy pandas scikit-learn matplotlib seaborn

# Add Jupyter as a dev tool (you don't ship notebooks to prod)
uv add --group dev jupyterlab ipykernel

# Register the kernel so Jupyter sees your environment
uv run python -m ipykernel install --user --name=my-analysis

# Launch
uv run jupyter lab

GPU / CUDA packages (PyTorch, JAX)

bash
# PyTorch with CUDA 12.1
uv add torch torchvision --index https://download.pytorch.org/whl/cu121

# Or pin it in pyproject.toml under [[tool.uv.index]]
!

conda vs uv: If your workflow relies on non-Python system libraries installed via conda (CUDA toolkit, MKL, etc.), uv doesn't replace conda for that layer. A common pattern is: conda for system libs → uv inside the conda env for Python packages.

Things that trip people up

Common gotchas

Handy shortcuts

SituationCommand
Quick one-off script, no projectuv run --with pandas python script.py
See what's installeduv pip list
Export to requirements.txt formatuv export --format requirements-txt
Run a tool without installing ituv tool run ruff check . (or just uvx ruff check .)
Install a global CLI tooluv tool install ruff
Check environment is healthyuv sync --check

What to commit vs ignore

.gitignore
.venv/
__pycache__/
*.pyc
.ipynb_checkpoints/