Main workflow (Neoteroi/BlackSheep)
The Main workflow from Neoteroi/BlackSheep, 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 Main workflow from the Neoteroi/BlackSheep 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: Main
on:
workflow_dispatch:
inputs:
publish_artifacts:
description: 'Publish artifacts (Y|N)'
required: true
default: 'N'
release:
types: [published]
push:
branches:
- main
- ci
paths-ignore:
- README.md
- CHANGELOG.md
pull_request:
branches:
- "*"
paths-ignore:
- README.md
- CHANGELOG.md
env:
PROJECT_NAME: blacksheep
jobs:
windows-validation:
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
runs-on: windows-latest
if: github.event_name == 'pull_request' || github.event_name == 'push'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 9
submodules: false
- name: Use Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install flake8
- name: Compile Cython extensions
run: |
cython blacksheep/url.pyx
cython blacksheep/exceptions.pyx
cython blacksheep/headers.pyx
cython blacksheep/cookies.pyx
cython blacksheep/contents.pyx
cython blacksheep/messages.pyx
cython blacksheep/scribe.pyx
cython blacksheep/baseapp.pyx
python setup.py build_ext --inplace
- name: Run tests
run: |
pytest
build:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "pypy-3.11"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 9
submodules: false
- name: Use Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
VER='${{ matrix.python-version }}'
if [ $VER == "pypy-3.11" ]; then
echo "Install requirements for PyPy"
pip install -r requirements.pypy.txt
else
echo "Install requirements for CPython"
pip install -r requirements.txt
fi
pip install black==25.1.0 isort==6.0.1 flake8==7.2.0
- name: Run tests for Pure-Python Code
run: |
echo "Running unit tests..."
pytest tests
echo "Running integration tests..."
export APP_DEFAULT_ROUTER=false
pytest itests
- name: Compile Cython extensions
if: matrix.python-version != 'pypy-3.11'
run: |
make compile
- name: Run tests with Cython Code
if: matrix.python-version != 'pypy-3.11'
run: |
echo "Running unit tests..."
pytest tests
echo "Running integration tests..."
export APP_DEFAULT_ROUTER=false
pytest itests
- name: Run linters
continue-on-error: true
run: |
echo "Running linters - if build fails here, please be patient! Feel free to ask for assistance."
flake8 blacksheep
flake8 tests
flake8 itests
isort --check-only blacksheep 2>&1
isort --check-only tests 2>&1
isort --check-only itests 2>&1
black --check blacksheep 2>&1
black --check tests 2>&1
black --check itests 2>&1
- name: Test Hypercorn
run: |
PYVER=`python -V 2>&1`
if [ "${PYVER:0:-2}" == "Python 3.10" ]; then
echo "Skipping Hypercorn tests, since it fails with Python 3.10.0 on Ubuntu (2021/10/24)"
else
APP_DEFAULT_ROUTER=false ASGI_SERVER=hypercorn pytest itests/test_server.py
fi
- name: Test Piccolo Admin / Starlette mount compatibility
if: matrix.os == 'ubuntu-latest' && matrix.python-version == 3.14
run: |
echo "Running Starlette mount compatibility tests (ref: piccolo-orm/piccolo_admin#472)..."
pip install piccolo-admin # include the dedicated test
pytest tests/test_piccolo_admin_compat.py -v
- name: Install distribution dependencies
run: pip install --upgrade twine setuptools wheel
if: matrix.os == 'ubuntu-latest' && matrix.python-version == 3.12
- name: Create distribution package
run: python setup.py sdist
if: matrix.os == 'ubuntu-latest' && matrix.python-version == 3.12
- name: Upload distribution package
uses: actions/upload-artifact@v4
with:
name: dist-ubuntu-no-wheels
path: dist
if: matrix.os == 'ubuntu-latest' && matrix.python-version == 3.12
- name: Test imports with no optional dependencies
run: |
pip uninstall -y -r <(pip freeze)
BLACKSHEEP_NO_EXTENSIONS="1" pip install -e .
cd itests
echo "Expect no import error…"
python issue606.py
# Build wheels with cibuildwheel
build-wheels:
needs: build
name: Build ${{ matrix.os }} wheels
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
[
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
# macos-15-intel, # Failing with "File Not Found" for wheels it builds
# macos-latest, # Failing with "File Not Found" for wheels it builds
]
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Build wheels
uses: pypa/cibuildwheel@v3.2.1
env:
CIBW_BUILD: cp310-* cp311-* cp312-* cp313-* cp314-*
CIBW_ARCHS: "auto64"
# Before build commands
CIBW_BEFORE_BUILD_WINDOWS: >
python3 -m pip install setuptools wheel Cython==3.1.4 &&
python -c "import os, glob; [os.remove(f) for f in glob.glob('blacksheep/*.c') + glob.glob('blacksheep/*.so')]" &&
make compile
# Unix-based systems (Linux)
CIBW_BEFORE_BUILD: >
python3 -m pip install setuptools wheel Cython==3.1.4 &&
make clean &&
make compile
with:
output-dir: dist
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-${{ matrix.os }}-${{ strategy.job-index }}
path: dist/*.whl
# Separate macOS wheel building
build-macos-wheels:
needs: build
name: Build macOS wheels
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest] # macos-15-intel NOT supported
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "pypy-3.11"]
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install build dependencies
run: |
python -m pip install --upgrade pip build wheel setuptools Cython==3.1.4
- name: Clean and compile
run: |
make clean
make compile
- name: Build wheel
run: |
python -m build --wheel
- name: Upload wheels
uses: actions/upload-artifact@v4
with:
name: wheels-${{ matrix.os }}-${{ matrix.python-version }}
path: dist/*.whl
# Build PyPy wheel (pure Python)
build-pypy-wheels:
needs: build
name: Build PyPy wheel
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
python-version: ["pypy-3.11"]
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 9
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
pip install -r requirements.pypy.txt
- name: Run tests with PyPy
run: |
echo "Running unit tests..."
pytest tests
- name: Install build dependencies
run: |
pip install --upgrade build wheel setuptools
- name: Build wheels
run: |
BLACKSHEEP_NO_EXTENSIONS=1 python -m build --wheel
- uses: actions/upload-artifact@v4
with:
name: wheels-${{ matrix.os }}-${{ matrix.python-version }}
path: dist/*.whl
# Build source distribution
build-sdist:
needs: build
name: Build source distribution
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: "3.13"
- name: Install build dependencies
run: |
python -m pip install --upgrade pip
pip install build Cython==3.1.4
- name: Build sdist
run: |
python -m build --sdist
- name: Upload sdist
uses: actions/upload-artifact@v4
with:
name: wheels-sdist
path: dist/*.tar.gz
publish:
runs-on: ubuntu-latest
needs: [build-wheels, build-macos-wheels, build-pypy-wheels, build-sdist]
if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.publish_artifacts == 'Y')
steps:
- name: Download a distribution artifact
uses: actions/download-artifact@v4
with:
pattern: wheels-*
merge-multiple: true
path: dist
- name: Use Python 3.13
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install dependencies
run: |
pip install twine
- name: Publish distribution 📦 to Test PyPI
run: |
twine upload -r testpypi dist/*
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.test_pypi_password }}
- name: Publish distribution 📦 to PyPI
run: |
twine upload -r pypi dist/*
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.pypi_password }}
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: Main on: workflow_dispatch: inputs: publish_artifacts: description: 'Publish artifacts (Y|N)' required: true default: 'N' release: types: [published] push: branches: - main - ci paths-ignore: - README.md - CHANGELOG.md pull_request: branches: - "*" paths-ignore: - README.md - CHANGELOG.md env: PROJECT_NAME: blacksheep concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: windows-validation: timeout-minutes: 30 strategy: fail-fast: false matrix: python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] runs-on: windows-latest if: github.event_name == 'pull_request' || github.event_name == 'push' steps: - uses: actions/checkout@v4 with: fetch-depth: 9 submodules: false - name: Use Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: cache: 'pip' python-version: ${{ matrix.python-version }} - name: Install dependencies run: | pip install -r requirements.txt pip install flake8 - name: Compile Cython extensions run: | cython blacksheep/url.pyx cython blacksheep/exceptions.pyx cython blacksheep/headers.pyx cython blacksheep/cookies.pyx cython blacksheep/contents.pyx cython blacksheep/messages.pyx cython blacksheep/scribe.pyx cython blacksheep/baseapp.pyx python setup.py build_ext --inplace - name: Run tests run: | pytest build: timeout-minutes: 30 strategy: fail-fast: false matrix: os: [ubuntu-latest] python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "pypy-3.11"] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 with: fetch-depth: 9 submodules: false - name: Use Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: cache: 'pip' python-version: ${{ matrix.python-version }} - name: Install dependencies run: | VER='${{ matrix.python-version }}' if [ $VER == "pypy-3.11" ]; then echo "Install requirements for PyPy" pip install -r requirements.pypy.txt else echo "Install requirements for CPython" pip install -r requirements.txt fi pip install black==25.1.0 isort==6.0.1 flake8==7.2.0 - name: Run tests for Pure-Python Code run: | echo "Running unit tests..." pytest tests echo "Running integration tests..." export APP_DEFAULT_ROUTER=false pytest itests - name: Compile Cython extensions if: matrix.python-version != 'pypy-3.11' run: | make compile - name: Run tests with Cython Code if: matrix.python-version != 'pypy-3.11' run: | echo "Running unit tests..." pytest tests echo "Running integration tests..." export APP_DEFAULT_ROUTER=false pytest itests - name: Run linters continue-on-error: true run: | echo "Running linters - if build fails here, please be patient! Feel free to ask for assistance." flake8 blacksheep flake8 tests flake8 itests isort --check-only blacksheep 2>&1 isort --check-only tests 2>&1 isort --check-only itests 2>&1 black --check blacksheep 2>&1 black --check tests 2>&1 black --check itests 2>&1 - name: Test Hypercorn run: | PYVER=`python -V 2>&1` if [ "${PYVER:0:-2}" == "Python 3.10" ]; then echo "Skipping Hypercorn tests, since it fails with Python 3.10.0 on Ubuntu (2021/10/24)" else APP_DEFAULT_ROUTER=false ASGI_SERVER=hypercorn pytest itests/test_server.py fi - name: Test Piccolo Admin / Starlette mount compatibility if: matrix.os == 'ubuntu-latest' && matrix.python-version == 3.14 run: | echo "Running Starlette mount compatibility tests (ref: piccolo-orm/piccolo_admin#472)..." pip install piccolo-admin # include the dedicated test pytest tests/test_piccolo_admin_compat.py -v - name: Install distribution dependencies run: pip install --upgrade twine setuptools wheel if: matrix.os == 'ubuntu-latest' && matrix.python-version == 3.12 - name: Create distribution package run: python setup.py sdist if: matrix.os == 'ubuntu-latest' && matrix.python-version == 3.12 - name: Upload distribution package uses: actions/upload-artifact@v4 with: name: dist-ubuntu-no-wheels path: dist if: matrix.os == 'ubuntu-latest' && matrix.python-version == 3.12 - name: Test imports with no optional dependencies run: | pip uninstall -y -r <(pip freeze) BLACKSHEEP_NO_EXTENSIONS="1" pip install -e . cd itests echo "Expect no import error…" python issue606.py # Build wheels with cibuildwheel build-wheels: timeout-minutes: 30 needs: build name: Build ${{ matrix.os }} wheels if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ ubuntu-latest, ubuntu-24.04-arm, windows-latest, windows-11-arm, # macos-15-intel, # Failing with "File Not Found" for wheels it builds # macos-latest, # Failing with "File Not Found" for wheels it builds ] steps: - uses: actions/checkout@v5 with: fetch-depth: 0 - name: Build wheels uses: pypa/cibuildwheel@v3.2.1 env: CIBW_BUILD: cp310-* cp311-* cp312-* cp313-* cp314-* CIBW_ARCHS: "auto64" # Before build commands CIBW_BEFORE_BUILD_WINDOWS: > python3 -m pip install setuptools wheel Cython==3.1.4 && python -c "import os, glob; [os.remove(f) for f in glob.glob('blacksheep/*.c') + glob.glob('blacksheep/*.so')]" && make compile # Unix-based systems (Linux) CIBW_BEFORE_BUILD: > python3 -m pip install setuptools wheel Cython==3.1.4 && make clean && make compile with: output-dir: dist - name: Upload wheels uses: actions/upload-artifact@v4 with: name: wheels-${{ matrix.os }}-${{ strategy.job-index }} path: dist/*.whl # Separate macOS wheel building build-macos-wheels: timeout-minutes: 30 needs: build name: Build macOS wheels if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' runs-on: ${{ matrix.os }} strategy: matrix: os: [macos-latest] # macos-15-intel NOT supported python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "pypy-3.11"] steps: - uses: actions/checkout@v5 with: fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v6 with: cache: 'pip' python-version: ${{ matrix.python-version }} - name: Install build dependencies run: | python -m pip install --upgrade pip build wheel setuptools Cython==3.1.4 - name: Clean and compile run: | make clean make compile - name: Build wheel run: | python -m build --wheel - name: Upload wheels uses: actions/upload-artifact@v4 with: name: wheels-${{ matrix.os }}-${{ matrix.python-version }} path: dist/*.whl # Build PyPy wheel (pure Python) build-pypy-wheels: timeout-minutes: 30 needs: build name: Build PyPy wheel if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: python-version: ["pypy-3.11"] os: [ubuntu-latest] steps: - uses: actions/checkout@v5 with: fetch-depth: 9 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 with: cache: 'pip' python-version: ${{ matrix.python-version }} - name: Install dependencies run: | pip install -r requirements.pypy.txt - name: Run tests with PyPy run: | echo "Running unit tests..." pytest tests - name: Install build dependencies run: | pip install --upgrade build wheel setuptools - name: Build wheels run: | BLACKSHEEP_NO_EXTENSIONS=1 python -m build --wheel - uses: actions/upload-artifact@v4 with: name: wheels-${{ matrix.os }}-${{ matrix.python-version }} path: dist/*.whl # Build source distribution build-sdist: timeout-minutes: 30 needs: build name: Build source distribution if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' runs-on: latchkey-small steps: - uses: actions/checkout@v5 with: fetch-depth: 0 - name: Setup Python uses: actions/setup-python@v6 with: cache: 'pip' python-version: "3.13" - name: Install build dependencies run: | python -m pip install --upgrade pip pip install build Cython==3.1.4 - name: Build sdist run: | python -m build --sdist - name: Upload sdist uses: actions/upload-artifact@v4 with: name: wheels-sdist path: dist/*.tar.gz publish: timeout-minutes: 30 runs-on: latchkey-small needs: [build-wheels, build-macos-wheels, build-pypy-wheels, build-sdist] if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.publish_artifacts == 'Y') steps: - name: Download a distribution artifact uses: actions/download-artifact@v4 with: pattern: wheels-* merge-multiple: true path: dist - name: Use Python 3.13 uses: actions/setup-python@v5 with: cache: 'pip' python-version: '3.13' - name: Install dependencies run: | pip install twine - name: Publish distribution 📦 to Test PyPI run: | twine upload -r testpypi dist/* env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.test_pypi_password }} - name: Publish distribution 📦 to PyPI run: | twine upload -r pypi dist/* env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.pypi_password }}
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.
1 third-party action is referenced by a movable tag. Pin it 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 7 jobs (24 with the matrix expanded) per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.