~/iprabhat.dev

Managing Python Environments With UV

7 min read

I’ve been using Python since my college days — and it’s been almost five years of using it professionally now. Most of my work revolves around building API servers (FastAPI), writing automation scripts, and deep learning projects.

If you’ve been around the Python ecosystem for a while, you know the chaos of package and environment managers — venv, pipenv, poetry, pyenv, conda, you name it. I’ve tried them all at some point, but none really clicked for me.

For machine learning experiments, I’ve always relied on miniconda. For regular development, virtualenvwrapper has been my go-to. It’s simple and works well — except that the Python version you want needs to already be installed on your system. I still have a few old projects running on it, so it hasn’t completely left my setup yet.

Discovering UV

Last year, I came across some posts on HackerNews and Lobsters about a new tool called UV, built by the folks at Astral (the same team behind Ruff). It was described as a superfast Python package manager that aims to fix the long-standing dependency hell in Python.

I ignored it at first. I’ve seen too many tools come and go, and every time I end up back with — virtualenv + miniconda.

Earlier this year, sometime around summer 2025, I finally decided to give UV a real try. I’d seen people talking about it for months, but I’d been ignoring the hype. But UV hit different. I started using it for a few personal projects just to test it out. And at work, we've started adopting it across our microservices. It's fast, reliable, and just works.

UV abstracts away so much of the old Python packaging pain — dependency resolution, environment isolation, Python version management.

What Makes UV Different?

  • It's Fast: About 100 times faster than pip, which means less time waiting for dependencies to resolve.
  • No Need for Admin Rights: UV installs and manages packages in your user space. That's helpful in places with Active Directory or other restrictions where you can't install things system-wide.
  • Handles Python Versions: You don't need Python installed beforehand. UV can download and manage versions for you.
  • Reproducible Builds: It creates uv.lock files automatically, so you get consistent results every time.

Getting Started with UV

Installation

no sudo required:

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

# Verify installation
uv --version

Starting a New Project

# Create a project with a specific Python version
uv init hello-stars --python 3.13

cd hello-stars

# uv automatically creates:
# - pyproject.toml (modern Python standard)
# - .python-version (pins Python version)
# - .gitignore
# - README.md
# - main.py

Managing Dependencies

# Add packages (auto-creates venv if needed)
uv add fastapi uvicorn

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

# Update all dependencies
uv sync --upgrade

# Remove unused package
uv remove some-package

About uv sync uv sync is one of the most useful commands — it reads your pyproject.toml and uv.lock to install exactly what’s pinned. If you change dependencies manually, or pull fresh code from GitHub, just run:

uv sync

and UV will make sure your local environment matches the lock file perfectly. It’s like npm ci for Python.

Running Your Code

No more manual virtualenv activation — UV does that automatically.

# Run scripts
uv run python main.py

# Run tests or formatters
uv run pytest
uv run black .

# Run without installing globally
uvx ruff check .   # like `npx` for Python

Common Gotchas (and Their Fixes)

Don’t activate virtual environments manually

# Old habit (Don't)
source .venv/bin/activate
python main.py

# UV way (Do)
uv run python main.py

Always run from the project root

Running from inside src can mess up imports.

# Can cause import issues (Don't)
cd src && python ../scripts/test.py

# Always run from root (Do)
uv run python scripts/test.py

Manage your cache

UV caches Python versions and dependencies to speed up installs.

# Check cache info
uv cache info

# Clean occasionally
uv cache clean

Best Practices

Project Structure

A clean and reproducible layout goes a long way:

my-project/
├── pyproject.toml      # Single source of truth
├── .python-version     # Python version for project
├── uv.lock             # Commit this for reproducibility
├── src/
│   └── my_project/
└── tests/

Python Version Strategies

# For libraries – support multiple versions
uv init my-lib --python ">=3.9"

# For apps – pin a specific version
uv init my-app --python 3.12
uv python pin 3.12

# Test across versions
uv run --python 3.10 pytest
uv run --python 3.11 pytest
uv run --python 3.12 pytest

Dependency Management Tips

# Use version ranges or pins when needed
uv add "fastapi>=0.115,<0.120"
uv add "pydantic==2.8.2"

# Dev dependencies
uv add --dev pytest mypy black ruff

# Optional groups for docs, linting, etc.
uv add --group docs sphinx mkdocs

Keeping Dependencies Fresh

# Upgrade everything based on constraints
uv sync --upgrade

# Upgrade a specific package
uv add fastapi@latest

# Show outdated packages
uv pip list --outdated

Exporting Requirements

If you need compatibility with old CI/CD setups that still use pip:

uv export --format requirements.txt > requirements.txt

You can then share that file with teams that haven’t moved to UV yet.

Using UV Tools

UV can also handle globally installed CLI tools — no need for pipx.

# Install tools globally (user space)
uv tool install black
uv tool install ruff

# Run them directly
uvx black .
uvx ruff check .

Using UV With Legacy Pip Workflows

Sometimes you’ll still have an older project with a requirements.txt. UV can handle that too:

uv pip install -r requirements.txt

This way you don’t have to change the project structure — UV just works with existing setups.

Quick Migration Guide (pip → uv)

Here’s a simple mapping of common commands:

TaskOld WayNew Way (UV)
Install Pythonpyenv install 3.12uv python install 3.12
Create venvpython -m venv .venvuv venv (auto-created)
Add dependencypip install fastapiuv add fastapi
Install from requirementspip install -r requirements.txtuv pip install -r requirements.txt
Update depspip install -U -r requirements.txtuv sync --upgrade
Run scriptsource .venv/bin/activate && python main.pyuv run python main.py
Install global toolpipx install blackuv tool install black

Things to Keep in Mind

A few small but important details I’ve learned while using UV:

  • uv sync respects your .python-version, but if you set the UV_PYTHON environment variable, that will override it.
  • UV uses python-build-standalone internally — which might be 1–3% slower than your system Python, but it trades that for consistency across platforms.
  • Cache size can get pretty large over time (that’s the price you pay for speed and reliability). Just clean it up once in a while with uv cache clean.
  • If you’re working on older legacy projects, some installs might fail because UV is stricter with dependency resolution compared to pip’s older, looser behavior.

Final Thoughts

After using UV for a few months, I can confidently say it’s one of the best tools to happen to Python in a while. It’s fast, reliable, and actually makes dependency management easier.