uv (by Astral) collapsed Python tooling into one fast tool. By 2026 it’s the default for most new projects and many migrating teams. This post is the working set.
What uv covers
- Python install (any version).
- Virtual environments.
- Dependency resolution (replaces pip-tools).
- Lockfile.
- Project workflow (replaces poetry).
- Tool install (replaces pipx).
- Script execution with inline deps.
One binary; one mental model.
Project workflow
uv init myproject
cd myproject
uv add fastapi uvicorn
uv add --dev pytest ruff
uv run pytest
uv run uvicorn main:app
Behind the scenes:
- Creates
.venv/automatically. - Updates
pyproject.toml. - Updates
uv.lock. - Resolves deps in milliseconds.
pyproject.toml
[project]
name = "myproject"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"fastapi>=0.110",
"uvicorn>=0.30",
]
[dependency-groups]
dev = ["pytest>=8", "ruff>=0.5"]
dependency-groups is the new PEP 735 standard; uv respects it. Replaces Poetry’s [tool.poetry.group.dev.dependencies].
Lockfile
uv.lock is the single source of truth for reproducible installs.
uv sync # install from lock
uv sync --frozen # fail if lock is stale
uv lock --upgrade-package fastapi
uv lock --upgrade
Cross-platform lock; resolves once for all platforms.
Commit uv.lock for applications. For libraries: don’t pin in pyproject.toml (let consumers solve).
Python versions
uv python install 3.13
uv python list
uv python pin 3.13
Manages Python versions like nvm for Node. Pin a project to a specific Python.
Tool install
uv tool install ruff
uv tool install pre-commit
uv tool list
Replaces pipx. Each tool in its own isolated venv. Available globally.
uvx (run tool ad-hoc)
uvx ruff check .
uvx --from "ruff==0.5.0" ruff check .
Like npx. Don’t install; just run.
Inline script deps (PEP 723)
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "httpx",
# "rich",
# ]
# ///
import httpx
from rich import print
print(httpx.get("https://api.example.com").json())
uv run script.py
uv reads the inline metadata, installs deps, runs. Self-contained scripts; no venv management.
Killer feature for one-off scripts and shareable utilities.
Performance
For a 30-dep pip install:
- pip: ~30s
- Poetry: ~25s
- uv: ~1-3s
Resolution + install both massively faster. Rust + smart caching. Especially noticeable in CI.
Migration
From pip + requirements.txt
uv add -r requirements.txt
# uv reads, adds to pyproject, writes lock
From Poetry
uv init
# manually copy deps from pyproject.toml
uv add ...
There’s no auto-migration tool but it’s straightforward.
From conda
uv doesn’t replace conda for non-Python deps (CUDA, GCC). If conda’s only doing Python: uv replaces it. If CUDA + Python: keep conda for the env, use uv inside it.
CI usage
- uses: astral-sh/setup-uv@v3
- run: uv sync --frozen
- run: uv run pytest
Fast: cache the uv cache directory. CI runs go from minutes to seconds for installs.
Workspace / monorepo
[tool.uv.workspace]
members = ["packages/*"]
Uv handles monorepos. Each package has its own pyproject.toml; uv sync resolves the whole workspace.
Lock structure
# uv.lock excerpt
[[package]]
name = "fastapi"
version = "0.110.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [...]
wheels = [...]
Human-readable; cross-platform. Reviewable in PRs.
What it doesn’t do
- Build wheels (use
uv buildwhich calls hatchling/setuptools). - Publish to PyPI (use
uv publish). - Run tasks (no replacement for
make/just). - Replace docker (it’s not a runtime).
Compatibility
uv works with:
- pip-installable packages.
- pyproject.toml (PEP 517/518/621).
- requirements.txt.
- VCS dependencies.
- Local path / editable installs.
If pip can install it, uv can.
Common mistakes
1. Mixing tools
pip install then uv sync — sync removes pip-installed packages. Pick one.
2. Not committing uv.lock
Production installs from pyproject.toml only — different versions than dev. Commit the lock.
3. Pinning in pyproject.toml for libraries
Library users get stuck on your pin. Pin in lock (apps); range in pyproject (libs).
4. Forgetting --frozen in CI
CI lock drifts; production behaves differently. Always --frozen in CI.
5. Not using script metadata
Hand-rolling scripts that depend on packages, requiring users to install them. PEP 723 is cleaner.
What I’d ship today
For new Python projects:
- uv for dependency, venv, Python.
- pyproject.toml +
uv.lock. uv tool installfor global tools (ruff, pre-commit, etc.).- PEP 723 inline metadata for scripts.
- CI with
setup-uv+uv sync --frozen. - Docker:
uv syncinstead of pip.
Read this next
If you want my uv + ruff + pre-commit project starter, it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .