Python uv
Fast, all-in-one Python project and package manager: init, add, sync, run, tools, inline scripts, lockfiles, and migration from pip/poetry.
Python uv
The fast, all-in-one Python project and package manager — the command reference.
Overview
uv is a single Rust binary from Astral that replaces pip, pip-tools, pipx, pyenv, virtualenv, and most of poetry. It manages Python interpreters, virtual environments, project dependencies, lockfiles, tools, inline scripts, builds, and publishing. This sheet is the task-oriented command reference; for the wider packaging story — build backends, pyproject.toml anatomy, publishing to PyPI, version pinning — see Python Packaging and Dependency Management. Commands verified against uv 0.11.x.
The mental model: pyproject.toml declares abstract dependencies, uv.lock records the exact cross-platform resolution, and .venv is kept in sync for you. You rarely activate the environment — uv run does it.
flowchart LR
A[uv init] --> B[uv add / uv remove]
B --> C[pyproject.toml]
C --> D[uv lock]
D --> E[uv.lock]
E --> F[uv sync]
F --> G[.venv]
G --> H[uv run]
H -.->|edit deps| B
uv keeps the environment current implicitly: uv add, uv remove, and uv run all re-lock and re-sync as needed, so the explicit uv lock / uv sync steps are mostly for CI and Docker.
Project Workflow
The day-to-day loop: scaffold a project, add dependencies, run code. uv edits pyproject.toml, refreshes uv.lock, and syncs .venv in one step.
Key Concepts
- uv init: Scaffolds a project (
pyproject.toml,.python-version,.venvon first sync) - uv add / uv remove: Edit
pyproject.toml, re-lock, and sync in one command - uv sync: Make
.venvmatchuv.lockexactly — adds and removes packages - uv run: Execute a command in the project environment, syncing first if stale
- uv.lock: Cross-platform lockfile — commit it; never hand-edit it
Common Commands
# Scaffold a new project (app by default — no build system)
uv init myproject
cd myproject
# Scaffold a publishable library / packaged app (adds uv_build backend)
uv init --lib mylib
uv init --package myapp
# Scaffold in the current directory
uv init
# Add runtime dependencies (updates pyproject.toml, uv.lock, and .venv)
uv add requests "pydantic>=2.0"
uv add 'uvicorn[standard]' # quote extras so the shell leaves them alone
# Add from git / a local path / a URL
uv add "git+https://github.com/psf/requests"
uv add ../shared-lib # editable by default for local paths
# Remove a dependency
uv remove requests
# Run inside the project env (auto-syncs first — no activation needed)
uv run python -m myproject
uv run pytest
uv run -- python script.py --flag value # -- ends uv's own option parsing
# Sync the env to the lockfile explicitly
uv sync # includes the dev group by default
uv sync --no-dev # production: omit dev dependencies
uv sync --all-groups # every dependency group
uv sync --frozen # install from uv.lock as-is; never re-resolve
uv sync --locked # fail if uv.lock is stale (CI guard, no mutation)
# Inspect the resolved dependency tree
uv tree
Examples
A minimal uv-managed pyproject.toml after uv init and a couple of uv add calls:
[project]
name = "myproject"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
"requests>=2.32.0",
"pydantic>=2.0.0",
]
[dependency-groups]
dev = [
"pytest>=8.0.0",
"ruff>=0.8.0",
]
Dependency Groups and Dev Dependencies
uv writes dev and optional dependencies to the right place automatically. Dev tooling goes in [dependency-groups] (PEP 735), which is not published in package metadata; published extras go in [project.optional-dependencies].
Common Commands
# Dev group (the default group; installed by uv sync unless --no-dev)
uv add --dev pytest ruff mypy
# A named group — for docs, lint, type-check, etc.
uv add --group docs mkdocs-material
uv add --group lint ruff
# A published optional extra (consumers get it via `pip install pkg[cli]`)
uv add --optional cli typer
# Sync selected groups
uv sync --group docs # default + docs
uv sync --only-group docs # docs alone, nothing else
uv sync --no-default-groups # skip even the dev group
uv run --group lint ruff check . # run with a group available ad hoc
This produces two distinct tables — extras for consumers, groups for developers:
[project.optional-dependencies]
cli = ["typer>=0.12.0"] # published — installable as pkg[cli]
[dependency-groups]
dev = ["pytest>=8.0.0"] # dev-only — what `uv add --dev` writes
docs = ["mkdocs-material>=9.5.0"]
Lockfiles
uv.lock records the exact resolution for every platform and Python version in range — a lock generated on macOS installs identically on Linux CI. It is the source of truth for reproducible installs. Commit it; let uv manage it.
Common Commands
# Generate or refresh the lockfile (implicit on add/remove/run/sync)
uv lock
# CI guard: fail if uv.lock is stale relative to pyproject.toml
uv lock --check
# Upgrade within the constraints declared in pyproject.toml
uv lock --upgrade # everything
uv lock --upgrade-package requests # one package
# Install exactly what is locked, refusing to re-resolve
uv sync --frozen # use uv.lock as-is
uv sync --locked # error if uv.lock is out of date
# Export to requirements.txt for tools that can't read uv.lock
uv export -o requirements.txt # hashes included by default
uv export --no-hashes --no-dev -o requirements.txt
--frozen vs --locked: --frozen installs from uv.lock without checking it against pyproject.toml; --locked first verifies the lock is current and errors if not. Use --locked in CI to catch a forgotten uv lock, --frozen in Docker for speed.
Running and Inline Scripts (PEP 723)
uv run executes anything in the project environment. For one-off scripts, uv reads inline dependency metadata (PEP 723) and builds a throwaway environment on the fly — no project, no manual venv.
Key Concepts
- uv run: Runs a command/script with the project (or an ephemeral) environment
- PEP 723 inline metadata: A
# /// scriptblock declaring deps inside a.pyfile - uv add --script: Writes/updates that block for you
- --with: Inject extra packages for a single invocation without touching the project
Common Commands
# Run with extra packages injected for this call only
uv run --with rich -- python -m myproject
uv run --with 'httpx>=0.27' script.py
# Run a script outside any project (ephemeral env from its inline metadata)
uv run --no-project script.py
# Run a throwaway Python with specific deps and no project at all
uv run --no-project --with pandas -- python -c "import pandas; print(pandas.__version__)"
Inline Script Dependencies
A self-contained, runnable script — uv resolves and caches the environment on first run:
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "httpx",
# "rich",
# ]
# ///
import httpx
from rich import print
r = httpx.get("https://api.example.com/status")
print(r.json())
# Run it — uv reads the block, builds the env, executes
uv run status.py
# Let uv write/maintain the inline block for you
uv add --script status.py httpx rich
uv remove --script status.py rich
# Pin the interpreter for the script
uv add --script status.py --python 3.12 httpx
# Lock a script's dependencies into status.py.lock for reproducibility
uv lock --script status.py
Inline metadata makes a single .py shareable and runnable by anyone with uv — no requirements.txt, no setup step. Pair it with a #!/usr/bin/env -S uv run --script shebang to make the file directly executable.
Tools (uvx)
uv installs and runs CLI tools in their own isolated environments — the pipx replacement. uvx is an alias for uv tool run.
Common Commands
# Run a tool one-off without installing it (downloads + caches on first use)
uvx ruff check .
uvx black --check .
# Disambiguate when the command name differs from the package
uvx --from httpie http GET https://example.com
uvx --from 'mkdocs-material' mkdocs --version
# Pin the tool version
uvx ruff@0.8.0 check .
uvx --from 'ruff==0.8.0' ruff check .
# Install a tool persistently onto PATH (isolated env per tool)
uv tool install ruff
uv tool install --with mkdocs-material mkdocs # tool + plugins together
# Manage installed tools
uv tool list
uv tool upgrade ruff
uv tool upgrade --all
uv tool uninstall ruff
# Ensure the tool bin directory is on PATH (prints/updates shell profile)
uv tool update-shell
Use uvx for throwaway invocations (CI linting, one-off generators) and uv tool install for tools you reach for daily. Each tool gets its own environment, so their dependencies never collide.
Python Version Management
uv downloads and manages standalone CPython builds — no compiling, no shims. This replaces pyenv. uv will fetch a missing interpreter automatically when a command needs one.
Common Commands
# Install one or more interpreters (from python-build-standalone)
uv python install 3.11 3.12 3.13
# List installed and available versions
uv python list
uv python list --only-installed
# Pin the project's interpreter (writes .python-version; commit it)
uv python pin 3.12
# Find where an interpreter lives
uv python find 3.12
# Use a specific version for a one-off run (downloads if absent)
uv run --python 3.13 python --version
# Free-threaded / other variants
uv python install 3.13t # free-threaded (no-GIL) build
.python-version drives uv venv, uv sync, and uv run. requires-python in pyproject.toml constrains the resolution; .python-version selects the interpreter used now — keep them compatible.
Virtual Environments
uv venv creates an environment instantly. You usually don't need to activate it — uv run and uv sync target .venv automatically — but activation still works for interactive shells.
Common Commands
# Create .venv (respects .python-version; downloads the interpreter if needed)
uv venv
# Pin the interpreter / location / name explicitly
uv venv --python 3.12
uv venv .venv-3.13 --python 3.13
# Seed pip + setuptools into the venv (only if a tool insists on them)
uv venv --seed
# Activate (optional — uv run works without it)
source .venv/bin/activate # Linux / macOS
.venv\Scripts\activate # Windows
deactivate
# Install into the active/created venv with the pip-compatible interface
uv pip install requests
uv pip install -e ".[dev]" # editable, with extras
uv pip list
For project work prefer uv sync over uv pip install — sync keeps the environment matched to the lockfile, whereas uv pip install mutates the venv without recording anything in uv.lock.
Building and Publishing
uv builds and publishes too, acting as a frontend to any PEP 517 backend. This is the short version — the full publishing workflow (Trusted Publishing/OIDC, TestPyPI, private indexes, version bumping, attestations) lives in Python Packaging and Dependency Management.
# Build wheel + sdist into dist/ (--no-sources before publishing)
uv build
uv build --no-sources
# Bump the version in pyproject.toml
uv version --bump patch # also: minor, major, alpha, beta, rc, stable
# Publish to PyPI — Trusted Publishing in CI (id-token: write),
# or a token locally via UV_PUBLISH_TOKEN / --token
uv publish
# Sanity-check artefacts before upload
uvx twine check dist/*
The pip-Compatible Interface
uv pip is a drop-in, much faster reimplementation of pip and pip-tools for working against the active environment directly — handy in scripts, legacy workflows, and minimal images. It does not read or write uv.lock; for managed projects use uv add / uv sync instead.
# Install / uninstall against the current environment
uv pip install requests "django>=4.2"
uv pip install -r requirements.txt
uv pip install -r requirements.txt -c constraints.txt
uv pip uninstall requests
# pip-tools replacement: compile requirements.in -> pinned requirements.txt
uv pip compile requirements.in -o requirements.txt
uv pip compile --generate-hashes requirements.in -o requirements.txt
uv pip compile pyproject.toml -o requirements.txt
# Sync the environment to match a requirements file exactly
uv pip sync requirements.txt
# Inspect
uv pip list
uv pip freeze
uv pip show requests
uv pip check # report broken dependencies
Migration
From pip / venv
# Bring an existing requirements.txt into a uv project
uv init # if no pyproject.toml yet
uv add -r requirements.txt # import deps into pyproject.toml
# Or keep the pip-style workflow, just faster
uv venv
uv pip install -r requirements.txt
From poetry
uv reads PEP 621 [project] metadata directly. If a project still uses the legacy [tool.poetry] tables, migrate the metadata into [project] (the uvx migrate-to-uv community tool automates this), then add deps and lock:
# Reimport dependencies once metadata is in [project] form
uv add 'fastapi>=0.100' 'uvicorn[standard]'
uv add --dev pytest ruff
uv lock # replaces poetry.lock with uv.lock
uv sync
uv vs pip command mapping
| Task | pip / pip-tools | uv |
|---|---|---|
| Create environment | python -m venv .venv |
uv venv |
| Install a package | pip install requests |
uv add requests (project) / uv pip install requests |
| Install from requirements | pip install -r requirements.txt |
uv pip install -r requirements.txt |
| Uninstall | pip uninstall requests |
uv remove requests / uv pip uninstall requests |
| Freeze versions | pip freeze > requirements.txt |
uv pip freeze / uv export |
| Compile a lockfile | pip-compile requirements.in |
uv pip compile requirements.in -o requirements.txt |
| Sync to a lockfile | pip-sync requirements.txt |
uv pip sync requirements.txt |
| List installed | pip list |
uv pip list |
| Check conflicts | pip check |
uv pip check |
| Run a tool one-off | pipx run black |
uvx black |
| Install a CLI tool | pipx install ruff |
uv tool install ruff |
| Install Python | pyenv install 3.12 |
uv python install 3.12 |
uv vs poetry command mapping
| Task | poetry | uv |
|---|---|---|
| Init project | poetry init / poetry new |
uv init / uv init --package |
| Add dependency | poetry add requests |
uv add requests |
| Add dev dependency | poetry add --group dev pytest |
uv add --dev pytest |
| Add to a group | poetry add --group docs mkdocs |
uv add --group docs mkdocs |
| Remove dependency | poetry remove requests |
uv remove requests |
| Install from lock | poetry install |
uv sync |
| Install, no dev | poetry install --without dev |
uv sync --no-dev |
| Update / relock | poetry update |
uv lock --upgrade |
| Lock dependencies | poetry lock |
uv lock |
| Verify lock current | poetry check --lock |
uv lock --check |
| Run in env | poetry run pytest |
uv run pytest |
| Activate shell | poetry env activate |
source .venv/bin/activate |
| Build | poetry build |
uv build |
| Publish | poetry publish |
uv publish |
| Bump version | poetry version patch |
uv version --bump patch |
| Export requirements | poetry export -o requirements.txt |
uv export -o requirements.txt |
Quick Reference
| Task | Command |
|---|---|
| New project / library | uv init myproject / uv init --lib mylib |
| Add dependency | uv add requests |
| Add dev dependency | uv add --dev pytest |
| Add to a group / extra | uv add --group docs sphinx / uv add --optional cli typer |
| Remove dependency | uv remove requests |
| Lock dependencies | uv lock |
| Sync environment | uv sync (--no-dev --frozen for prod) |
| Verify lock is current | uv lock --check (CI) / uv sync --locked |
| Run in project env | uv run pytest |
| Run with extra package | uv run --with rich script.py |
| Run inline script (PEP 723) | uv run script.py |
| Add deps to a script | uv add --script script.py httpx |
| Upgrade one package | uv lock --upgrade-package requests |
| Run a tool one-off | uvx ruff check . |
| Install a CLI tool | uv tool install ruff |
| Install Python | uv python install 3.12 |
| Pin Python version | uv python pin 3.12 |
| Create venv | uv venv (--python 3.12) |
| pip-style install | uv pip install -e ".[dev]" |
| Export requirements.txt | uv export --no-hashes -o requirements.txt |
| Build / publish | uv build --no-sources / uv publish |
| Bump version | uv version --bump patch |
| Dependency tree | uv tree |
| Upgrade uv itself | uv self update |
Common Issues and Solutions
| Issue | Solution |
|---|---|
uv add changed unrelated package versions |
Expected — uv re-resolves the whole graph. Pin the version you care about, or use uv lock --upgrade-package X for surgical bumps |
| Lockfile out of sync in CI | Run uv lock and commit uv.lock; gate CI with uv lock --check or uv sync --locked |
uv pip install didn't update uv.lock |
By design — uv pip targets the venv directly. Use uv add / uv sync for managed projects |
| Wrong Python version selected | Set it with uv python pin 3.12 (writes .python-version); confirm requires-python in pyproject.toml is compatible |
Tool command not found after uv tool install |
Run uv tool update-shell and restart the shell so the tool bin dir is on PATH |
uvx foo runs the wrong command |
The package and command names differ — use uvx --from package command |
| Inline-script deps re-resolve every run | Lock them once with uv lock --script script.py to pin against script.py.lock |
| Dev tools shipped to production | Dev deps belong in [dependency-groups], not dependencies; build/install with uv sync --no-dev |
| Slow / failed network on locked install | uv sync --frozen --offline reuses the cache; pre-warm with uv sync in a cached CI/Docker layer |
| Editable local dep not picked up | uv add ../lib adds it editable; re-run uv sync after changes to its metadata |
| Need a plain requirements.txt | uv export --no-hashes -o requirements.txt (or uv pip compile from a .in) |
| uv itself is out of date | uv self update (Homebrew installs: brew upgrade uv) |
Related Topics
The following topics complement uv:
- Python Packaging and Dependency Management: The deep dive — build backends,
pyproject.tomlanatomy, lockfile theory, and publishing to PyPI - Python - Testing with pytest:
uv run pytestis the canonical test invocation; dev deps live in the dev group - Python - Type Hints:
uv tool install mypy/uv run mypyfor static type checking in the project environment - Python - CLI Applications: Ship CLIs via
[project.scripts], distribute and run them withuv tool/uvx - Makefiles: Wrap
uv sync,uv run, anduv lock --checkin make targets for a consistent developer interface - GitHub Actions:
astral-sh/setup-uvwith caching for fast, reproducible CI builds fromuv.lock