CI/CD Modernisation: Fully uv-native Workflows #551
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ===================================================================== | |
| # Main CI Workflow (Fully uv-native) | |
| # | |
| # Goals: | |
| # - No setup-python | |
| # - No system Python usage | |
| # - All interpreters provisioned via uv | |
| # - All execution via uv run | |
| # - Deterministic environments | |
| # - Explicit Python pinning per matrix job | |
| # | |
| # This workflow validates: | |
| # - Code quality via pre-commit | |
| # - Core functionality without soft dependencies | |
| # - Full functionality with extras | |
| # - Coverage generation | |
| # - Notebook execution | |
| # | |
| # Interpreter resolution: | |
| # - Each job installs and pins its own Python version | |
| # - .python-version is ignored in matrix jobs (overridden explicitly) | |
| # | |
| # ===================================================================== | |
| name: pytest | |
| on: | |
| push: | |
| branches: ["main"] | |
| pull_request: | |
| branches: ["main"] | |
| # Prevent overlapping CI runs on same branch/PR | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # ================================================================ | |
| # 1️⃣ CODE QUALITY | |
| # | |
| # Runs pre-commit only on changed files (for PRs). | |
| # Uses uv-managed Python. | |
| # | |
| # We explicitly: | |
| # - install Python via uv | |
| # - pin interpreter | |
| # - run pre-commit through uv | |
| # | |
| # No setup-python. | |
| # ================================================================ | |
| code-quality: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: astral-sh/setup-uv@v7 | |
| with: | |
| enable-cache: true | |
| - name: Determine changed files (PR only) | |
| id: changed-files | |
| run: | | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| CHANGED_FILES=$(git diff --name-only \ | |
| ${{ github.event.pull_request.base.sha }} \ | |
| ${{ github.sha }} | tr '\n' ' ') | |
| else | |
| # On push to main, check entire repository | |
| CHANGED_FILES=$(git ls-files | tr '\n' ' ') | |
| fi | |
| echo "CHANGED_FILES=${CHANGED_FILES}" >> $GITHUB_ENV | |
| - name: Run pre-commit | |
| run: | | |
| if [ -n "$CHANGED_FILES" ]; then | |
| uvx pre-commit run \ | |
| --color always \ | |
| --files $CHANGED_FILES \ | |
| --show-diff-on-failure | |
| else | |
| echo "No changed files to check." | |
| fi | |
| # ================================================================ | |
| # 2️⃣ CORE TESTS (No Soft Dependencies) | |
| # | |
| # Tests package with only base + dev dependencies. | |
| # | |
| # For each Python version + OS: | |
| # - Install Python via uv | |
| # - Pin interpreter | |
| # - Install dependencies via uv | |
| # - Execute via uv run | |
| # | |
| # Ensures: | |
| # - minimal dependency footprint works | |
| # - no accidental hard dependency on extras | |
| # ================================================================ | |
| pytest-nosoftdeps: | |
| needs: code-quality | |
| name: nosoftdeps (${{ matrix.python-version }}, ${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| env: | |
| MPLBACKEND: Agg | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: astral-sh/setup-uv@v7 | |
| with: | |
| enable-cache: true | |
| - name: Install and pin Python ${{ matrix.python-version }} | |
| run: | | |
| uv python install ${{ matrix.python-version }} | |
| uv python pin ${{ matrix.python-version }} | |
| uv run python --version | |
| - name: Install base + dev dependencies | |
| run: | | |
| uv venv --clear | |
| uv pip install ".[dev]" | |
| - name: Verify dependency graph | |
| run: uv pip check | |
| - name: Run pytest (core only) | |
| run: uv run pytest ./tests | |
| # ================================================================ | |
| # 3️⃣ FULL TEST MATRIX (All Extras) | |
| # | |
| # Same as above but includes optional extras. | |
| # | |
| # Validates: | |
| # - optional dependency combinations | |
| # - platform compatibility | |
| # ================================================================ | |
| pytest: | |
| needs: pytest-nosoftdeps | |
| name: full (${{ matrix.python-version }}, ${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| env: | |
| MPLBACKEND: Agg | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: astral-sh/setup-uv@v7 | |
| with: | |
| enable-cache: true | |
| - name: Install and pin Python ${{ matrix.python-version }} | |
| run: | | |
| uv python install ${{ matrix.python-version }} | |
| uv python pin ${{ matrix.python-version }} | |
| uv run python --version | |
| - name: Install full dependency set | |
| run: | | |
| uv venv --clear | |
| uv pip install ".[dev,all_extras]" | |
| - name: Verify dependency graph | |
| run: uv pip check | |
| - name: Run full pytest suite | |
| run: uv run pytest ./tests | |
| # ================================================================ | |
| # 4️⃣ COVERAGE (Single Reference Interpreter) | |
| # | |
| # We run coverage only once (e.g., Python 3.12) | |
| # to reduce CI cost. | |
| # | |
| # Coverage executed via uv-managed environment. | |
| # ================================================================ | |
| codecov: | |
| name: coverage (3.12 on ubuntu) | |
| runs-on: ubuntu-latest | |
| needs: code-quality | |
| env: | |
| MPLBACKEND: Agg | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: astral-sh/setup-uv@v7 | |
| with: | |
| enable-cache: true | |
| # this is somewhat obsolete as we have .python-version... | |
| - name: Install and pin Python 3.12 | |
| run: | | |
| uv python install 3.12 | |
| uv python pin 3.12 | |
| - name: Install dependencies | |
| run: | | |
| uv venv --clear | |
| uv pip install ".[dev,all_extras]" | |
| uv pip install pytest-cov | |
| - name: Generate coverage report | |
| run: | | |
| uv run pytest \ | |
| --cov=./ \ | |
| --cov-report=xml | |
| # Upload currently disabled | |
| - name: Upload coverage to Codecov | |
| if: false | |
| uses: codecov/codecov-action@v5 | |
| with: | |
| files: ./coverage.xml | |
| fail_ci_if_error: true | |
| # ================================================================ | |
| # 5️⃣ NOTEBOOK TESTING | |
| # | |
| # Executes Jupyter notebooks via nbmake. | |
| # | |
| # For each Python version: | |
| # - Install interpreter via uv | |
| # - Install notebook test dependencies | |
| # - Discover notebooks dynamically | |
| # - Execute via uv run | |
| # | |
| # Ensures: | |
| # - Documentation stays executable | |
| # - No silent drift between code and notebooks | |
| # ================================================================ | |
| notebooks: | |
| needs: code-quality | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: astral-sh/setup-uv@v7 | |
| with: | |
| enable-cache: true | |
| - name: Install and pin Python ${{ matrix.python-version }} | |
| run: | | |
| uv python install ${{ matrix.python-version }} | |
| uv python pin ${{ matrix.python-version }} | |
| uv run python --version | |
| - name: Install notebook dependencies | |
| run: | | |
| uv venv --clear | |
| uv pip install ".[dev,all_extras,notebook_test]" | |
| - name: Collect notebooks | |
| id: notebooks | |
| run: | | |
| NOTEBOOKS=$(find cookbook -name '*.ipynb' -print0 | xargs -0 echo) | |
| echo "notebooks=$NOTEBOOKS" >> $GITHUB_OUTPUT | |
| - name: Execute notebooks | |
| run: | | |
| uv run pytest \ | |
| --reruns 3 \ | |
| --nbmake \ | |
| --nbmake-timeout=3600 \ | |
| -vv \ | |
| ${{ steps.notebooks.outputs.notebooks }} |