Overview

Use uv for all Python dependency and environment management. It replaces pip, pip-tools, virtualenv, and venv with a single fast tool that understands pyproject.toml natively. This page is about the day-to-day workflow; for package structure and wheel building see python-packaging.

Use uv instead of pip and venv

uv installs packages, creates virtualenvs, and maintains lockfiles. It is 10-100x faster than pip for resolution, uses the system Rust binary with no Python dependency at install time, and produces reproducible installs via uv.lock.

# Create a venv and install all project deps (including dev)
uv sync --all-extras
 
# Add a new runtime dependency
uv add httpx
 
# Add a dev-only dependency
uv add --dev ruff
 
# Run a command inside the project venv without activating
uv run pytest

Never run pip install inside a project that uses uv. Mix-and-match installs bypass the lockfile and break reproducibility.

Declare extras for different install profiles

[project.optional-dependencies] in pyproject.toml defines named groups. Use at least dev (tools) and test (test runners and libraries).

[project.optional-dependencies]
dev = [
  "ruff>=0.5",
  "pyright>=1.1.370",
  "pre-commit>=3",
]
test = [
  "pytest>=8",
  "pytest-asyncio>=0.23",
  "pytest-cov>=5",
  "hypothesis>=6",
]

CI for linting installs .[dev]. CI for tests installs .[test]. Production Docker images install bare . with no extras. This reduces the attack surface in production and shrinks image sizes.

Commit uv.lock to version control

uv.lock pins every transitive dependency to an exact version and hash. Commit it for applications; omit it for libraries (libraries publish a range in pyproject.toml and let the application resolve).

# Regenerate the lockfile after changing dependencies
uv lock
 
# Install exactly what is in the lockfile (CI, production)
uv sync --frozen

uv sync --frozen fails if uv.lock is out of date, catching cases where a developer added a dependency but forgot to commit the updated lockfile.

Use pipx for global CLI tools

pipx installs Python CLI tools into isolated virtualenvs, one per tool, and adds the executables to PATH. It prevents tool dependencies from leaking into project envs.

# Install tools globally
pipx install ruff
pipx install pyright
pipx install pre-commit
 
# Upgrade all tools at once
pipx upgrade-all

Do not install tools like ruff or pytest into the system Python with pip. Use pipx for tools you run from any directory and uv add --dev for tools that are project-specific.

Audit dependencies for known vulnerabilities

pip-audit scans the installed packages against the Python Packaging Advisory Database. Run it in CI on every build.

uv run pip-audit

Or add it as a CI step that reads the lockfile:

pip-audit -r <(uv export --format requirements-txt)

Fix or pin around vulnerable versions promptly. A patched version that introduces a breaking API change is less risky than shipping a known CVE. See python-security for the full security checklist.

Pin the Python version itself

Set requires-python in pyproject.toml to the minimum version the code actually tests against. Specify the interpreter version in CI explicitly.

[project]
requires-python = ">=3.12"
# .github/workflows/ci.yml
- uses: actions/setup-python@v5
  with:
    python-version: "3.12"

Avoid requires-python = ">=3.8" as a default; each older version you support adds testing cost and blocks newer language features.