Available for day contractsFrom 21st September I have availability for day and half day contracts. Please contact for more information.

Contact →
mikepreston.org

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.

edit depsuv inituv add / uv removepyproject.tomluv lockuv.lockuv sync.venvuv runedit depsuv inituv add / uv removepyproject.tomluv lockuv.lockuv sync.venvuv run

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, .venv on first sync)
  • uv add / uv remove: Edit pyproject.toml, re-lock, and sync in one command
  • uv sync: Make .venv match uv.lock exactly — 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 # /// script block declaring deps inside a .py file
  • 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:

  1. Python Packaging and Dependency Management: The deep dive — build backends, pyproject.toml anatomy, lockfile theory, and publishing to PyPI
  2. Python - Testing with pytest: uv run pytest is the canonical test invocation; dev deps live in the dev group
  3. Python - Type Hints: uv tool install mypy / uv run mypy for static type checking in the project environment
  4. Python - CLI Applications: Ship CLIs via [project.scripts], distribute and run them with uv tool / uvx
  5. Makefiles: Wrap uv sync, uv run, and uv lock --check in make targets for a consistent developer interface
  6. GitHub Actions: astral-sh/setup-uv with caching for fast, reproducible CI builds from uv.lock