Uv: package manager

uv generalities

Relies on the pip repository :
https://pypi.org/

Architecture & Nature :
uv is an extremely fast, standalone Python package and project manager written in Rust.
It replaces pip, pip-tools, pipx, poetry, pyenv, virtualenv, build and twine in a single unified binary.
It must be installed globally at the system level (not inside a virtual environment).

Execution model : uv run VS manual venv activation :
With active venv : Once source .venv/bin/activate is run, standard direct commands (python script.py, pytest, ruff) run inside the venv as usual.
Without active venv (recommended) : uv run <command> automatically discovers the project .venv (or creates/syncs it if missing) and runs the command in isolation without altering the host shell environment.

Why uv is better than pip-compile, Poetry, and PDM

While pip-compile, Poetry, and PDM solved critical packaging problems in the past, uv supersedes them all by consolidating the entire Python toolchain into a single, high-performance binary.

1. Versus pip-compile (pip-tools) :
Performance : uv pip compile is 100x to 1000x faster than pip-compile.
Cross-platform portability : pip-compile resolves dependencies specifically for the host OS and environment, creating platform-dependent requirements.txt files.
uv generates a single, universal multi-platform lockfile (uv.lock) working seamlessly across Linux, macOS, and Windows.
All-in-one scope : pip-tools only locks dependencies.
uv manages Python versions, virtual environments, execution (uv run), workspaces, and building/publishing packages.

2. Versus Poetry :
Strict PEP 621 (Storing project metadata in pyproject.toml) compliance : Poetry uses its own proprietary table ([tool.poetry]), creating vendor lock-in.
uv uses standard [project] metadata, ensuring universal compatibility.
Speed & Resolution : Poetry’s solver often hangs or takes minutes on complex projects.
uv resolves full dependency trees in milliseconds thanks to Rust.
Integrated Python management : Poetry requires external tools like pyenv to install Python interpreters.
uv automatically downloads and manages Python builds on the fly.
Superior Workspaces : uv handles monorepos with a single unified uv.lock natively and effortlessly, whereas Poetry workspace support requires complex configurations.

3. Versus PDM :
Execution speed : While PDM pioneered PEP 621 adoption, it is written in Python and suffers from performance bottlenecks compared to uv.
Built-in CLI tool management : uv replaces pipx with uv tool and uvx out of the box, avoiding extra external dependencies.
Ecosystem momentum : Developed by Astral (creators of ruff), uv is rapidly becoming the de facto industry standard backed by massive enterprise adoption.

uv-pip incompatibility issues

Pip allows us to name our virtual environment directory as we want : venv, .venv, anything…
But uv waits for .venv and not other thing.
By default, you will get that kind of error during some uv commands such as : warning: `VIRTUAL_ENV=venv` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead.

To overcome that issue while still using another directory name, we have 2 options:
– Set a env var with the wished name such as : UV_PROJECT_ENVIRONMENT=venv
– add the --active flags in uv commands.

uv quirks

Workspace and sub-projects : dependency resolution with sync
Concerning dependency resolution between root and children sub-projects, uv is very inconsistent.
Executing uv sync from :
– the root with --package foo
– inside the foo child directory
– the root with --all-packages
may produce totally different results : for example root’s default dependency-groups (dev, etc.) may silently not be pulled in when targeting a single package, even though the docs describe them as « shared across the workspace ».

Two independent scoping axes that don’t talk to each other :
--package X / --all-packages control which project code (the package itself + its normal dependencies) gets installed.
--group X / --all-groups control which dependency-groups get installed, and this is resolved against the entire workspace’s [dependency-groups] tables, regardless of what --package targets.
Consequence : uv sync --package foo --all-groups will still pull in a group only defined at the workspace root, even though --package supposedly scopes you to foo. The two flags simply don’t share the same notion of « scope ».

Implicit vs explicit group resolution is even less consistent :
Without an explicit --group/--all-groups flag, uv falls back to the implicit dev group + whatever is listed in [tool.uv] default-groups. This implicit path only works reliably when default-groups is declared at the workspace root — trying to scope a default group to a specific member fails, per astral-sh/uv#14377. So implicit resolution and explicit --group resolution don’t follow the same rules, which is exactly what causes dev tools (pytest, pyright…) to silently vanish from a CI environment that only ran a plain uv sync targeted at one sub-package.

In a general way, to avoid any trouble : never rely on implicit/default behavior in a workspace with more than one package or dependency-group. Always be fully explicit and run from the root :
uv sync --all-packages
This is also the fix explicitly recommended by uv maintainers themselves in astral-sh/uv#9755 — note there’s still no persistent config or env var to make this the default, so it has to be repeated on every invocation (alias / Makefile / CI script recommended).

uv Environment Variables

Virtual environment & project :
UV_PROJECT_ENVIRONMENT : path to the project’s .venv (bypasses the .venv requirement, see the pip incompatibility section above)
VIRTUAL_ENV : read by uv to detect an already-active venv — this is what triggers the warning mentioned earlier
UV_PROJECT : equivalent to --project, forces the project root
UV_NO_SYNC : equivalent to --no-sync, prevents uv run from auto-syncing the environment
UV_FROZEN / UV_LOCKED : runs without updating uv.lock / asserts it stays unchanged (useful in CI)

Python & interpreter :
UV_PYTHON : forces a specific interpreter/version for all operations
UV_PYTHON_PREFERENCE : prefer a uv-managed Python or a system Python
UV_PYTHON_INSTALL_DIR : directory where uv stores downloaded Python builds
UV_PYTHON_DOWNLOADS : whether uv is allowed to auto-download a missing Python
UV_NO_MANAGED_PYTHON / UV_MANAGED_PYTHON : disables / requires use of a uv-managed Python
UV_SYSTEM_PYTHON : equivalent to --system (⚠️ intended for CI/Docker, modifies the system Python)

Indexes, proxy & network :
UV_DEFAULT_INDEX : default index (replaces UV_INDEX_URL, deprecated)
UV_INDEX : space-separated list of additional indexes (replaces UV_EXTRA_INDEX_URL, deprecated)
UV_INDEX_{NAME}_USERNAME / UV_INDEX_{NAME}_PASSWORD : HTTP Basic credentials for a named index declared in [[tool.uv.index]]
UV_INDEX_STRATEGY : first-index (default) / unsafe-first-match / unsafe-best-match
UV_INSECURE_HOST : equivalent to --allow-insecure-host
UV_HTTP_TIMEOUT : HTTP timeout in seconds (default 30s)
UV_OFFLINE : disables all network access
UV_SYSTEM_CERTS (replaces UV_NATIVE_TLS, deprecated) : uses the platform’s certificate store instead of the bundled Mozilla certs
HTTP_PROXY / HTTPS_PROXY / NO_PROXY / ALL_PROXY : standard proxy variables, read natively by uv

Cache & storage :
UV_CACHE_DIR : location of the global cache
UV_NO_CACHE : disables the cache for the current operation
UV_TOOL_DIR : directory where tools installed via uv tool install are stored
UV_TOOL_BIN_DIR : directory for those tools’ linked executables (bin)

CLI behavior :
UV_BREAK_SYSTEM_PACKAGES : equivalent to --break-system-packages (⚠️ CI/Docker only)
UV_PRERELEASE : allows pre-release versions (e.g. allow)
UV_RESOLUTION : resolution strategy (e.g. lowest-direct)
UV_COMPILE_BYTECODE : compiles .pyc files after install
UV_LINK_MODE : link mode used between the cache and the venv
UV_NO_PROGRESS : disables progress bars (useful in CI/logs)

Installing uv itself :
UV_INSTALL_DIR : target directory for the uv binary (default ~/.local/bin)
UV_NO_MODIFY_PATH : prevents the installer from modifying PATH / shell profiles

Global uv Installation & Location

uv must be installed at the system level, never inside a project venv. Several installation methods exist — better not to mix several on the same machine, since update handling differs by method.

1. Standalone installer (recommended by Astral) :
curl -LsSf https://astral.sh/uv/install.sh | sh (Linux)
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" (Windows)
This is the only method that supports self-updating via uv self update.

2. Via a third-party package manager :
pipx install uv (recommended if installing from PyPI, isolates uv in its own environment)
pip install uv

Binary location :
– Standalone installer : ~/.local/bin/uv and ~/.local/bin/uvx
– Customizable via the UV_INSTALL_DIR variable

Workspace Mechanics : Inheritance, Scope & Cross-Linking

1. Inherited & Shared at Root (Global Scope) :
Single Virtual Environment : All workspace members share a single .venv located at the workspace root.
Unified Lockfile : A single uv.lock resolves all dependencies across all sub-projects simultaneously, preventing version conflicts.
Shared Development Tools : Development dependency groups ([dependency-groups], e.g. pytest, ruff, pyright) declared at root are installed into the shared .venv and available across all sub-projects.
Global Tool Configurations : Rules defined in root pyproject.toml (e.g. [tool.uv], [tool.pyright], [tool.ruff]) and the pinned Python version (.python-version) apply workspace-wide.
Indexes & Mirrors : Private registries or proxy configurations defined at root ([[tool.uv.index]]) are automatically inherited by all workspace members.

2. Isolated per Sub-package (NO Metadata Inheritance) :
Direct Dependencies : Production dependencies ([project.dependencies]) are strictly isolated. Sub-packages do NOT inherit runtime requirements declared at root or by sibling packages. Each member must explicitly declare what it directly imports.
Package Metadata & Build System : Package identity (name, version, your project authors) and build backends remain independent per sub-package, allowing individual wheel building (uv build) and PyPI publishing.

3. Local Cross-Linking :
Workspace Dependency Binding : Declaring [tool.uv.sources] package-name = { workspace = true } links local sub-packages in editable mode inside the shared .venv.
Instant Hot-Reloading : Code modifications in a local library (e.g. models-lib) take effect instantly in consuming applications (e.g. service-lib) without requiring manual re-installation.

Python version management

List available and installed Python versions :
uv python list
Scans the system to detect both uv-managed builds, externally installed versions (OS package managers, Homebrew, pyenv, etc.), and downloadable versions from the registry.

To filter and show only installed versions on the machine :
uv python list --only-installed

To find the exact binary location of a version :
uv python find 3.12

Install a specific Python version (downloads standalone builds) :
uv python install 3.12
uv python install 3.10 3.11 3.12

Pin Python version for the current project (.python-version file) :
uv python pin 3.12
Creates a standard .python-version file at the project root.
Enforces this exact Python version for all local uv commands (and automatically downloads it on the fly if missing).

List / inspect commands

Show visual dependency tree (project workspace) :
uv tree
or for flat environment / pip mode :
uv pip tree

List installed packages :
uv pip list

Check installed packages compatibility and integrity :
uv pip check
Detects broken requirements and version conflicts among installed packages without modifying them.

Show installed package metadata and files location :
uv pip show --files foo-package

Project dependency management 

Add dependencies to pyproject.toml and update lockfile :
uv add requests
uv add 'requests>=2.31.0,<3.0'

Add development / test dependencies :
uv add --dev pytest

Remove a dependency :
uv remove requests

Generate / Update lockfile without modifying environment :
uv lock
or upgrade all locked versions :
uv lock --upgrade

Sync virtual environment with lockfile (installs/removes packages to match exactly) :
uv sync
or sync all workspace packages and extras :
uv sync --all-packages

Pip drop-in replacement mode (uv pip)

Create a virtual environment :
uv venv
with specific Python version :
uv venv --python 3.10

Install packages into venv (auto-detects local .venv without manual activation) :
uv pip install requests python-dateutil
uv pip install 'pycryptodome==3.20.0'
uv pip install -r requirements.txt

Install packages into a specific venv :
uv pip install --python .venv_ide requests python-dateutil
uv pip install --python .venv_ide 'pycryptodome==3.20.0'
uv pip install --python .venv_ide -r requirements.txt

Compile dependencies to requirements.txt (replaces pip-compile) :
uv pip compile pyproject.toml -o requirements.txt
or from requirements.in :
uv pip compile requirements.in -o requirements.txt

Upgrade a package :
uv pip install --upgrade foo-package

Force reinstall :
uv pip install foo-package --force-reinstall

Install in system environment (Docker only / equivalent to –break-system-packages) :
uv pip install --system requests

Application and dependencies : development and build

Build, package and publish a library with uv

Standard pyproject.toml (PEP 621) :

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
 
[project]
name = "service-lib"
version = "0.1.0"
description = "My package description"
authors = [
    {name = "David"},
]
requires-python = "&gt;=3.10"
classifiers = [
    "Framework :: Flask",
    "Programming Language :: Python :: 3.10",
]
 
dependencies = [
    "flask==2.3.1",
    "flask-cors==4.0.0",
    "requests==2.31.0",
    "gunicorn==21.2.0",
    "models-lib",
]

1. Create the virtual environment :
uv venv --python 3.10

2. Generate the deterministic lockfile (uv.lock) :
uv lock

3. Synchronize environment and install package in editable mode :
uv sync

4. Build distribution archives (sdist and wheel into dist/) :
uv build

5. Publish package to repository (replaces twine) :
uv publish --publish-url https://your-repo/legacy/ dist/*

Multiple packages development : uv Workspaces

Workspaces natively replace manual .pth files and pip install -e links.
They maintain a single root .venv and a single unified uv.lock for all sub-packages in the monorepo.

Root pyproject.toml :

[project]
name = "monorepo-root"
version = "0.1.0"
requires-python = "&gt;=3.10"
 
[tool.uv.workspace]
members = ["libs/*", "services/*"]

Sub-package services/service-lib/pyproject.toml consuming local models-lib :

[project]
name = "service-lib"
version = "0.1.0"
dependencies = [
    "models-lib",
]
 
[tool.uv.sources]
models-lib = { workspace = true }
 
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

Synchronize the whole workspace :
uv sync --all-packages
All local packages are cross-linked in editable mode inside the root .venv automatically.

Global CLI tools & single-file execution

Install a CLI tool globally in an isolated environment (replaces pipx) :
uv tool install ruff
uv tool install black
uv tool install twine

List installed global tools :
uv tool list

Update or remove global tools :
uv tool update --all
uv tool uninstall ruff

Run an ephemeral tool without installing it (uvx) :
uvx ruff check .
uvx black --check .

Network, proxy and cache management

Use a proxy and custom / trusted index :
uv pip install requests --proxy http://user:pass@myproxy:8080 --default-index https://pypi.org/simple --allow-insecure-host my-internal-host

Clean and prune uv global cache :
uv cache prune (removes outdated / unreferenced packages)
uv cache clean (clears entire cache directory)

Ce contenu a été publié dans Non classé. Vous pouvez le mettre en favoris avec ce permalien.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *