Composite GitHub Action for setting up Python environments with the uv package manager, designed for modern Python projects using pyproject.toml and dependency groups.
- Overview
- Quick Start
- Inputs Reference
- Features
- Usage Scenarios
- Dependency Groups
- Lock File Verification
- Virtual Environment
- Testing Guide
- Troubleshooting
- Best Practices
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
steps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
# Virtual environment is automatically activated!
- run: python --version
- run: pyteststeps:
- uses: actions/checkout@v5
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
install-groups: 'dev test'
- run: pytest
- run: black --check .| 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' |
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)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.
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.
The action automatically activates the .venv virtual environment by:
- Adding
.venv/bin(Linux/macOS) or.venv/Scripts(Windows) to$GITHUB_PATH - Setting
$VIRTUAL_ENVenvironment 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 blackThe 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:
requestsonly
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)
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 outdatedSkip verification (useful for dynamic dependency updates):
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
verify-lock: 'false'The action uses astral-sh/setup-uv@v4 with:
enable-cache: true
cache-dependency-glob: uv.lockThis caches dependencies based on uv.lock hash, significantly speeding up CI runs.
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.
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.pypyproject.toml:
[project]
name = "my-app"
version = "1.0.0"
dependencies = ["requests", "pydantic"]What gets installed: requests, pydantic only
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:
[project]
dependencies = ["requests"]
[dependency-groups]
dev = ["black", "mypy", "ruff"]What gets installed: requests + black + mypy + ruff
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 reportpyproject.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)
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 --forcepyproject.toml:
[project]
dependencies = ["mylib"]
[dependency-groups]
docs = ["mkdocs", "mkdocs-material"]What gets installed: mylib + mkdocs + mkdocs-material
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=srcpyproject.toml:
[project]
dependencies = ["requests"]
[dependency-groups]
dev = ["black", "mypy"]
test = ["pytest", "pytest-cov"]
docs = ["mkdocs"] # Not installedWhat gets installed: requests + black + mypy + pytest + pytest-cov
What's excluded: mkdocs (docs group not requested)
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: pytestResult: Tests run on Python 3.10, 3.11, and 3.12
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: pytestResult: Tests run on Linux, Windows, and macOS
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: pytestWhat Happens:
- Skips
uv lock --checkvalidation - Installs dependencies from lock file as-is
- Useful when lock file changes frequently
Use Case: Simple project without optional dependency groups.
pyproject.toml:
[project]
name = "simple-app"
dependencies = ["requests", "click"]
# No [dependency-groups] sectionWorkflow:
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.pyWhat Happens:
- Core dependencies installed:
requests,click - No error even though
[dependency-groups]section doesn't exist - Action handles missing dependency-groups gracefully
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"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=srcWorkflow (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 buildWorkflow (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 appUse 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"
pytestWhat Happens:
- Runs
uv lock --checkbefore installation - Fails if lock file is outdated with clear error message
- Prevents deploying with mismatched dependencies
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 .
pytestWhat Happens:
- Core:
fastapi,uvicorn - Dev group:
pytest,black,mypy,ruff,httpx - All dev tools available for comprehensive CI checks
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:appWhat Happens:
- Only core dependencies installed
- No dev/test tools (smaller image, faster deployment)
- Production-ready minimal environment
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.lockhash - Cache invalidation: Automatic when
uv.lockchanges
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.
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)
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 |
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):
uv sync --group test # Might also install 'dev' group unexpectedlyWith --no-default-groups (current behavior):
uv sync --no-default-groups --group test # ONLY test groupThe action accepts multiple formats for install-groups:
Space-separated:
install-groups: 'groups: dev test docs'Comma-separated:
install-groups: 'groups: dev,test,docs'Mixed (spaces and commas):
install-groups: 'groups: dev, test docs'Groups and extras together:
install-groups: 'groups: dev test, extras: aws viz'Reverse order (extras first):
install-groups: 'extras: aws, groups: dev'All formats produce the same result - the action parses them intelligently.
Development:
[dependency-groups]
dev = ["black>=23.0", "mypy>=1.0", "ruff>=0.1", "ipython>=8.0"]- uses: .../actions/python-setup/uv@v1
with:
install-groups: 'groups: dev'
- run: black --check .
- run: mypy src/Testing:
[dependency-groups]
test = ["pytest>=7.0", "pytest-cov>=4.0", "pytest-mock>=3.0", "faker>=20.0"]- uses: .../actions/python-setup/uv@v1
with:
install-groups: 'groups: test'
- run: pytest --cov=srcDocumentation:
[dependency-groups]
docs = ["mkdocs>=1.5", "mkdocs-material>=9.0", "mkdocstrings[python]>=0.24"]- uses: .../actions/python-setup/uv@v1
with:
install-groups: 'groups: docs'
- run: mkdocs buildProduction (extras for end-users):
[project.optional-dependencies]
postgres = ["psycopg2-binary>=2.9"]
redis = ["redis>=5.0"]- uses: .../actions/python-setup/uv@v1
with:
install-groups: 'extras: postgres redis'
- run: python app.pyCombined (dev + testing):
- uses: .../actions/python-setup/uv@v1
with:
install-groups: 'groups: dev test'
- run: black --check .
- run: pytestAll groups:
- uses: .../actions/python-setup/uv@v1
with:
install-groups: 'groups: dev test docs, extras: aws azure'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.
uv.lock is a lock file that pins exact versions of all dependencies. Verification ensures the lock file is synchronized with pyproject.toml.
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 outdatedError if outdated:
error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided.
Fix: Run uv lock locally and commit the updated lock file.
Disable verification (verify-lock: 'false') when:
- Dependencies change frequently (Git dependencies)
- Development branches with experimental changes
- Prototyping/testing new dependencies
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
verify-lock: 'false'Recommended workflow:
- Modify
pyproject.toml(add/update dependencies) - Run
uv locklocally - Commit both
pyproject.tomlanduv.lock - CI runs with
verify-lock: 'true'and passes
The action automatically activates the virtual environment created by uv sync at .venv.
How it works:
- Action runs
uv syncwhich creates.venv/ - Action adds
.venv/bin(or.venv/Scriptson Windows) to$GITHUB_PATH - Action sets
$VIRTUAL_ENVenvironment variable - All subsequent steps use the activated 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):
- run: uv run pytest
- run: uv run black .Manual activation (unnecessary but possible):
- run: |
source .venv/bin/activate # Linux/macOS
python --versionThe virtual environment is always at:
- Linux/macOS:
$(pwd)/.venv - Windows:
$(pwd)\.venv
Python executable:
- Linux/macOS:
.venv/bin/python - Windows:
.venv\Scripts\python.exe
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 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 |
Default Behavior Test (test-uv-basic):
- Validates: Core dependencies installed, optional groups excluded by default
- Key assertion:
httpxfrom dev group should NOT be present - Purpose: Verify
--no-default-groupsprevents automatic group installation
Custom Groups Test (test-uv-custom-groups):
- Validates: Only requested groups (
test,docs) installed - Key assertion:
httpxfrom dev group should NOT be present - Purpose: Verify group isolation and
--no-default-groupsbehavior
Lock Verification Test (test-uv-lock-verification):
- Validates:
uv lock --checkpasses with synchronized lock file - Purpose: Ensure lock file validation works correctly
Lock Verification Failure Test (test-uv-lock-verification-fail):
- Setup: Modifies
pyproject.tomlafter 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
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 lockCustom 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 lockUsing 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"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 PATHVIRTUAL_ENVenvironment variable set- Subsequent steps can use
pythonand CLI tools directly
Cache Behavior:
- First run: Downloads and caches dependencies
- Subsequent runs: Restores from cache based on
uv.lockhash - Cache invalidates automatically when lock file changes
Verify Python and uv installation:
- name: Debug environment
run: |
python --version
uv --version
which python
which uvCheck installed packages:
- name: List packages
run: |
uv pip list
python -c "import sys; print(sys.path)"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:
- name: Check venv
run: |
echo "VIRTUAL_ENV: $VIRTUAL_ENV"
echo "PATH: $PATH"
ls -la .venv/Check lock file:
- name: Verify lock
run: |
cat pyproject.toml
ls -la uv.lock
uv lock --checkPositive assertion (should be installed):
python -c "import pytest; print('[OK] pytest 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"
fiVersion assertion:
python -c "import sys; assert sys.version_info[:2] == (3, 12), 'Wrong Python version'"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 configurationCause: 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 pushOr disable verification temporarily:
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
verify-lock: 'false'Cause: Lock file doesn't exist in repository.
Solution:
# Generate lock file
uv lock
# Commit it
git add uv.lock
git commit -m "Add uv.lock file"
git pushIssue: Action installs Python 3.12 but need 3.10.
Solution: Specify version explicitly:
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
python-version: '3.10'Issue: ModuleNotFoundError even after installation.
Possible causes:
-
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
# Package: "pyyaml", Import: "yaml" dependencies = ["pyyaml"]
import yaml # Not: import pyyaml
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:
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1 # LatestIssue: python: command not found or wrong Python version.
Verification:
- run: |
echo "Python: $(which python)"
echo "PATH: $PATH"
echo "VIRTUAL_ENV: $VIRTUAL_ENV"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.
Symptoms: Dependencies reinstall on every run.
Debug:
- Check
uv.lockexists and is committed - Verify cache hit in action logs:
Restored cache from key: setup-uv-... - Check lock file hasn't changed between runs
Force cache refresh:
Change uv.lock content (update dependencies).
# 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.
# Good: Explicit version
python-version: '3.11'
# Avoid: Version ranges or latest
python-version: '3.x' # Too broad# 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# Good: Catch dependency drift early
- uses: serapeum-org/github-actions/actions/python-setup/uv@v1
with:
verify-lock: 'true' # Default, but explicit is clear# 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# 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 buildstrategy:
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 }}| 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-pythonalone: Minimal setup, no uv neededastral-sh/setup-uvalone: Need full control over uv commands
For issues, questions, or contributions: