uv: Python Project Environments and Dependencies
For an ordinary repository whose dependencies are available through Python package indexes and suitable wheels, a clear default is:
uv + pyproject.toml + uv.lock + repository-local .venv
This does not make Conda obsolete. uv primarily owns Python projects, Python versions, dependency resolution, and command execution. Conda can also own native libraries, non-Python executables, and cross-language environments. Give each environment one explicit owner; do not let pip, uv, and Conda alternately mutate it.
Choose by Boundary
| Condition | Starting choice | Reason |
|---|---|---|
| Application, CLI, script, or service with suitable wheels | uv | declaration, lock, environment, and commands stay project-local |
| Python extensions have target-platform wheels | test a uv shadow environment | installability does not prove runtime parity |
| CUDA, BLAS, GDAL, GEOS, TA-Lib, or another native tool must come from Conda | retain Conda | uv does not replace system or cross-language dependencies |
| Production automation names a Conda environment | retain the reference and validate in parallel | migration must not silently change the runtime path |
| Sources, licenses, or target platforms are unclear | defer | first establish inventory and acceptance criteria |
A long conda list is not a direct-dependency list. It contains transitive packages, native runtimes, and Conda infrastructure. Reconstruct intent from pyproject.toml, requirements, source imports, entry points, tests, and deployment commands.
Project Model
pyproject.toml intended Python range and direct dependencies
uv.lock exact dependency graph resolved by uv
.python-version optional project Python selection
.venv/ local installation; ignored by Git
Commit uv.lock; do not edit it manually. A lock captures resolution, not trust, safety, licensing, or deployment fitness.
Start a Project
uv python install 3.12
uv init example-app
cd example-app
uv python pin 3.12
uv add requests
uv add --dev pytest
uv sync
uv run python -m pytest
For an internal script collection that is not installed as a package:
[project]
name = "example-tool"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[tool.uv]
package = false
package = false changes installation behavior; it does not remove the need for tests, versions, and dependency declarations.
Adopt an Existing Repository
- Read existing project metadata, requirements, entry points, CI, and production launch paths.
- Declare only dependencies directly required by code and runtime paths; do not copy an installed environment wholesale.
- If
pyproject.tomlexists, start withuv lockanduv sync; migrate requirements against the official guide. - Keep the old environment and build a repository-local
.venvas a shadow. - Run compilation, unit/integration tests, and representative read-only workloads.
- For numerical or data workloads, compare results, backends, threading, and performance—not only imports.
- Switch CI, schedulers, services, or editors last, with an explicit rollback target.
Do not combine an environment-manager migration with a Python upgrade, broad dependency refresh, or application refactor; that destroys causal diagnosis.
Everyday Commands
# Restore and execute
uv sync
uv run python --version
uv run python script.py
uv run python -m unittest discover -s tests
uv pip check
# Change declarations deliberately
uv add pandas
uv add --dev pytest
uv remove pandas
uv tree
# Require an unchanged lock
uv lock --check
uv sync --locked
uv run --locked python -m pytest
# Run a tool without adding it to project dependencies
uvx ruff check .
By default, uv sync updates the lock when project metadata requires it. CI and release paths can use --locked so stale metadata fails instead of silently producing another resolution. --frozen uses the existing lock without checking consistency; use it only when that distinction is intentional.
Prefer uv add/remove over manual installation into .venv. The environment is disposable; pyproject.toml and uv.lock hold project state.
Coexist with Conda
Coexistence should happen at the repository level, not through two resolvers owning one environment:
- one project may use uv while another retains Conda;
- one project may temporarily keep a Conda reference beside an independent uv shadow;
- if Conda owns a native runtime, document the ownership and complete command path;
- successful
uv syncis not permission to delete the reference environment or change production.
Predeclare cutover criteria: tests pass, representative outputs agree, dependency checks are clean, target-platform installation succeeds, runtime behavior is verified, and rollback is rehearsed.
Common Failures
| Symptom | Inspect |
|---|---|
| local success but CI re-resolves | committed uv.lock and --locked use |
unexpected Python under uv run | .python-version, requires-python, shell environment |
| installation succeeds but native behavior changes | wheel backend, BLAS/CUDA, system libraries, threads |
| works only after manual activation | launch command fails to express its environment |
| large lock file | normal; distinguish direct declaration from transitive resolution |
| private-index authentication fails | controlled credentials; never put tokens in docs, URLs, or Git |
Privacy Boundary for Public Notes
Public examples use example-app, /path/to/repository, and placeholder services. Repository names, internal domains, usernames, absolute directories, service units, migration status, test counts, and unpublished dependencies belong in private operational records. A public tool guide should preserve reusable decisions and verification methods, not publish a workspace asset inventory.