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 = ">=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 = ">=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)