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 build which 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 install for global tools (ruff, pre-commit, etc.).
  • PEP 723 inline metadata for scripts.
  • CI with setup-uv + uv sync --frozen.
  • Docker: uv sync instead 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 .