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.locklock fileSimplified 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 |
|
Installs workspace packages as editable |
Install with specific Python |
|
Useful for ROS or version testing |
Frozen install (CI) |
|
Fails if lock file out of sync |
Add dependency |
|
Updates |
Add dev dependency |
|
Adds to |
Remove dependency |
|
Also uninstalls if no longer needed |
Update all dependencies |
|
Respects version constraints |
Update specific package |
|
Only updates one package |
Build wheel |
|
Creates wheel in |
Run command |
|
Execute without activating venv |
Clean cache |
|
Clear cached packages |
Reinstall packages |
|
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 workspaceMembers:
pntos-apiandpntos-cobrapackages
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 |
Instant updates |
Changes to workspace members immediately available (editable installs) |
Shared dependencies |
Common packages installed once |
Simple commands |
Single |
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 |
With |
|---|---|
Different developers get different versions |
Everyone gets the exact same version |
“Works on my machine” problems |
Consistent environments |
Lock File Updates
Automatically updated:
uv sync(whenpyproject.tomlchanged)uv addoruv removeuv 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 --frozenfor reproducible buildsVerify lock file is up-to-date (catches forgotten commits)
Fail builds if out of sync
Troubleshooting
Problem |
Symptoms |
Solutions |
|---|---|---|
Dependency conflicts |
|
• Use less restrictive version constraints ( |
Python version issues |
Can’t find Python version, compatibility errors |
• Specify version: |
Cache issues |
Stale packages, corruption suspected |
• |
Stale lock file |
CI fails with “lock file out of sync” |
• Regenerate: |
Build failures |
|
• Verify |
Workspace issues |
Members not recognized, changes not reflected |
• Verify workspace config in root |
Performance issues |
Commands unusually slow |
• Check cache is enabled: |
Additional Resources: