uv in Python: Astral's Rust Package Manager for Data Science Workflows (2026)

uv is Astral's Rust-based Python package manager that replaces pip, pip-tools, pyenv, pipx, and Poetry with one tool that resolves and installs dependencies 10-100x faster. This 2026 guide covers uv.lock, PEP 723 scripts, workspaces, PyTorch/CUDA installs, and Jupyter integration.

uv Python Guide 2026: Fast Package Manager

Updated: August 31, 2026

uv is Astral's Rust-based Python package and project manager that combines pip, pip-tools, pyenv, pipx, poetry, and virtualenv into a single binary that resolves and installs dependencies 10-100x faster. For data science workflows in 2026, uv is the fastest way to lock a project, install PyTorch with CUDA, run PEP 723 single-file scripts, and manage Jupyter kernels across Python 3.10-3.14 without touching pyenv or Poetry. This guide covers uv 0.12.7, the uv.lock universal lockfile, workspaces for monorepos, and the specific commands data scientists reach for daily.

  • uv 0.12.7 (August 2026) resolves and installs Python packages 10-100x faster than pip and about 10x faster than Poetry, backed by Rust, PubGrub, and aggressive parallelism.
  • One binary replaces pip, pip-tools, pyenv, pipx, poetry, and virtualenv. You reach for uv add, uv sync, uv run, uv python install, and uvx.
  • uv.lock is a universal, cross-platform TOML lockfile that produces reproducible environments on macOS, Linux, and Windows from a single resolve.
  • PyTorch installs correctly with uv pip install torch --torch-backend=auto or by pinning the CUDA index. CUDA 13.0 is the default in PyTorch 2.11+.
  • PEP 723 inline metadata turns any .py file into a reproducible, lockable script. It's ideal for data-cleaning one-offs and cron jobs.
  • OpenAI acquired Astral in March 2026, giving uv the strongest corporate backing of any Python packaging tool.

What is uv and why do data scientists care?

uv is an all-in-one Python package and project manager written in Rust by Astral, the company behind Ruff. It ships as a single static binary with zero Python runtime dependency, so it can bootstrap Python itself. A typical data science stack used to need pyenv for interpreters, virtualenv for environments, pip for wheels, pip-tools for lockfiles, pipx for CLI tools, and Poetry for project metadata. uv rolls all six responsibilities into one tool and does each of them measurably faster.

The current version at the time of writing is 0.12.7, published on 27 August 2026. Astral ships roughly one release every three days (a cadence more typical of a compiler than a package manager), and OpenAI announced its acquisition of Astral in March 2026, so uv now sits inside the largest AI infrastructure company in the world. That matters for data scientists because the resolver and installer speeds directly translate to shorter CI pipelines, faster container builds, and less waiting during interactive experiments.

For Python data science specifically, uv fixes three long-standing friction points. Dependency resolution collapses from tens of seconds to milliseconds. PyTorch and CUDA wheels become one-liners. And Jupyter kernels no longer need bespoke registration scripts. Honestly, if you've ever spent an afternoon fighting a conflict between numpy, torch, and scikit-learn (I lost a full evening to this last spring), uv's PubGrub-based resolver produces cleaner error messages and usually finds a solution the older pip resolver would miss.

Is uv better than pip? Command-by-command mapping

Yes. For almost every practical workflow, uv is a strict superset of pip. uv provides a uv pip subcommand that's a drop-in for pip's most common verbs (install, uninstall, freeze, list, compile, sync), while uv add/uv sync/uv lock replace the higher-level Poetry style. The difference is speed and correctness: uv's resolver understands universal lockfiles, downloads wheels in parallel, and caches by content hash rather than by URL.

Below is the concrete migration table many teams tape to the wall while switching:

Legacy commanduv equivalent
pip install pandasuv pip install pandas or uv add pandas
pip-compile requirements.inuv pip compile requirements.in or uv lock
pip-syncuv sync
python -m venv .venvuv venv
pyenv install 3.13uv python install 3.13
pyenv local 3.13uv python pin 3.13
pipx install ruffuv tool install ruff
pipx run cowsayuvx cowsay
poetry init / add / install / runuv init / add / sync / run
poetry build / publishuv build / publish

The performance gap isn't marginal. On a warm-cache install of a 200-package Django+Celery+pandas+scikit-learn stack in April 2026, uv finished in about 8 seconds while pip took 90 seconds and Poetry 50 seconds. On a fresh Apple Silicon environment installing numpy, pandas, and requests, uv completed in 0.10 seconds versus 5.16 seconds for pip and 48.6 seconds for conda create, using 67 MB of disk against 275 MB for the Conda environment. In CI, several public benchmarks show PyTorch-heavy builds dropping from ~8 minutes to ~2 minutes just by swapping the install step for uv sync --frozen.

Installing uv and initializing a data science project

uv installs without Python. On macOS and Linux, the recommended installer is a one-liner curl script that drops a static binary into ~/.local/bin. Windows users can use the equivalent PowerShell script or grab a release binary from GitHub. Homebrew, Scoop, and Winget also ship uv, but the direct installer usually gets a new release first.

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

# Windows PowerShell
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"

# Verify
uv --version
# uv 0.12.7 (2026-08-27)

Once installed, a new data science project looks like this. The --bare flag matters, because since 0.12.0, uv init defaults to adding a build backend. That's useful for libraries, but pure noise for an analysis notebook or a Jupyter environment.

# Start a new project pinned to Python 3.13
uv init sales-forecast --python 3.13 --bare
cd sales-forecast

# Add the core stack; resolves & installs in parallel
uv add "pandas>=2.3" "polars>=1.39" numpy scikit-learn matplotlib

# Dev dependencies (not shipped with the project)
uv add --dev pytest ruff mypy ipykernel

# Run something in the managed environment (no activation needed)
uv run python -c "import pandas; print(pandas.__version__)"

# Freeze the exact set for reproducibility
uv lock

# Replay the lockfile on a fresh machine
uv sync --frozen

Everything lives in pyproject.toml and uv.lock. There's no separate requirements.txt, no poetry.lock, no Pipfile. The .venv directory is created automatically at the project root and picked up by uv run transparently. You never need to run source .venv/bin/activate unless you want to.

How does uv handle Python versions?

uv replaces pyenv with built-in Python management. It downloads portable Python builds (the python-build-standalone project) on demand, so uv python install 3.13 takes about two seconds on a fast connection and requires no C toolchain, no make, and no shell hook. If a project's requires-python constraint can't be satisfied by an installed interpreter, uv will download a compatible one the next time you uv sync.

# List installed and downloadable interpreters
uv python list

# Install a specific version
uv python install 3.14

# Pin the project to a specific Python (writes .python-version)
uv python pin 3.13

# Find the current project interpreter
uv python find

This is particularly powerful for data science reproducibility. In a research repo, checking in .python-version, pyproject.toml, and uv.lock gives collaborators a one-command bootstrap. uv sync --frozen pulls the correct Python, the exact wheels resolved on the original machine, and creates the venv. No README instructions telling people to install pyenv, brew tap this or that, or install a specific Xcode Command Line Tools version. Free-threaded (no-GIL) Python 3.13 and 3.14 builds are also available via uv python install 3.14t, which pairs nicely with our guide on NumPy 2.x migration and its free-threaded improvements.

Can uv install PyTorch with CUDA?

Yes, and it's the simplest PyTorch install experience in the Python ecosystem today. The official uv + PyTorch guide describes three approaches, but the shortest one is auto-detection:

# Detect CPU/GPU/ROCm/XPU at install time
uv pip install torch torchvision --torch-backend=auto

# Or pin the CUDA build explicitly
uv add torch torchvision --index https://download.pytorch.org/whl/cu130

Valid values for --torch-backend in 2026 are auto, cpu, cu118, cu126, cu128, cu130, rocm6.4, and xpu. Since PyTorch 2.11 the default CUDA build is cu130 (CUDA 13.0). If you need torchaudio, note that it's in maintenance mode and lags on newer CUDA builds. Drop it from your uv add line on cu132 or resolves will fail. (I hit this exact bug shipping a training image last month; the error message points at torch, but torchaudio is the actual culprit.)

For a project that must work on both a CPU-only laptop and a CUDA GPU workstation, the recommended pattern is to declare a source per platform in pyproject.toml:

[project]
dependencies = ["torch>=2.11", "torchvision"]

[tool.uv.sources]
torch = [
  { index = "pytorch-cu130", marker = "sys_platform == 'linux'" },
  { index = "pytorch-cpu",   marker = "sys_platform == 'darwin'" },
]

[[tool.uv.index]]
name = "pytorch-cu130"
url = "https://download.pytorch.org/whl/cu130"
explicit = true

[[tool.uv.index]]
name = "pytorch-cpu"
url = "https://download.pytorch.org/whl/cpu"
explicit = true

With this configuration, uv sync on macOS pulls CPU wheels and on Linux pulls the CUDA 13 build, all from a single uv.lock committed to Git. Combined with our guide on ML model serving with BentoML, Ray Serve, and FastAPI, this makes container images 2-4x smaller because you can strip CUDA out of the CI test image entirely.

Does uv work with Jupyter notebooks?

uv works with Jupyter through two idiomatic patterns. The lightest option is a per-project Jupyter that lives entirely inside .venv: uv add --dev jupyterlab ipykernel and then uv run jupyter lab. Everything you install with uv add becomes importable in the notebook because the kernel launches from the same environment.

The heavier but more IDE-friendly option is to register the project's kernel with the global Jupyter installation, which lets VS Code and PyCharm pick it up from any notebook file:

# Register this project's venv as a named kernel
uv add --dev ipykernel
uv run ipython kernel install --user \
  --env VIRTUAL_ENV "$(pwd)/.venv" \
  --name sales-forecast \
  --display-name "Python 3.13 (sales-forecast)"

VS Code 1.94+ and PyCharm 2025.3+ auto-detect uv projects. PyCharm 2025.3 promoted uv to the default environment manager for new projects, and 2026.2 added a redesigned settings panel with first-class workspace, monorepo, and remote-target support. For a reactive alternative to Jupyter, see our comparison of Marimo vs Jupyter notebooks. Both work identically well under uv.

What is uv.lock? The universal cross-platform lockfile

uv.lock is a human-readable TOML file that captures the fully resolved dependency graph across every operating system, CPU architecture, and Python version your project supports, all in a single artifact. Unlike poetry.lock, which resolves per-platform on the machine that generated it, uv.lock is a universal resolve: one uv lock on macOS produces the same locked resolution that uv sync --frozen on Linux or Windows will use. That's the property that makes reproducible CI/CD trivial.

The lockfile records exact wheel URLs, source distribution URLs, hashes (SHA-256), and marker expressions. It's automatically updated by uv add, uv remove, and uv lock. You commit it to VCS, you never hand-edit it, and you use it in three ways:

  • uv sync: apply the lockfile to the current .venv, re-resolving only if pyproject.toml has changed.
  • uv sync --frozen: apply the lockfile without touching the resolver. This is the CI/production command, and it fails fast if the lockfile doesn't match pyproject.toml.
  • uv sync --locked --all-extras --dev: verify the lockfile is up-to-date and install every optional group. Handy in a CI job that also runs uv lock --check.

You can export the lockfile to a legacy requirements.txt for tools that don't yet understand uv.lock. It's useful for Dockerfiles that use pip install in a base image or for compliance scanning tools:

uv export --format requirements-txt --no-hashes -o requirements.txt
uv export --only-group prod -o requirements.prod.txt

The uv.lock format also underpins the data pipeline reproducibility pattern many teams have adopted with Async ETL in Python. Commit the lockfile alongside the ETL DAG, tag the ETL run with the lockfile's Git SHA, and you get bit-for-bit reproducible transformations.

PEP 723 scripts: single-file reproducible pipelines

PEP 723, defined in the official Python Enhancement Proposal, adds inline dependency metadata to any standalone .py file. uv is the reference implementation. This turns a single Python file into a fully reproducible artifact. No pyproject.toml, no requirements.txt, no README with install instructions.

#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.12"
# dependencies = [
#   "pandas>=2.3",
#   "duckdb>=1.4",
#   "httpx",
# ]
# ///
"""Daily sales report: pull, transform, publish."""
import pandas as pd, duckdb, httpx

r = httpx.get("https://example.com/sales.json", timeout=30)
df = pd.DataFrame(r.json())
duckdb.connect("warehouse.duckdb").execute(
    "INSERT INTO sales SELECT * FROM df"
)
print(df.describe())

Save this as daily_sales.py, make it executable, and ./daily_sales.py just works. uv builds a temporary environment on first run (cached <10 ms on subsequent invocations), executes the script, and returns. There's no venv to activate, no dependency file to remember to install. For scheduled data jobs, this is genuinely transformative: a cron entry or an Airflow BashOperator can point at a single URL and know it will run identically forever.

The script header is editable programmatically. uv add --script daily_sales.py requests appends a dependency to the metadata block. uv lock --script daily_sales.py writes a daily_sales.py.lock alongside it for full determinism. For heavier orchestration than one-off scripts, compare with Airflow vs Prefect vs Dagster.

uv workspaces for ML monorepos

Data science teams that ship models often outgrow a single package. A typical structure includes a features library, a training app, a serving API, and a shared schemas package for Pydantic contracts. uv workspaces let all four live in one Git repo, share one .venv, and produce one uv.lock. The [tool.uv.workspace] block goes in the root pyproject.toml:

# pyproject.toml (repo root)
[project]
name = "ml-platform"
version = "0.1.0"
requires-python = ">=3.12"

[tool.uv.workspace]
members = ["packages/*"]

[tool.uv.sources]
schemas  = { workspace = true }
features = { workspace = true }

Each packages/*/pyproject.toml declares its own name, version, and dependencies. Cross-package references (say, training depending on features) resolve to editable installs of the sibling package automatically. The resolver intersects every member's requires-python range, so a workspace-wide Python pin is a single change.

Useful workspace commands:

uv sync --package training       # install only one member and its deps
uv run --package training python -m training.cli
uv add --package features polars # add a dep to one member's pyproject
uv lock                          # regenerate the single root uv.lock

The single-lockfile model plays well with our guide on MLflow, W&B, and Comet experiment tracking because every experiment can log the lockfile's Git SHA and get bit-for-bit reproducible re-runs.

uv vs pip vs Poetry vs Conda vs pixi

So, let's talk about the trade-offs. The Python packaging landscape in 2026 has consolidated around four serious contenders: uv, Poetry, Conda (via miniforge/mamba), and pixi. Plain pip remains the fallback and the interop layer (every tool speaks its wheel format), but almost no one uses raw pip for a new project anymore. Below is the honest trade-off matrix:

FeatureuvpipPoetryCondapixi
Cold install speed10-100x pipbaseline3-5x pipslowest10-100x pip (uses uv)
Lockfileuv.lock (universal)nonepoetry.lock (per-platform)environment.yml (weak)pixi.lock (universal)
Python version mgmtYes, built-inNoNoYesYes
Workspaces / monorepoYesNoLimitedNoYes
PEP 723 scriptsYes (reference impl)NoNoNoPartial
Non-Python binaries (CUDA toolkit, GDAL, HDF5)NoNoNoYes (conda-forge)Yes (conda-forge)
Tool runner (pipx replacement)Yes (uvx)NoNoNoYes (pixi global)
Implementation languageRustPythonPythonPythonRust
Recommended 2026 defaultPure-Python, ML on wheelsFallback onlyLegacy librariesRegulated / binary-heavyGDAL/HDF5-heavy science

The short version: if your dependencies are all on PyPI (which includes almost every ML library today, from PyTorch and JAX to scikit-learn, XGBoost, LightGBM, and the LLM tooling ecosystem), reach for uv. If you need conda-forge binaries like GDAL, PROJ, GEOS, HDF5, or the CUDA toolkit itself (not just the runtime), pixi gives you conda-style binary distribution with a uv-style workflow. Conda proper still makes sense in regulated environments where the conda-forge channel is a compliance requirement. Poetry remains a fine choice for pure-Python library authors who prize its opinionated project scaffolding and don't mind the resolver being a hundred times slower.

Common pitfalls and gotchas

uv is remarkably smooth, but a handful of edges bite data science users regularly. Knowing them up front saves an hour of debugging.

1. PyTorch defaults to CPU wheels

Without --torch-backend, a --index, or a [tool.uv.sources] mapping in pyproject.toml, uv will resolve torch to the CPU build. This isn't a bug, it's the safe default, but it surprises people upgrading from a Conda workflow where GPU wheels were automatic.

2. Editable installs and [tool.uv.cache-keys]

uv only rebuilds an editable install when pyproject.toml, setup.py, setup.cfg, or the presence of a src/ directory changes. If your build reads other files (a VERSION.txt, a build.rs, a codegen step), add [tool.uv.cache-keys]. Watch out: the setting replaces the default keys, so you must re-list pyproject.toml.

3. uvx --from <path> caches source aggressively

Issue #16196 on the astral-sh/uv tracker documents that uvx --from ./mytool does not invalidate on file edits. For local iteration on a tool, use uv run from inside the project instead.

4. Activation is optional (and sometimes harmful)

You almost never need source .venv/bin/activate. Every uv run, uv sync, and uvx command handles the environment implicitly. Manually activating can shadow tools or hide a missing uv sync step behind stale state.

5. Cache location on Windows

uv's cache defaults to %LOCALAPPDATA%\uv\cache on Windows, but UV_CACHE_DIR is currently ignored by uv python install on that platform (issue #9749). If disk pressure matters, symlink the cache directory instead.

6. Pre-1.0 breakage

Astral's versioning policy allows minor bumps to break behaviour. 0.12.0 changed the uv init default. Pin uv itself in CI (curl -LsSf https://astral.sh/uv/0.12.7/install.sh | sh) and pin uv_build in pyproject.toml if you ship wheels.

Frequently Asked Questions

Is uv production-ready if it is still on version 0.x?

Yes. Astral treats uv as production software; the 0.x is a signal that minor bumps may include breaking changes, not that the tool is unstable. It's used in production by Meta, Bloomberg, and (since March 2026) OpenAI. Pin the uv version in CI, pin uv_build if you publish wheels, and read the CHANGELOG before minor upgrades.

How do I migrate from Poetry to uv?

Run uvx migrate-to-uv in the project root. It reads pyproject.toml, converts Poetry's [tool.poetry] table to the standard [project] layout, translates group dependencies to [dependency-groups], and drops a compatible [tool.uv] block. Follow up with uv lock to produce a fresh uv.lock, then delete poetry.lock.

What is the difference between uv add and uv pip install?

uv add is project-aware: it updates pyproject.toml, refreshes uv.lock, and installs the resolved wheels into .venv. uv pip install is pip-compatibility mode: it mutates the environment only and does not touch project metadata. Use uv add for anything you want to persist across machines; use uv pip install for one-off ad-hoc experiments.

Does uv work with Docker and GitHub Actions?

Yes. Astral publishes an official Docker image (ghcr.io/astral-sh/uv) and a GitHub Action (astral-sh/setup-uv). In a Dockerfile, use the multi-stage pattern: copy the uv binary, run uv sync --frozen --no-dev in a builder stage, and copy the resulting .venv into a slim runtime image. This is the standard way to build reproducible ML container images in 2026.

Is uv better than pixi for scientific Python?

It depends on the binaries. uv wins on speed, ergonomics, and PEP 723 script support. pixi wins when you need conda-forge binaries such as GDAL, PROJ, GEOS, HDF5, netCDF, or the CUDA toolkit itself (not the PyTorch CUDA runtime, which uv handles fine). Many teams use both: pixi for the geospatial and HPC stack, uv for everything else.

Editorial Team
About the Author Editorial Team

Our team of expert writers and editors.