Python Setup with uv - Complete Guide¶
Composite GitHub Action for setting up Python environments with the uv package manager, designed for modern Python projects using pyproject.toml and dependency groups.
Table of Contents¶
- Overview
- Quick Start
- Inputs Reference
- Features
- Usage Scenarios
- Dependency Groups
- Lock File Verification
- Virtual Environment
- Testing Guide
- Troubleshooting
- Best Practices
Overview¶
This action provides a complete Python environment setup using uv, the fast Python package installer and resolver. It automatically:
- Installs Python and uv
- Verifies lock file integrity (optional)
- Installs dependencies with group support
- Activates the virtual environment automatically
- Caches dependencies for faster CI runs
Location: serapeum-org/github-actions/actions/python-setup/uv@v1
Quick Start¶
Basic Usage (Core Dependencies Only)¶
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
# Virtual environment is automatically activated!
- run: python --version
- run: pytest
With Dependency Groups¶
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
install-groups: 'dev test'
- run: pytest
- run: black --check .
Inputs Reference¶
| Input | Description | Required | Default | Valid Values |
|---|---|---|---|---|
python-version |
Python version to install | No | '3.12' |
Any valid Python version (e.g., '3.10', '3.11', '3.12') |
install-groups |
Dependency groups to install | No | '' (core only) |
Space or comma-separated list (e.g., 'dev', 'dev test', 'dev,test,docs') |
verify-lock |
Verify lock file is up to date | No | 'true' |
'true', 'false' |
Input Details¶
python-version¶
Specifies which Python version to install via actions/setup-python@v6.
Examples:
python-version: '3.10' # Python 3.10
python-version: '3.11' # Python 3.11
python-version: '3.12' # Python 3.12 (default)
install-groups¶
Specifies which dependency groups from [dependency-groups] in pyproject.toml to install.
Default behavior (''): Installs only core dependencies (those listed in dependencies), no optional groups.
When specified: Installs core dependencies + only the specified groups (all other groups are excluded).
Formats supported:
- Space-separated: 'dev test'
- Comma-separated: 'dev,test,docs'
- Mixed: 'dev, test docs'
Important: The action uses uv sync --no-default-groups --group <name> to ensure ONLY the specified groups are installed, preventing unwanted transitive group installations.
verify-lock¶
Controls whether to verify the uv.lock file is up to date before installation.
When 'true' (default): Runs uv lock --check and fails if lock file is outdated.
**When 'false': Skips lock file verification.
Features¶
1. Automatic Virtual Environment Activation¶
The action automatically activates the .venv virtual environment by:
- Adding .venv/bin (Linux/macOS) or .venv/Scripts (Windows) to $GITHUB_PATH
- Setting $VIRTUAL_ENV environment variable
Result: All subsequent steps can use python, pytest, black, etc. directly without manual activation or uv run.
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
install-groups: 'dev test'
# No activation needed!
- run: python --version # Uses venv Python
- run: pytest # Uses venv pytest
- run: black . # Uses venv black
2. Smart Dependency Group Management¶
The action uses --no-default-groups flag to ensure clean group isolation:
# pyproject.toml
[project]
dependencies = ["requests"]
[dependency-groups]
dev = ["httpx"]
test = ["pytest-cov"]
docs = ["mkdocs"]
Without groups (install-groups: ''):
- Command: uv sync --frozen --no-default-groups
- Installs: requests only
With specific groups (install-groups: 'test docs'):
- Command: uv sync --frozen --no-default-groups --group test --group docs
- Installs: requests + pytest-cov + mkdocs
- Excludes: httpx (dev group not requested)
3. Lock File Verification¶
Ensures reproducible builds by validating uv.lock matches pyproject.toml:
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
verify-lock: 'true' # Fails if lock is outdated
Skip verification (useful for dynamic dependency updates):
4. Dependency Caching¶
The action uses astral-sh/setup-uv@v4 with:
This caches dependencies based on uv.lock hash, significantly speeding up CI runs.
5. Comprehensive Logging¶
The action provides detailed output:
Environment information
Virtual environment: ACTIVATED
Virtual environment location:
/home/runner/work/repo/repo/.venv
Python executable:
/home/runner/work/repo/repo/.venv/bin/python
The virtual environment has been automatically activated.
You can now use 'python' and installed CLI tools directly in subsequent steps.
Usage Scenarios¶
Scenario 1: Core Dependencies Only¶
Use Case: Simple project with only core dependencies, no dev tools needed.
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
- name: Run application
run: python main.py
pyproject.toml:
What gets installed: requests, pydantic only
Scenario 2: Development Environment¶
Use Case: Local-style development with all dev tools.
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
install-groups: 'dev'
- run: black --check .
- run: mypy src/
- run: ruff check .
pyproject.toml:
What gets installed: requests + black + mypy + ruff
Scenario 3: Testing Workflow¶
Use Case: Run tests without dev tools to match production environment.
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
install-groups: 'groups: test'
- run: pytest --cov=src --cov-report=xml
- run: coverage report
pyproject.toml:
[project]
dependencies = ["requests"]
[dependency-groups]
dev = ["black", "mypy"]
test = ["pytest", "pytest-cov", "coverage"]
What gets installed: requests + pytest + pytest-cov + coverage (dev tools excluded)
Scenario 4: Documentation Build¶
Use Case: Build documentation without test/dev dependencies.
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
install-groups: 'docs'
- run: mkdocs build
- run: mkdocs gh-deploy --force
pyproject.toml:
What gets installed: mylib + mkdocs + mkdocs-material
Scenario 5: Multiple Groups¶
Use Case: CI workflow that needs both testing and linting.
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
install-groups: 'dev test'
- name: Lint
run: |
black --check .
mypy src/
- name: Test
run: pytest --cov=src
pyproject.toml:
[project]
dependencies = ["requests"]
[dependency-groups]
dev = ["black", "mypy"]
test = ["pytest", "pytest-cov"]
docs = ["mkdocs"] # Not installed
What gets installed: requests + black + mypy + pytest + pytest-cov
What's excluded: mkdocs (docs group not requested)
Scenario 6: Matrix Testing Across Python Versions¶
Use Case: Test compatibility with multiple Python versions.
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
python-version: ${{ matrix.python-version }}
install-groups: 'groups: test'
- run: pytest
Result: Tests run on Python 3.10, 3.11, and 3.12
Scenario 7: Cross-Platform Testing¶
Use Case: Ensure application works on all major operating systems.
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
install-groups: 'test'
- run: pytest
Result: Tests run on Linux, Windows, and macOS
Scenario 8: Skip Lock Verification for Dynamic Dependencies¶
Use Case: Dependencies from Git branches or local paths that change frequently.
pyproject.toml:
[project]
dependencies = [
"mylib @ git+https://github.com/user/repo@main",
"another-lib @ git+https://github.com/user/another@develop"
]
Workflow:
jobs:
test-dynamic:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
verify-lock: 'false' # Skip lock check for dynamic deps
install-groups: 'groups: dev'
- name: Run tests
run: pytest
What Happens:
- Skips uv lock --check validation
- Installs dependencies from lock file as-is
- Useful when lock file changes frequently
Scenario 9: No Dependency Groups Section¶
Use Case: Simple project without optional dependency groups.
pyproject.toml:
Workflow:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
- name: Run application
run: python app.py
What Happens:
- Core dependencies installed: requests, click
- No error even though [dependency-groups] section doesn't exist
- Action handles missing dependency-groups gracefully
Scenario 10: Multiple Formats for Groups¶
Use Case: Demonstrating flexible group specification formats.
All these are equivalent:
# Space-separated
install-groups: 'groups: dev test docs'
# Comma-separated
install-groups: 'groups: dev,test,docs'
# Mixed
install-groups: 'groups: dev, test docs'
Workflow:
jobs:
test-formats:
runs-on: ubuntu-latest
strategy:
matrix:
format:
- 'groups: dev test docs'
- 'groups: dev,test,docs'
- 'groups: dev, test docs'
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
install-groups: ${{ matrix.format }}
- name: Verify all formats work
run: |
python -c "import black, pytest, mkdocs"
echo "[OK] All formats install same dependencies"
Scenario 11: Complex Multi-Group Setup¶
Use Case: Large project with many specialized dependency groups.
pyproject.toml:
[project]
name = "enterprise-app"
dependencies = ["requests", "pydantic", "typer"]
[project.optional-dependencies]
aws = ["boto3", "s3fs"]
azure = ["azure-storage-blob", "azure-identity"]
gcp = ["google-cloud-storage"]
postgres = ["psycopg2-binary", "sqlalchemy"]
redis = ["redis", "hiredis"]
[dependency-groups]
dev = ["black", "mypy", "ruff", "ipython"]
test = ["pytest", "pytest-cov", "pytest-mock", "hypothesis"]
docs = ["mkdocs", "mkdocs-material", "mkdocstrings[python]"]
lint = ["pylint", "flake8", "bandit"]
build = ["build", "twine", "wheel"]
Workflow (development):
jobs:
develop:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
install-groups: 'groups: dev test lint, extras: postgres redis'
- name: Full dev environment
run: |
black --check .
mypy src/
pytest --cov=src
Workflow (documentation):
jobs:
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
install-groups: 'groups: docs'
- name: Build docs
run: mkdocs build
Workflow (production):
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
install-groups: 'extras: aws postgres' # No dev groups
- name: Deploy app
run: python -m app
Scenario 12: Testing Lock File Validation¶
Use Case: Ensuring lock file stays in sync with pyproject.toml.
Workflow:
jobs:
validate-lock:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Verify lock file is up to date
uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
verify-lock: 'true' # Default, but explicit
install-groups: 'groups: dev'
- name: Run checks
run: |
echo "Lock file is valid and up to date"
pytest
What Happens:
- Runs uv lock --check before installation
- Fails if lock file is outdated with clear error message
- Prevents deploying with mismatched dependencies
Scenario 13: Explicit Dev Group Installation¶
Use Case: Ensuring dev tools are available for local-style development in CI.
pyproject.toml:
[project]
name = "myapp"
dependencies = ["fastapi", "uvicorn"]
[dependency-groups]
dev = ["pytest", "black", "mypy", "ruff", "httpx"]
Workflow:
jobs:
dev-ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
install-groups: 'groups: dev'
- name: Run all dev checks
run: |
black --check .
mypy src/
ruff check .
pytest
What Happens:
- Core: fastapi, uvicorn
- Dev group: pytest, black, mypy, ruff, httpx
- All dev tools available for comprehensive CI checks
Scenario 14: Minimal Production Build¶
Use Case: Production deployment with only runtime dependencies.
pyproject.toml:
[project]
name = "webapp"
dependencies = ["flask", "gunicorn", "psycopg2"]
[dependency-groups]
dev = ["pytest", "black"]
test = ["pytest-cov", "faker"]
Workflow:
jobs:
production:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
# No install-groups = core only
- name: Verify minimal install
run: |
uv pip list
python -c "import flask, gunicorn, psycopg2"
echo "[OK] Only production dependencies installed"
- name: Deploy
run: gunicorn app:app
What Happens: - Only core dependencies installed - No dev/test tools (smaller image, faster deployment) - Production-ready minimal environment
Scenario 15: Cache Behavior Testing¶
Use Case: Understanding and testing caching behavior.
Workflow:
jobs:
cache-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: First run (populate cache)
uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
cache: 'true' # Default
install-groups: 'groups: dev test'
- name: Verify packages
run: uv pip list
# On subsequent runs:
# - Cache restored from uv.lock hash
# - Much faster (seconds vs minutes)
Cache behavior:
- First run: Downloads and caches all dependencies (~2-5 minutes)
- Subsequent runs: Restores from cache (~10-30 seconds)
- Cache key: Based on uv.lock hash
- Cache invalidation: Automatic when uv.lock changes
Dependency Groups¶
Dependency Groups vs Optional Dependencies¶
Key Differences:
- Dependency Groups (
[dependency-groups]): - Part of PEP 735 standard
- Fully supported by uv (unlike pip)
- Development-only dependencies, not published with package
- Installed using
--groupflag withuv sync - Use prefix
groups:in this action -
Recommended for uv users
-
Optional Dependencies (
[project.optional-dependencies]): - Part of PEP 621 standard, widely supported
- Published with your package, can be installed by end users
- Installed using
--extraflag withuv sync - Use prefix
extras:in this action - Also supported by uv
Key Advantage of uv: Unlike pip, uv has full native support for PEP 735 dependency groups without any limitations or warnings.
Understanding Dependency Groups¶
Dependency groups in pyproject.toml allow organizing optional dependencies:
[project]
name = "myapp"
dependencies = ["requests"] # Core - always installed
[project.optional-dependencies]
aws = ["boto3", "s3fs"] # Published extras (end-user features)
viz = ["matplotlib", "seaborn"] # End-user features
[dependency-groups]
dev = ["black", "mypy", "ruff"] # Development tools (not published)
test = ["pytest", "pytest-cov"] # Testing tools (not published)
docs = ["mkdocs", "mkdocstrings"] # Documentation tools (not published)
When to use which:
- optional-dependencies: Features for end-users (e.g., cloud integrations, visualization)
- dependency-groups: Development tools only needed by contributors (e.g., linting, testing)
Group Installation Behavior¶
install-groups Value |
Command Generated | What Gets Installed |
|---|---|---|
'' (empty/default) |
uv sync --frozen --no-default-groups |
Core only |
'groups: dev' |
uv sync --frozen --no-default-groups --group dev |
Core + dev |
'groups: test' |
uv sync --frozen --no-default-groups --group test |
Core + test |
'groups: dev test' |
uv sync --frozen --no-default-groups --group dev --group test |
Core + dev + test |
'groups: dev,test,docs' |
uv sync --frozen --no-default-groups --group dev --group test --group docs |
Core + dev + test + docs |
'extras: aws' |
uv sync --frozen --no-default-groups --extra aws |
Core + aws extra |
'groups: dev, extras: aws' |
uv sync --frozen --no-default-groups --group dev --extra aws |
Core + dev + aws |
Why --no-default-groups?¶
By default, uv sync includes certain groups automatically (like dev). Using --no-default-groups ensures:
- ✅ Clean group isolation - only requested groups are installed
- ✅ Predictable builds - same result every time
- ✅ Reproducible - no unexpected transitive group installations
- ✅ Explicit control - you specify exactly what to install
Without --no-default-groups (old behavior):
With --no-default-groups (current behavior):
Format Options¶
The action accepts multiple formats for install-groups:
Space-separated:
Comma-separated:
Mixed (spaces and commas):
Groups and extras together:
Reverse order (extras first):
All formats produce the same result - the action parses them intelligently.
Common Group Patterns¶
Development:
- uses: .../actions/python-setup/uv@v1
with:
install-groups: 'groups: dev'
- run: black --check .
- run: mypy src/
Testing:
Documentation:
Production (extras for end-users):
- uses: .../actions/python-setup/uv@v1
with:
install-groups: 'extras: postgres redis'
- run: python app.py
Combined (dev + testing):
- uses: .../actions/python-setup/uv@v1
with:
install-groups: 'groups: dev test'
- run: black --check .
- run: pytest
All groups:
- uses: .../actions/python-setup/uv@v1
with:
install-groups: 'groups: dev test docs, extras: aws azure'
Installation Summary Output¶
The action provides detailed feedback about what will be installed:
Example output:
Processing install-groups: groups: dev test, extras: aws
- Adding dependency group: dev
- Adding dependency group: test
- Adding optional dependency (extra): aws
=== Installation Summary ===
✓ Dependency groups will be installed: dev test
✓ Optional dependencies (extras) will be installed: aws
✗ No optional dependencies specified
✓ Core dependencies will always be installed
==========================
Running: uv sync --frozen --no-default-groups --group dev --group test --extra aws
This makes it clear exactly what's being installed and why.
Lock File Verification¶
What is Lock File Verification?¶
uv.lock is a lock file that pins exact versions of all dependencies. Verification ensures the lock file is synchronized with pyproject.toml.
When to Enable (Default)¶
Enable verification (verify-lock: 'true') when:
- Working in a team (ensure everyone uses same dependencies)
- Production deployments (reproducible builds)
- CI/CD pipelines (catch dependency drift early)
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
verify-lock: 'true' # Fails if lock is outdated
Error if outdated:
Fix: Run uv lock locally and commit the updated lock file.
When to Disable¶
Disable verification (verify-lock: 'false') when:
- Dependencies change frequently (Git dependencies)
- Development branches with experimental changes
- Prototyping/testing new dependencies
Lock File Workflow¶
Recommended workflow:
1. Modify pyproject.toml (add/update dependencies)
2. Run uv lock locally
3. Commit both pyproject.toml and uv.lock
4. CI runs with verify-lock: 'true' and passes
Virtual Environment¶
Automatic Activation¶
The action automatically activates the virtual environment created by uv sync at .venv.
How it works:
1. Action runs uv sync which creates .venv/
2. Action adds .venv/bin (or .venv/Scripts on Windows) to $GITHUB_PATH
3. Action sets $VIRTUAL_ENV environment variable
4. All subsequent steps use the activated environment
Using the Environment¶
Direct Python/CLI commands (recommended):
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
install-groups: 'dev test'
- run: python --version
- run: pytest
- run: black .
- run: mypy src/
Alternative: uv run (also works):
Manual activation (unnecessary but possible):
Environment Location¶
The virtual environment is always at:
- Linux/macOS: $(pwd)/.venv
- Windows: $(pwd)\.venv
Python executable:
- Linux/macOS: .venv/bin/python
- Windows: .venv\Scripts\python.exe
Testing Guide¶
This action is comprehensively tested across multiple scenarios to ensure reliability and correct behavior. Reference the test workflow at .github/workflows/test-python-setup-uv.yml.
Test Coverage Matrix¶
| Test Job | Purpose | Key Validations | Behavior Tested |
|---|---|---|---|
test-uv-basic |
Default behavior (core only) | Core dependencies installed, dev group NOT installed | --no-default-groups excludes optional groups by default |
test-uv-custom-groups |
Specific groups | Only test and docs groups installed, dev excluded |
Group isolation with --no-default-groups |
test-uv-no-groups |
Explicit empty groups | Only core dependencies, no optional groups | Empty install-groups: '' handled correctly |
test-uv-lock-verification |
Lock validation (valid) | Lock file check passes | uv lock --check succeeds with up-to-date lock |
test-uv-lock-verification-disabled |
Skip lock check | Installation succeeds without verification | verify-lock: 'false' skips validation |
test-uv-lock-verification-fail |
Outdated lock handling | Action fails gracefully with clear error | Outdated lock detected and reported |
test-uv-matrix |
Cross-platform/version | Python 3.10/3.11/3.12 on Linux/Windows/macOS | Platform-specific virtual environment activation |
test-uv-cache |
Dependency caching | Cache populated and restored based on uv.lock hash |
enable-cache: true with cache-dependency-glob |
test-uv-comma-separated-groups |
Group parsing | 'dev,test,docs' format parsed correctly |
Multiple separator handling |
test-uv-mixed-separators |
Group parsing | 'dev, test docs' format parsed correctly |
Flexible formatting support |
test-uv-space-separated-groups |
Group parsing | 'dev test docs' format parsed correctly |
Space-separated groups |
test-uv-no-dependency-groups-section |
Missing groups section | Works without [dependency-groups] in pyproject.toml |
Graceful handling of missing section |
test-uv-explicit-dev-group |
Specific dev group | Only dev group installed when requested | Explicit group selection |
test-uv-groups-and-extras |
Mixed groups/extras | Both groups and extras install correctly | --group and --extra flags together |
test-uv-extras-only |
Extras without groups | Only extras installed, groups excluded | extras: prefix handling |
test-uv-reverse-order |
Order independence | Same result regardless of groups/extras order | Parser handles any order |
Test Scenarios Details¶
Default Behavior Test (test-uv-basic):
- Validates: Core dependencies installed, optional groups excluded by default
- Key assertion: httpx from dev group should NOT be present
- Purpose: Verify --no-default-groups prevents automatic group installation
Custom Groups Test (test-uv-custom-groups):
- Validates: Only requested groups (test, docs) installed
- Key assertion: httpx from dev group should NOT be present
- Purpose: Verify group isolation and --no-default-groups behavior
Lock Verification Test (test-uv-lock-verification):
- Validates: uv lock --check passes with synchronized lock file
- Purpose: Ensure lock file validation works correctly
Lock Verification Failure Test (test-uv-lock-verification-fail):
- Setup: Modifies pyproject.toml after generating lock file
- Validates: Action fails with clear error message
- Purpose: Verify outdated lock detection
Matrix Test (test-uv-matrix):
- Validates: Works across Python 3.10, 3.11, 3.12 on ubuntu, windows, macos
- Purpose: Cross-platform and cross-version compatibility
Format Tests (comma-separated, mixed-separators, space-separated):
- Validates: All group specification formats produce identical results
- Purpose: Flexible input format support
Test Fixtures¶
Tests use inline pyproject.toml creation for maximum clarity:
Basic Test Example:
- name: Create test project with uv
run: |
cat > pyproject.toml << 'EOF'
[project]
name = "test-uv-basic"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["requests"]
[dependency-groups]
dev = ["httpx"]
EOF
pip install uv
uv lock
Custom Groups Test Example:
- name: Create test project with multiple groups
run: |
cat > pyproject.toml << 'EOF'
[project]
name = "test-uv-groups"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["requests"]
[dependency-groups]
dev = ["httpx"]
test = ["pytest-cov", "pytest-mock"]
docs = ["mkdocs"]
EOF
pip install uv
uv lock
Running Tests Locally¶
Using act (GitHub Actions locally):
# Install act
choco install act-cli # Windows
brew install act # macOS
winget install nektos.act # Windows (alternative)
# Run specific test
act -j test-uv-basic
act -j test-uv-custom-groups
act -j test-uv-matrix
# Run all uv tests
act -j test-uv-*
Manual testing in your repository:
# .github/workflows/test-uv-action.yml
name: Test uv action
on: [push, pull_request]
jobs:
test-basic:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
install-groups: 'groups: dev test'
- run: pytest
test-groups-isolation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
install-groups: 'groups: test'
- name: Verify only test group
run: |
python -c "import pytest; print('[OK] Test group installed')"
# Verify dev group NOT installed
python -c "import black" 2>&1 && exit 1 || echo "[OK] Dev group not installed"
test-lock-verification:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
verify-lock: 'true'
- run: echo "Lock file verified"
Expected Test Behaviors¶
Group Isolation:
- When install-groups: 'groups: test' specified, ONLY test group installed
- Dev, docs, and other groups explicitly excluded via --no-default-groups
- Core dependencies always installed
Lock File Verification:
- With verify-lock: 'true': Fails if lock outdated
- With verify-lock: 'false': Uses lock as-is without checking
- Default is 'true' for safety
Virtual Environment Activation:
- .venv/bin (Linux/macOS) or .venv/Scripts (Windows) added to PATH
- VIRTUAL_ENV environment variable set
- Subsequent steps can use python and CLI tools directly
Cache Behavior:
- First run: Downloads and caches dependencies
- Subsequent runs: Restores from cache based on uv.lock hash
- Cache invalidates automatically when lock file changes
Debugging Failed Tests¶
Verify Python and uv installation:
Check installed packages:
Test specific imports:
- name: Test imports
run: |
python -c "import pytest; print('pytest OK')"
python -c "import black; print('black OK')" || echo "black not installed (expected?)"
Verify virtual environment:
Check lock file:
Test Assertions Examples¶
Positive assertion (should be installed):
Negative assertion (should NOT be installed):
if python -c "import httpx" 2>/dev/null; then
echo "[ERROR] httpx should not be installed"
exit 1
else
echo "[OK] httpx not installed as expected"
fi
Version assertion:
Continuous Integration Best Practices¶
Matrix testing:
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
os: [ubuntu-latest, windows-latest, macos-latest]
groups: ['dev', 'test', 'dev test', '']
steps:
- uses: .../actions/python-setup/uv@v1
with:
python-version: ${{ matrix.python-version }}
install-groups: ${{ matrix.groups && format('groups: {0}', matrix.groups) || '' }}
Fail-fast disabled for comprehensive testing:
strategy:
fail-fast: false # Test all combinations even if one fails
matrix:
# ... matrix configuration
Troubleshooting¶
Error: "The lockfile at uv.lock needs to be updated"¶
Cause: Lock file is outdated compared to pyproject.toml.
Solution:
# Update lock file locally
uv lock
# Commit the changes
git add pyproject.toml uv.lock
git commit -m "Update dependencies"
git push
Or disable verification temporarily:
Error: "No such file or directory: uv.lock"¶
Cause: Lock file doesn't exist in repository.
Solution:
Wrong Python Version¶
Issue: Action installs Python 3.12 but need 3.10.
Solution: Specify version explicitly:
Dependency Not Found After Installation¶
Issue: ModuleNotFoundError even after installation.
Possible causes: 1. Group not specified: Dependency is in optional group not requested
# Wrong: dev group has pytest but not installed
install-groups: 'test'
# Fix: Add dev group
install-groups: 'dev test'
- Wrong import name: Package name ≠ import name
Groups Not Isolated¶
Issue: Unwanted packages installed even though group not specified.
Cause: Before the fix, the action used --group which included default groups.
Solution: Use latest version of the action which uses --no-default-groups:
Virtual Environment Not Activated¶
Issue: python: command not found or wrong Python version.
Verification:
Expected output:
Python: /home/runner/work/repo/repo/.venv/bin/python
PATH: /home/runner/work/repo/repo/.venv/bin:...
VIRTUAL_ENV: /home/runner/work/repo/repo/.venv
If not working: Check GitHub Actions runner logs for warnings in "Activate virtual environment" step.
Cache Not Working¶
Symptoms: Dependencies reinstall on every run.
Debug:
1. Check uv.lock exists and is committed
2. Verify cache hit in action logs:
Force cache refresh:
Change uv.lock content (update dependencies).
Best Practices¶
1. Always Commit Lock File¶
# Generate lock file
uv lock
# Always commit both files together
git add pyproject.toml uv.lock
git commit -m "Update dependencies"
Why: Ensures reproducible builds across all environments.
2. Use Specific Python Versions¶
# Good: Explicit version
python-version: '3.11'
# Avoid: Version ranges or latest
python-version: '3.x' # Too broad
3. Organize Dependency Groups by Purpose¶
# Good: Clear separation
[dependency-groups]
dev = ["black", "mypy", "ruff"]
test = ["pytest", "pytest-cov"]
docs = ["mkdocs"]
# Avoid: Single "dev" group for everything
[dependency-groups]
dev = ["black", "mypy", "pytest", "mkdocs"] # Too broad
4. Use Lock Verification in CI¶
# Good: Catch dependency drift early
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
verify-lock: 'true' # Default, but explicit is clear
5. Pin Action Versions¶
# Good: Pin to major version (gets updates)
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
# Good: Pin to exact commit (maximum stability)
- uses: serapeum-org/github-actions/actions/python-setup/uv@abc1234
# Avoid: Using @main (unpredictable)
- uses: serapeum-org/github-actions/actions/python-setup/uv@main
6. Separate Workflows by Purpose¶
# lint.yml
- uses: .../actions/python-setup/uv@v1
with:
install-groups: 'dev'
- run: black --check .
- run: mypy src/
# test.yml
- uses: .../actions/python-setup/uv@v1
with:
install-groups: 'test'
- run: pytest
# docs.yml
- uses: .../actions/python-setup/uv@v1
with:
install-groups: 'docs'
- run: mkdocs build
7. Use Matrix for Multi-Version Testing¶
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: .../actions/python-setup/uv@v1
with:
python-version: ${{ matrix.python-version }}
Comparison with Other Actions¶
| Feature | This Action | actions/setup-python | astral-sh/setup-uv alone |
|---|---|---|---|
| Python Installation | ✓ | ✓ | ✗ (requires setup-python) |
| uv Installation | ✓ | ✗ | ✓ |
| Dependency Installation | ✓ (automatic) | ✗ (manual) | ✗ (manual) |
| Group Support | ✓ (built-in) | ✗ | ✓ (manual) |
| Lock Verification | ✓ (built-in) | ✗ | ✗ (manual) |
| Auto VEnv Activation | ✓ | ✗ | ✗ |
| Dependency Caching | ✓ | ✓ (pip only) | ✓ |
| Group Isolation | ✓ (--no-default-groups) |
N/A | ✗ (manual) |
When to use this action:
- Modern Python projects using pyproject.toml
- Need dependency group support
- Want automatic environment activation
- Prefer uv's speed over pip
When to use alternatives:
- actions/setup-python alone: Minimal setup, no uv needed
- astral-sh/setup-uv alone: Need full control over uv commands
Additional Resources¶
Support¶
For issues, questions, or contributions: - Repository: https://github.com/serapeum-org/github-actions - Issues: https://github.com/serapeum-org/github-actions/issues