Skip to content
Latchkey

CI workflow (Diaoul/subliminal)

The CI workflow from Diaoul/subliminal, explained and optimized by Latchkey.

C

CI health: C - fair

Point runs-on at Latchkey and get caching, job timeouts, SHA-pinned actions, self-healing for flaky steps, and up to 58% lower cost, applied automatically.

Grade your own workflow free or run it on Latchkey →
Source: Diaoul/subliminal.github/workflows/CI.yamlLicense MITView source

What it does

This is the CI workflow from the Diaoul/subliminal repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
name: CI

on:
  push:
    branches: [main]
  pull_request: {}

permissions: {}

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

env:
  FORCE_COLOR: "1"
  PIP_DISABLE_PIP_VERSION_CHECK: "1"
  PIP_NO_PYTHON_VERSION_WARNING: "1"

jobs:
  check-manifest:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
      - run: pipx run check-manifest --ignore "_version.py"

  pre-commit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
      - uses: actions/setup-python@v6
        with:
          python-version-file: .python-version-default
      - uses: pre-commit/action@v3.0.1

  typing:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
      - uses: actions/setup-python@v6
        with:
          python-version-file: .python-version-default
      - name: Install hatch
        run: python -Im pip install hatch
      - name: Setup types environment
        run: hatch -v env create types
      - name: Type check
        run: hatch run types:run

  docs:
    name: Build docs and run doctests
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
      - uses: actions/setup-python@v6
        with:
          # Keep in sync with tox/docs, hatch.toml and .readthedocs.yaml.
          python-version-file: .python-version-default
          cache: pip

      - name: Install hatch
        run: python -Im pip install hatch
      - name: Build and check docs
        run: hatch run docs:run
      - name: Run doctests
        run: hatch run docs:doctest
      - name: Check changelog
        run: hatch run changelog:run

      - uses: actions/upload-artifact@v7
        with:
          name: docs
          path: docs/_build/

  install-dev:
    name: Verify install env
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
      - uses: actions/setup-python@v6
        with:
          python-version-file: .python-version-default
          cache: pip

      - name: Install in dev mode & import
        run: |
          python -Im pip install -e .[dev]
          python -Ic 'import subliminal; print(subliminal.__version__)'

  build-package:
    name: Build & verify package
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
          fetch-depth: 0

      - uses: hynek/build-and-inspect-python-package@v2
        id: baipp

    outputs:
      # Used to define the matrix for tests below. The value is based on
      # packaging metadata (trove classifiers).
      supported-python-versions: ${{ steps.baipp.outputs.supported_python_classifiers_json_array }}

  prepare-test:
    name: Download data for tests
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
          fetch-depth: 0
      - name: Prepare tests
        uses: ./.github/actions/prepare-tests
      - name: Display downloaded files
        run: ls -R tests/data

  test:
    name: Test
    runs-on: ${{ matrix.os }}
    needs: [build-package, prepare-test]
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        # Created by the build-and-inspect-python-package action above.
        py: ${{ fromJson(needs.build-package.outputs.supported-python-versions) }}
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
      - uses: actions/setup-python@v6
        with:
          python-version: ${{ matrix.py }}
          allow-prereleases: true
      - name: Prepare tests
        uses: ./.github/actions/prepare-tests
      - name: Display downloaded files
        run: ls -R tests/data

      - name: Install hatch
        run: python -Im pip install hatch

      - name: Pick environment to run
        env:
          PYTHON_VERSION: ${{ matrix.py }}
        run: |
          import codecs; import os
          py = f"tests.py{os.environ.get('PYTHON_VERSION')}"
          print(f"Picked {py}")
          with codecs.open(os.environ["GITHUB_ENV"], mode="a", encoding="utf-8") as file_handler:
              file_handler.write(f"FORCE_COLOR=1\nENV={py}\n")
        shell: python
      - name: Setup test environment
        run: |
          hatch -v env create ${ENV}
        shell: bash
      - name: Run test suite
        env:
          # Make sure to add `passenv=COVERAGE_FILE` to `[testenv]` in tox.ini
          # Make sure to add `overrides = { env.COVERAGE_FILE.env-vars = "COVERAGE_FILE" }` to `[tests]` in hatch.toml
          COVERAGE_FILE: ".coverage.${{ matrix.os }}.${{ matrix.py }}"
          CI_RUN: "yes"
        run: hatch -v run ${ENV}:test-cov
        shell: bash

      - name: Store coverage file
        uses: actions/upload-artifact@v7
        with:
          name: coverage-${{ matrix.os }}-${{ matrix.py }}
          path: .coverage*
          include-hidden-files: true
          if-no-files-found: error

  coverage:
    name: Coverage
    runs-on: ubuntu-latest
    needs: test
    permissions:
      pull-requests: write
      contents: write
    steps:
      - uses: actions/checkout@v7
        with:
          # py-cov-action/python-coverage-comment-action needs git credentials
          persist-credentials: true
      - uses: actions/setup-python@v6
        with:
          python-version-file: .python-version-default
      - uses: hynek/setup-cached-uv@v2

      - name: Download coverage data
        uses: actions/download-artifact@v8
        with:
          pattern: coverage-*
          merge-multiple: true

      - name: Combine coverage
        run: |
          uv tool install 'coverage[toml]>7'

          coverage combine
          coverage html --skip-covered --skip-empty

          # Report and write to summary.
          coverage report --skip-covered --skip-empty --show-missing --format=markdown >> $GITHUB_STEP_SUMMARY

      - name: Upload HTML report if check failed.
        uses: actions/upload-artifact@v7
        with:
          name: html-report
          path: htmlcov

      - name: Coverage comment
        id: coverage_comment
        uses: py-cov-action/python-coverage-comment-action@v3
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Store Pull Request comment to be posted
        uses: actions/upload-artifact@v7
        if: steps.coverage_comment.outputs.COMMENT_FILE_WRITTEN == 'true'
        with:
          # If you use a different name, update COMMENT_ARTIFACT_NAME accordingly
          name: python-coverage-comment-action
          # If you use a different name, update COMMENT_FILENAME accordingly
          path: python-coverage-comment-action.txt

  core-coverage:
    name: Core Coverage
    runs-on: ubuntu-latest
    needs: [build-package, prepare-test]
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
      - uses: actions/setup-python@v6
        id: setup
        with:
          python-version-file: .python-version-default
      - name: Prepare tests
        uses: ./.github/actions/prepare-tests
      - name: Display downloaded files
        run: ls -R tests/data
      - name: Install hatch
        run: python -Im pip install hatch

      - name: Pick environment to run
        env:
          PYTHON_VERSION: ${{ steps.setup.outputs.python-version }}
        run: |
          import codecs; import os
          py = os.environ.get('PYTHON_VERSION')
          py = ".".join(py.split(".")[:2])
          test_env = f"tests.py{py}"
          print(f"Picked {test_env}")
          with codecs.open(os.environ["GITHUB_ENV"], mode="a", encoding="utf-8") as file_handler:
              file_handler.write(f"ENV={test_env}\n")
        shell: python
      - name: Setup test environment
        run: |
          hatch -v env create ${ENV}
        shell: bash
      - name: Run test suite
        env:
          # Make sure to add `passenv=COVERAGE_FILE` to `[testenv]` in tox.ini
          # Make sure to add `overrides = { env.COVERAGE_FILE.env-vars = "COVERAGE_FILE" }` to `[tests]` in hatch.toml
          COVERAGE_FILE: ".core-coverage"
          CI_RUN: "yes"
        run: hatch -v run ${ENV}:run-cov-core
        shell: bash

      - name: Store coverage file
        uses: actions/upload-artifact@v7
        if: ${{ failure() }}
        with:
          name: core-coverage
          path: .core-coverage
          include-hidden-files: true
          if-no-files-found: error


  # Ensure everything required is passing for branch protection.
  required-checks-pass:
    if: always()

    needs:
      - coverage
      - core-coverage
      - docs
      - install-dev
      - typing
      - pre-commit
      - check-manifest

    runs-on: ubuntu-latest

    steps:
      - name: Decide whether the needed jobs succeeded or failed
        uses: re-actors/alls-green@release/v1
        with:
          jobs: ${{ toJSON(needs) }}

The same workflow, on Latchkey

Estimated ~20% faster on cache hits, plus fewer wasted runs and a safer supply chain. Added and changed lines are highlighted.

name: CI
 
on:
  push:
    branches: [main]
  pull_request: {}
 
permissions: {}
 
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
env:
  FORCE_COLOR: "1"
  PIP_DISABLE_PIP_VERSION_CHECK: "1"
  PIP_NO_PYTHON_VERSION_WARNING: "1"
 
jobs:
  check-manifest:
    timeout-minutes: 30
    runs-on: latchkey-small
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
      - run: pipx run check-manifest --ignore "_version.py"
 
  pre-commit:
    timeout-minutes: 30
    runs-on: latchkey-small
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
      - uses: actions/setup-python@v6
        with:
          cache: 'pip'
          python-version-file: .python-version-default
      - uses: pre-commit/action@v3.0.1
 
  typing:
    timeout-minutes: 30
    runs-on: latchkey-small
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
      - uses: actions/setup-python@v6
        with:
          cache: 'pip'
          python-version-file: .python-version-default
      - name: Install hatch
        run: python -Im pip install hatch
      - name: Setup types environment
        run: hatch -v env create types
      - name: Type check
        run: hatch run types:run
 
  docs:
    timeout-minutes: 30
    name: Build docs and run doctests
    runs-on: latchkey-small
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
      - uses: actions/setup-python@v6
        with:
          # Keep in sync with tox/docs, hatch.toml and .readthedocs.yaml.
          python-version-file: .python-version-default
          cache: pip
 
      - name: Install hatch
        run: python -Im pip install hatch
      - name: Build and check docs
        run: hatch run docs:run
      - name: Run doctests
        run: hatch run docs:doctest
      - name: Check changelog
        run: hatch run changelog:run
 
      - uses: actions/upload-artifact@v7
        with:
          name: docs
          path: docs/_build/
 
  install-dev:
    timeout-minutes: 30
    name: Verify install env
    runs-on: latchkey-small
 
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
      - uses: actions/setup-python@v6
        with:
          python-version-file: .python-version-default
          cache: pip
 
      - name: Install in dev mode & import
        run: |
          python -Im pip install -e .[dev]
          python -Ic 'import subliminal; print(subliminal.__version__)'
 
  build-package:
    timeout-minutes: 30
    name: Build & verify package
    runs-on: latchkey-small
 
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
          fetch-depth: 0
 
      - uses: hynek/build-and-inspect-python-package@v2
        id: baipp
 
    outputs:
      # Used to define the matrix for tests below. The value is based on
      # packaging metadata (trove classifiers).
      supported-python-versions: ${{ steps.baipp.outputs.supported_python_classifiers_json_array }}
 
  prepare-test:
    timeout-minutes: 30
    name: Download data for tests
    runs-on: latchkey-small
 
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
          fetch-depth: 0
      - name: Prepare tests
        uses: ./.github/actions/prepare-tests
      - name: Display downloaded files
        run: ls -R tests/data
 
  test:
    timeout-minutes: 30
    name: Test
    runs-on: ${{ matrix.os }}
    needs: [build-package, prepare-test]
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        # Created by the build-and-inspect-python-package action above.
        py: ${{ fromJson(needs.build-package.outputs.supported-python-versions) }}
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
      - uses: actions/setup-python@v6
        with:
          cache: 'pip'
          python-version: ${{ matrix.py }}
          allow-prereleases: true
      - name: Prepare tests
        uses: ./.github/actions/prepare-tests
      - name: Display downloaded files
        run: ls -R tests/data
 
      - name: Install hatch
        run: python -Im pip install hatch
 
      - name: Pick environment to run
        env:
          PYTHON_VERSION: ${{ matrix.py }}
        run: |
          import codecs; import os
          py = f"tests.py{os.environ.get('PYTHON_VERSION')}"
          print(f"Picked {py}")
          with codecs.open(os.environ["GITHUB_ENV"], mode="a", encoding="utf-8") as file_handler:
              file_handler.write(f"FORCE_COLOR=1\nENV={py}\n")
        shell: python
      - name: Setup test environment
        run: |
          hatch -v env create ${ENV}
        shell: bash
      - name: Run test suite
        env:
          # Make sure to add `passenv=COVERAGE_FILE` to `[testenv]` in tox.ini
          # Make sure to add `overrides = { env.COVERAGE_FILE.env-vars = "COVERAGE_FILE" }` to `[tests]` in hatch.toml
          COVERAGE_FILE: ".coverage.${{ matrix.os }}.${{ matrix.py }}"
          CI_RUN: "yes"
        run: hatch -v run ${ENV}:test-cov
        shell: bash
 
      - name: Store coverage file
        uses: actions/upload-artifact@v7
        with:
          name: coverage-${{ matrix.os }}-${{ matrix.py }}
          path: .coverage*
          include-hidden-files: true
          if-no-files-found: error
 
  coverage:
    timeout-minutes: 30
    name: Coverage
    runs-on: latchkey-small
    needs: test
    permissions:
      pull-requests: write
      contents: write
    steps:
      - uses: actions/checkout@v7
        with:
          # py-cov-action/python-coverage-comment-action needs git credentials
          persist-credentials: true
      - uses: actions/setup-python@v6
        with:
          cache: 'pip'
          python-version-file: .python-version-default
      - uses: hynek/setup-cached-uv@v2
 
      - name: Download coverage data
        uses: actions/download-artifact@v8
        with:
          pattern: coverage-*
          merge-multiple: true
 
      - name: Combine coverage
        run: |
          uv tool install 'coverage[toml]>7'
 
          coverage combine
          coverage html --skip-covered --skip-empty
 
          # Report and write to summary.
          coverage report --skip-covered --skip-empty --show-missing --format=markdown >> $GITHUB_STEP_SUMMARY
 
      - name: Upload HTML report if check failed.
        uses: actions/upload-artifact@v7
        with:
          name: html-report
          path: htmlcov
 
      - name: Coverage comment
        id: coverage_comment
        uses: py-cov-action/python-coverage-comment-action@v3
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 
      - name: Store Pull Request comment to be posted
        uses: actions/upload-artifact@v7
        if: steps.coverage_comment.outputs.COMMENT_FILE_WRITTEN == 'true'
        with:
          # If you use a different name, update COMMENT_ARTIFACT_NAME accordingly
          name: python-coverage-comment-action
          # If you use a different name, update COMMENT_FILENAME accordingly
          path: python-coverage-comment-action.txt
 
  core-coverage:
    timeout-minutes: 30
    name: Core Coverage
    runs-on: latchkey-small
    needs: [build-package, prepare-test]
    steps:
      - uses: actions/checkout@v7
        with:
          persist-credentials: false
      - uses: actions/setup-python@v6
        id: setup
        with:
          cache: 'pip'
          python-version-file: .python-version-default
      - name: Prepare tests
        uses: ./.github/actions/prepare-tests
      - name: Display downloaded files
        run: ls -R tests/data
      - name: Install hatch
        run: python -Im pip install hatch
 
      - name: Pick environment to run
        env:
          PYTHON_VERSION: ${{ steps.setup.outputs.python-version }}
        run: |
          import codecs; import os
          py = os.environ.get('PYTHON_VERSION')
          py = ".".join(py.split(".")[:2])
          test_env = f"tests.py{py}"
          print(f"Picked {test_env}")
          with codecs.open(os.environ["GITHUB_ENV"], mode="a", encoding="utf-8") as file_handler:
              file_handler.write(f"ENV={test_env}\n")
        shell: python
      - name: Setup test environment
        run: |
          hatch -v env create ${ENV}
        shell: bash
      - name: Run test suite
        env:
          # Make sure to add `passenv=COVERAGE_FILE` to `[testenv]` in tox.ini
          # Make sure to add `overrides = { env.COVERAGE_FILE.env-vars = "COVERAGE_FILE" }` to `[tests]` in hatch.toml
          COVERAGE_FILE: ".core-coverage"
          CI_RUN: "yes"
        run: hatch -v run ${ENV}:run-cov-core
        shell: bash
 
      - name: Store coverage file
        uses: actions/upload-artifact@v7
        if: ${{ failure() }}
        with:
          name: core-coverage
          path: .core-coverage
          include-hidden-files: true
          if-no-files-found: error
 
 
  # Ensure everything required is passing for branch protection.
  required-checks-pass:
    timeout-minutes: 30
    if: always()
 
    needs:
      - coverage
      - core-coverage
      - docs
      - install-dev
      - typing
      - pre-commit
      - check-manifest
 
    runs-on: latchkey-small
 
    steps:
      - name: Decide whether the needed jobs succeeded or failed
        uses: re-actors/alls-green@release/v1
        with:
          jobs: ${{ toJSON(needs) }}
 

What changed

5 third-party actions are referenced by a movable tag. Pin them to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.

What Latchkey heals here

This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:

This workflow runs 11 jobs (13 with the matrix expanded) per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow