CI workflow (aio-libs/async-lru)
The CI workflow from aio-libs/async-lru, explained and optimized by Latchkey.
CI health: F - at risk
Point runs-on at Latchkey and get caching, run de-duplication, 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 aio-libs/async-lru 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:
- master
- '[0-9].[0-9]+' # matches to backport branches, e.g. 3.6
tags: [ 'v*' ]
pull_request:
branches:
- master
- '[0-9].[0-9]+'
jobs:
lint:
name: Linter
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.10'
cache: 'pip'
cache-dependency-path: '**/requirements*.txt'
- name: Pre-Commit hooks
uses: pre-commit/action@v3.0.1
- name: Install dependencies
uses: py-actions/py-dependency-install@v4.1.0
with:
path: requirements-dev.txt
- name: Install itself
run: |
pip install .
- name: Run linter
run: |
make lint
- name: Prepare twine checker
run: |
pip install -U twine wheel build
python -m build
- name: Run twine checker
run: |
twine check dist/*
test:
name: Test
strategy:
matrix:
pyver: ['3.10', '3.11', '3.12', '3.13', '3.14']
os: [ubuntu, macos, windows]
experimental: [false]
include:
- pyver: pypy-3.11
os: ubuntu
experimental: false
- os: ubuntu
pyver: "3.14"
experimental: true
fail-fast: true
runs-on: ${{ matrix.os }}-latest
timeout-minutes: 15
continue-on-error: ${{ matrix.experimental }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Python ${{ matrix.pyver }}
uses: actions/setup-python@v6
with:
allow-prereleases: true
python-version: ${{ matrix.pyver }}
cache: 'pip'
cache-dependency-path: '**/requirements*.txt'
- name: Install dependencies
uses: py-actions/py-dependency-install@v4.1.0
with:
path: requirements.txt
- name: Run unittests
run: make test
env:
COLOR: 'yes'
- run: python -m coverage xml
- name: Upload coverage
uses: codecov/codecov-action@v5
with:
file: ./coverage.xml
flags: unit
check: # This job does nothing and is only used for the branch protection
if: always()
needs: [lint, test]
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) }}
deploy:
name: Deploy
runs-on: ubuntu-latest
needs: [check]
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
permissions:
contents: write # IMPORTANT: mandatory for making GitHub Releases
id-token: write # IMPORTANT: mandatory for trusted publishing & sigstore
environment:
name: pypi
url: https://pypi.org/p/async-lru
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: 3.13
- name: Install dependencies
run:
python -m pip install -U pip wheel setuptools build twine
- name: Build dists
run: |
python -m build
- name: Make Release
uses: aio-libs/create-release@v1.6.6
with:
changes_file: CHANGES.rst
name: async-lru
version_file: async_lru/__init__.py
github_token: ${{ secrets.GITHUB_TOKEN }}
dist_dir: dist
fix_issue_regex: "`#(\\d+) <https://github.com/aio-libs/async-lru/issues/\\1>`"
fix_issue_repl: "(#\\1)"
- name: >-
Publish ππ¦ to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
- name: Sign the dists with Sigstore
uses: sigstore/gh-action-sigstore-python@v3.4.0
with:
inputs: >-
./dist/*.tar.gz
./dist/*.whl
- name: Upload artifact signatures to GitHub Release
# Confusingly, this action also supports updating releases, not
# just creating them. This is what we want here, since we've manually
# created the release above.
uses: softprops/action-gh-release@v3
with:
# dist/ contains the built packages, which smoketest-artifacts/
# contains the signatures and certificates.
files: dist/**
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: - master - '[0-9].[0-9]+' # matches to backport branches, e.g. 3.6 tags: [ 'v*' ] pull_request: branches: - master - '[0-9].[0-9]+' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: lint: name: Linter runs-on: latchkey-small timeout-minutes: 5 steps: - name: Checkout uses: actions/checkout@v6 - name: Setup Python uses: actions/setup-python@v6 with: python-version: '3.10' cache: 'pip' cache-dependency-path: '**/requirements*.txt' - name: Pre-Commit hooks uses: pre-commit/action@v3.0.1 - name: Install dependencies uses: py-actions/py-dependency-install@v4.1.0 with: path: requirements-dev.txt - name: Install itself run: | pip install . - name: Run linter run: | make lint - name: Prepare twine checker run: | pip install -U twine wheel build python -m build - name: Run twine checker run: | twine check dist/* test: name: Test strategy: matrix: pyver: ['3.10', '3.11', '3.12', '3.13', '3.14'] os: [ubuntu, macos, windows] experimental: [false] include: - pyver: pypy-3.11 os: ubuntu experimental: false - os: ubuntu pyver: "3.14" experimental: true fail-fast: true runs-on: ${{ matrix.os }}-latest timeout-minutes: 15 continue-on-error: ${{ matrix.experimental }} steps: - name: Checkout uses: actions/checkout@v6 - name: Setup Python ${{ matrix.pyver }} uses: actions/setup-python@v6 with: allow-prereleases: true python-version: ${{ matrix.pyver }} cache: 'pip' cache-dependency-path: '**/requirements*.txt' - name: Install dependencies uses: py-actions/py-dependency-install@v4.1.0 with: path: requirements.txt - name: Run unittests run: make test env: COLOR: 'yes' - run: python -m coverage xml - name: Upload coverage uses: codecov/codecov-action@v5 with: file: ./coverage.xml flags: unit check: # This job does nothing and is only used for the branch protection timeout-minutes: 30 if: always() needs: [lint, test] 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) }} deploy: timeout-minutes: 30 name: Deploy runs-on: latchkey-small needs: [check] if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') permissions: contents: write # IMPORTANT: mandatory for making GitHub Releases id-token: write # IMPORTANT: mandatory for trusted publishing & sigstore environment: name: pypi url: https://pypi.org/p/async-lru steps: - name: Checkout uses: actions/checkout@v6 - name: Setup Python uses: actions/setup-python@v6 with: cache: 'pip' python-version: 3.13 - name: Install dependencies run: python -m pip install -U pip wheel setuptools build twine - name: Build dists run: | python -m build - name: Make Release uses: aio-libs/create-release@v1.6.6 with: changes_file: CHANGES.rst name: async-lru version_file: async_lru/__init__.py github_token: ${{ secrets.GITHUB_TOKEN }} dist_dir: dist fix_issue_regex: "`#(\\d+) <https://github.com/aio-libs/async-lru/issues/\\1>`" fix_issue_repl: "(#\\1)" - name: >- Publish ππ¦ to PyPI uses: pypa/gh-action-pypi-publish@release/v1 - name: Sign the dists with Sigstore uses: sigstore/gh-action-sigstore-python@v3.4.0 with: inputs: >- ./dist/*.tar.gz ./dist/*.whl - name: Upload artifact signatures to GitHub Release # Confusingly, this action also supports updating releases, not # just creating them. This is what we want here, since we've manually # created the release above. uses: softprops/action-gh-release@v3 with: # dist/ contains the built packages, which smoketest-artifacts/ # contains the signatures and certificates. files: dist/**
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. - Cancel superseded runs when a branch or PR gets a newer push.
- 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.
8 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 4 jobs (18 with the matrix expanded) per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.