UV Development Guide

UV is a fast, modern Python package and project manager that replaces tools like pip, poetry, and virtualenv with a single unified tool. In pntOS-Python, UV manages the workspace containing pntos-api and pntos-cobra packages, handles all dependencies, and builds distributable wheels.

UV offers significant advantages over traditional pip-based workflows:

  • Faster installations through parallel downloads and optimized caching

  • Workspace support for managing multiple related packages

  • Reproducible builds via the uv.lock lock file

  • Simplified tooling with one command for most operations

This guide covers how to use UV for development in this repository, and is intended to build on the concepts discussed in Pyproject Development Guide.

Common UV Workflows

For quick-reference, here are some of the most common uv commands and workflows when working within the pntOS-Python context. The concepts in this section will be expanded upon in subsequent sections.

Quick Reference

Task

Command

Notes

Install all dependencies

uv sync

Installs workspace packages as editable

Install with specific Python

uv sync --python 3.12

Useful for ROS or version testing

Frozen install (CI)

uv sync --frozen

Fails if lock file out of sync

Add dependency

uv add numpy

Updates pyproject.toml and uv.lock

Add dev dependency

uv add --dev pytest

Adds to [dependency-groups]

Remove dependency

uv remove numpy

Also uninstalls if no longer needed

Update all dependencies

uv lock

Respects version constraints

Update specific package

uv lock --upgrade-package numpy

Only updates one package

Build wheel

uv build pntos-api

Creates wheel in dist/ directory

Run command

uv run pytest

Execute without activating venv

Clean cache

uv cache clean

Clear cached packages

Reinstall packages

uv sync --reinstall

Force reinstall all packages

For pip compatibility workflows, see the Contributing Guide.

Detailed Workflows

Basic installation:

uv sync

Reads all workspace pyproject.toml files, resolves dependencies, updates uv.lock, and installs everything into a Python virtual environment (.venv/). For more information, see the Installation Guide.

For specific Python version:

uv sync --python 3.12  # For ROS Jazzy compatibility

In CI/CD (frozen):

uv sync --frozen

Uses exact versions from uv.lock without updating it. Fails if lock file is stale.

Add dependencies:

uv add numpy "matplotlib>=3.5"      # Core dependency
uv add --dev pytest "ruff>=0.1"     # Dev dependency

Automatically updates both pyproject.toml and uv.lock.

Remove dependencies:

uv remove numpy
uv remove --dev pytest

Uninstalls the package if no longer needed by other dependencies.

Note: Workspace member dependencies (pntos-api, pntos-cobra) are automatically installed as editable packages.

Update dependencies:

uv lock                             # Update all to latest compatible
uv lock --upgrade-package numpy     # Update specific package

Always commit uv.lock after updating.

Generate requirements files:

util/generate_requirements.sh
# Or manually:
uv export --frozen --no-hashes -o requirements.txt
uv export --frozen --no-dev --no-hashes -o requirements-minimal.txt

Build wheels:

uv build pntos-api pntos-cobra

Creates wheels in dist/ directory. Requires proper [build-system] configuration (see Build System Configuration).

Without activating environment:

uv run pytest
uv run python my_script.py

Best for one-off commands and CI/CD.

With activated environment:

To activate a Python virtual environment:

source .venv/bin/activate
source .venv/bin/activate.fish

Once activated, simply run commands inside the virtual environment:
pytest
python my_script.py

Best for interactive development with many commands.

Now, let’s dive into greater detail on some important UV concepts.

UV Workspaces

UV workspaces manage multiple related packages in a single repository with a unified lock file. pntOS-Python uses this structure:

  • Root (pntos-python): Meta-package coordinating the workspace

  • Members: pntos-api and pntos-cobra packages

This allows downstream projects to depend on either package individually while letting developers work with both simultaneously.

Configuration

Example workspace configuration from the root pyproject.toml:

[project]
dependencies = [
    "pntos-api",    # The [tool.uv.*] fields below tell uv what to do with these
    "pntos-cobra",
    # Other deps
]

[tool.uv]
package = false  # Meta-package, not distributed

[tool.uv.workspace]
members = ["pntos-api", "pntos-cobra"]

[tool.uv.sources]
pntos-api = { workspace = true }     # Use local version
pntos-cobra = { workspace = true }

There are several benefits to this workspace approach:

Benefit

Description

Unified lock file

One uv.lock ensures consistent versions across all packages

Instant updates

Changes to workspace members immediately available (editable installs)

Shared dependencies

Common packages installed once

Simple commands

Single uv sync for everything in both top-level meta-project (pntos-python) and workspaces (pntos-api, pntos-cobra)

Build System Configuration

To create distributable wheels, configure the build system in each package’s pyproject.toml. Both pntos-api and pntos-cobra use Hatchling. Given this structure:

pntos-python/
├── pyproject.toml
├── pntos-api/
│   ├── pyproject.toml
│   └── src/
│       └── pntos/
│           ├── __init__.py
│           └── api/
│               └── __init__.py
└── pntos-cobra/
    ├── pyproject.toml
    └── src/
        └── pntos/
            ├── __init__.py
            └── cobra/
                └── __init__.py

Here is the build configuration within both workspace pyproject.toml files:

[build-system]
build-backend = "hatchling.build"
requires = ["hatchling"]

[tool.hatch.metadata]
allow-direct-references = true  # Required for Git dependencies

[tool.hatch.build.targets.wheel]
packages = ["src/pntos"]  # Package location for src/ layout

Both packages specify src/pntos as the build directory, placing them in the pntos namespace. If only one workspace is installed downstream, only that module (pntos.api or pntos.cobra) is available. If both are installed, both sub-namespaces exist under pntos.

Custom Package Indexes

UV can use custom package indexes in addition to PyPI for private or organization-specific packages such as a self-hosted package index on a github instance.

Lock File Management

The uv.lock file ensures reproducible, deterministic builds by recording exact versions, sources, and hashes for all dependencies.

Why Lock Files Matter

Without uv.lock

With uv.lock

Different developers get different versions
dependencies = ["numpy>=1.24"] → might get 1.24.0, 1.26.0, or 2.0.0

Everyone gets the exact same version
Everyone gets exactly numpy==1.24.3

“Works on my machine” problems
Hard-to-reproduce CI/CD issues

Consistent environments
Reproducible bugs

Lock File Updates

Automatically updated:

  • uv sync (when pyproject.toml changed)

  • uv add or uv remove

  • uv lock (explicit regeneration)

Not updated:

  • uv sync --frozen (uses existing lock)

  • Editing Python code

Best Practices

Always commit uv.lock to version control to ensure:

  • Team members use identical dependencies

  • CI/CD builds are reproducible

  • Production matches development

Resolving Git conflicts in uv.lock:

Danger

Never manually edit uv.lock to resolve conflicts. Always regenerate it.

# 1. Accept one version (yours or theirs)
git checkout --ours uv.lock     # or --theirs

# 2. Regenerate and verify
uv lock
uv sync
util/generate_requirements.sh

# 3. Commit
git add uv.lock requirements*.txt
git commit

In CI/CD:

  • Use uv sync --frozen for reproducible builds

  • Verify lock file is up-to-date (catches forgotten commits)

  • Fail builds if out of sync

Troubleshooting

Problem

Symptoms

Solutions

Dependency conflicts

error: No solution found when resolving dependencies

• Use less restrictive version constraints (>= instead of ==)
• Run uv lock --upgrade to try newer versions
• Check for conflicting package sources

Python version issues

Can’t find Python version, compatibility errors

• Specify version: uv sync --python 3.12
• Check installation: which python3.10 python3.12
• Loosen requires-python constraint in pyproject.toml

Cache issues

Stale packages, corruption suspected

uv cache clean
• Delete and recreate: rm -rf .venv && uv sync
uv sync --reinstall

Stale lock file

CI fails with “lock file out of sync”

• Regenerate: uv lock
• Sync and commit: uv sync && git add uv.lock requirements*.txt

Build failures

uv build errors, missing dependencies

• Verify [build-system] in pyproject.toml
• Check [tool.hatch.build.targets.wheel] points to correct package
• Enable allow-direct-references = true for Git deps
• Run uv sync first

Workspace issues

Members not recognized, changes not reflected

• Verify workspace config in root pyproject.toml
• Check member directories have valid pyproject.toml
• Reinstall: uv sync --reinstall-package pntos-api
• Ensure package names match across configs

Performance issues

Commands unusually slow

• Check cache is enabled: uv cache dir
• Verify network connectivity
• Use --offline for repeated installs

Additional Resources: