Migrating from pip to UV

A guide for developers transitioning from pip/venv to UV.

Quick Command Translation

What you wantpip/venvuv
Create environmentpython -m venv .venvuv sync
Activate environmentsource .venv/bin/activateNot needed with uv run
Install from requirementspip install -r requirements.txtuv sync
Install packagepip install packageuv add package
Install specific versionpip install package==1.0.0uv add "package==1.0.0"
Install from gitpip install git+https://...uv add git+https://...
Uninstall packagepip uninstall packageuv remove package
List installedpip listuv pip list
Show package infopip show packageuv pip show package
Freeze dependenciespip freeze > requirements.txtuv lock (automatic)
Run scriptpython script.pyuv run python script.py
Run with modulepython -m pytestuv run pytest
Upgrade packagepip install --upgrade packageuv add package@latest
Upgrade allpip install --upgrade -r requirements.txtuv sync --upgrade
Install editablepip install -e .uv pip install -e .

Key Conceptual Differences

1. No Manual Activation Needed

pip/venv:

source .venv/bin/activate  # Must activate
python script.py           # Then run
deactivate                 # Then deactivate

uv:

uv run python script.py    # Just run, no activation!

You can still activate if you prefer, but uv run is faster and more convenient.

2. Lock File Instead of requirements.txt

pip/venv:

# Manual process
pip freeze > requirements.txt
git add requirements.txt

uv:

# Automatic lock file
uv add package  # Automatically updates uv.lock
git add uv.lock # Commit the lock file

The lock file is automatically maintained and includes transitive dependencies.

3. Workspace Instead of Multiple venvs

pip/venv (monorepo with multiple packages):

cd package1
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

cd ../package2
python -m venv .venv  # Another venv!
source .venv/bin/activate
pip install -r requirements.txt

uv (workspace):

# One environment for all packages
uv sync  # Installs everything once

4. Dependency Resolution

pip: Best-effort, can produce incompatible versions

uv: Always produces a valid dependency resolution or fails clearly

5. Speed

pip: ~30 seconds for typical install

uv: ~3 seconds (10-100x faster with cache)

Migration Steps

Step 1: Install UV

curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"

Step 2: Initial Sync

# Remove old venv
rm -rf .venv

# Let UV create new one
uv sync

This reads the root pyproject.toml and creates everything.

Step 3: Update Your Workflow

Old workflow:

source .venv/bin/activate
pip install package
python script.py
deactivate

New workflow:

uv add package
uv run python script.py

Step 4: Update Scripts

Old shell script:

#!/bin/bash
source .venv/bin/activate
python my_script.py

New shell script:

#!/bin/bash
uv run python my_script.py

Step 5: Update CI/CD

Old CI:

- name: Setup Python
  uses: actions/setup-python@v4
  with:
    python-version: '3.10'

- name: Install dependencies
  run: |
    python -m venv .venv
    source .venv/bin/activate
    pip install -r requirements.txt

- name: Run tests
  run: |
    source .venv/bin/activate
    pytest

New CI:

- name: Install UV
  run: curl -LsSf https://astral.sh/uv/install.sh | sh

- name: Install dependencies
  run: uv sync --frozen

- name: Run tests
  run: uv run pytest

Converting requirements.txt to pyproject.toml

If you have a requirements.txt, you can convert it:

Old requirements.txt:

numpy>=1.26.0
pandas>=2.1.0
scikit-learn>=1.4.0
matplotlib>=3.8.0

New pyproject.toml:

[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = [
    "numpy>=1.26.0",
    "pandas>=2.1.0",
    "scikit-learn>=1.4.0",
    "matplotlib>=3.8.0",
]

Then run:

uv sync

Converting setup.py to pyproject.toml

Old setup.py:

from setuptools import setup, find_packages

setup(
    name="my-package",
    version="0.1.0",
    packages=find_packages(),
    install_requires=[
        "numpy>=1.26.0",
        "pandas>=2.1.0",
    ],
    python_requires=">=3.10",
)

New pyproject.toml:

[project]
name = "my-package"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = [
    "numpy>=1.26.0",
    "pandas>=2.1.0",
]

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

Common Gotchas

1. Forgetting to Use uv run

Won’t work:

pytest  # Uses system Python, not .venv!

Will work:

uv run pytest  # Uses .venv Python
# or
source .venv/bin/activate
pytest

2. Editing pyproject.toml Without Syncing

Won’t work:

# Edit pyproject.toml manually
python script.py  # Old dependencies still in .venv

Will work:

# Edit pyproject.toml manually
uv sync  # Update .venv
uv run python script.py

3. Expecting requirements.txt to Work

UV doesn’t use requirements.txt by default. Use:

  • pyproject.toml for project dependencies
  • uv.lock for locked versions (committed to git)

4. Installing Globally

Don’t do this:

uv pip install package  # Don't install globally

Do this:

uv add package  # Add to project dependencies

FAQ

Can I still use pip?

Yes, but you’ll lose UV’s speed and workspace benefits. Within a UV environment:

uv run pip install package  # Works, but not recommended
uv add package              # Better, updates lock file

Do I need requirements.txt?

No. UV uses pyproject.toml and uv.lock. But you can still generate one:

uv pip freeze > requirements.txt

Can I use both pip and uv?

Not recommended. Choose one:

  • Use UV for project development
  • Use pip only for quick experiments or legacy projects

What about conda?

UV doesn’t replace conda environments. For conda:

  1. Create conda environment
  2. Install UV in it: conda install uv or pip install uv
  3. Use UV for Python packages: uv add package

How do I share my environment?

With pip:

pip freeze > requirements.txt
# Share requirements.txt

With uv:

# Commit uv.lock to git
git add uv.lock pyproject.toml
git commit -m "Update dependencies"
# Others just run: uv sync

What about virtual environment managers (pyenv, virtualenv)?

UV replaces most of these:

  • pyenvuv python install 3.13
  • virtualenv/venvuv sync creates .venv
  • pipenv → UV has lock files built-in
  • poetry → Similar, but UV is faster

Benefits You’ll Gain

  1. Speed: 10-100x faster installs
  2. Reliability: Always valid dependency resolution
  3. Reproducibility: Lock file ensures everyone has same versions
  4. Simplicity: Fewer commands, less configuration
  5. Modern: Uses latest Python packaging standards
  6. Workspace: Manage multiple packages easily

Common Patterns

Development Workflow

Old:

source .venv/bin/activate
pip install -e .
pip install pytest
pytest
deactivate

New:

uv sync  # Installs everything including dev deps
uv run pytest

Adding Dependencies

Old:

pip install new-package
pip freeze | grep new-package >> requirements.txt

New:

uv add new-package  # Automatically updates lock file

Updating Dependencies

Old:

pip install --upgrade package
pip freeze > requirements.txt

New:

uv add package@latest
# or
uv sync --upgrade  # Update all

Running Scripts

Old:

source .venv/bin/activate
python -m pytest
python -m black .
python script.py

New:

uv run pytest
uv run black .
uv run python script.py

Still Confused?

Check these resources:

  1. UV Guide - Complete UV documentation
  2. Quick Start - Get started quickly
  3. Troubleshooting - Common issues
  4. UV Documentation - Official docs

Summary

Stop doing:

  • python -m venv .venv
  • source .venv/bin/activate
  • pip install -r requirements.txt
  • pip freeze > requirements.txt

Start doing:

  • uv sync (once at start)
  • uv run python script.py (no activation!)
  • uv add package (to add dependencies)
  • Commit uv.lock (automatic versioning)

Result: Faster, simpler, more reliable development! 🚀