CI workflow (Diaoul/subliminal)
The CI workflow from Diaoul/subliminal, explained and optimized by Latchkey.
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.
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
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
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Cache dependency installs on the setup step so they are served from cache.
- Add a job timeout so a hung step cannot burn hours of runner time.
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:
- Dependency installs
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.