Add reusable workflows

This commit is contained in:
Patrick Lehmann
2021-11-30 06:53:51 +01:00
committed by GitHub
12 changed files with 740 additions and 0 deletions

34
.github/workflows/ArtifactCleanUp.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: ArtifactCleanUp
on:
workflow_call:
inputs:
package:
description: 'Artifacts to be removed on not tagged runs.'
required: true
type: string
remaining:
description: 'Artifacts to be removed unconditionally.'
required: false
default: ''
type: string
jobs:
ArtifactCleanUp:
name: 🗑️ Artifact Cleanup
runs-on: ubuntu-latest
steps:
- name: 🗑️ Delete package Artifacts
if: ${{ ! startsWith(github.ref, 'refs/tags') }}
uses: geekyeggo/delete-artifact@v1
with:
name: ${{ inputs.package }}
- name: 🗑️ Delete remaining Artifacts
if: ${{ inputs.remaining != '' }}
uses: geekyeggo/delete-artifact@v1
with:
name: ${{ inputs.remaining }}

31
.github/workflows/BuildTheDocs.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: Documentation
on:
workflow_call:
inputs:
artifact:
description: 'Name of the documentation artifact.'
required: true
type: string
jobs:
BuildTheDocs:
name: 📓 Run BuildTheDocs
runs-on: ubuntu-latest
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v2
- name: 🛳️ Build documentation
uses: buildthedocs/btd@v0
with:
skip-deploy: true
- name: 📤 Upload 'documentation' artifacts
uses: actions/upload-artifact@master
with:
name: ${{ inputs.artifact }}
path: doc/_build/html
retention-days: 1

View File

@@ -0,0 +1,80 @@
name: Coverage Collection
on:
workflow_call:
inputs:
python_version:
description: 'Python version.'
required: false
default: '3.10'
type: string
requirements:
description: 'Python dependencies to be installed through pip.'
required: false
default: '-r tests/requirements.txt'
type: string
artifact:
description: 'Name of the coverage artifact.'
required: true
type: string
secrets:
codacy_token:
description: 'Token to push result to codacy.'
required: true
jobs:
Coverage:
name: 📈 Collect Coverage Data using Python ${{ inputs.python_version }}
runs-on: ubuntu-latest
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v2
- name: 🐍 Setup Python ${{ inputs.python_version }}
uses: actions/setup-python@v2
with:
python-version: ${{ inputs.python_version }}
- name: 🗂 Install dependencies
run: |
python -m pip install -U pip
python -m pip install ${{ inputs.requirements }}
- name: Collect coverage
continue-on-error: true
run: |
python -m pytest -rA --cov=.. --cov-config=tests/.coveragerc tests/unit --color=yes
- name: Convert to cobertura format
run: coverage xml
- name: Convert to HTML format
run: |
coverage html
rm htmlcov/.gitignore
- name: 📤 Upload 'Coverage Report' artifact
continue-on-error: true
uses: actions/upload-artifact@v2
with:
name: ${{ inputs.artifact }}
path: htmlcov
if-no-files-found: error
retention-days: 1
- name: 📊 Publish coverage at CodeCov
continue-on-error: true
uses: codecov/codecov-action@v1
with:
file: ./coverage.xml
flags: unittests
env_vars: PYTHON
- name: 📉 Publish coverage at Codacy
continue-on-error: true
uses: codacy/codacy-coverage-reporter-action@master
with:
project-token: ${{ secrets.codacy_token }}
coverage-reports: ./coverage.xml

53
.github/workflows/Package.yml vendored Normal file
View File

@@ -0,0 +1,53 @@
name: Package
on:
workflow_call:
inputs:
python_version:
description: 'Python version.'
required: false
default: '3.10'
type: string
requirements:
description: 'Python dependencies to be installed through pip.'
required: false
default: 'wheel'
type: string
artifact:
description: 'Name of the package artifact.'
required: true
type: string
jobs:
Package:
name: 📦 Package in Source and Wheel Format
runs-on: ubuntu-latest
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v2
- name: 🐍 Setup Python ${{ inputs.python_version }}
uses: actions/setup-python@v2
with:
python-version: ${{ inputs.python_version }}
- name: 🔧 Install dependencies for packaging and release
run: |
python -m pip install -U pip
python -m pip install ${{ inputs.requirements }}
- name: 🔨 Build Python package (source distribution)
run: python setup.py sdist
- name: 🔨 Build Python package (binary distribution - wheel)
run: python setup.py bdist_wheel
- name: 📤 Upload wheel artifact
uses: actions/upload-artifact@v2
with:
name: ${{ inputs.artifact }}
path: dist/
if-no-files-found: error
retention-days: 1

69
.github/workflows/Params.yml vendored Normal file
View File

@@ -0,0 +1,69 @@
name: Params
on:
workflow_call:
inputs:
python_version:
description: 'Python version.'
required: false
default: '3.10'
type: string
python_version_list:
description: 'Space separated list of Python versions to run tests with.'
required: false
default: '3.6 3.7 3.8 3.9 3.10'
type: string
name:
description: 'Name of the tool.'
required: true
type: string
outputs:
params:
description: "Parameters to be used in other jobs."
value: ${{ jobs.Params.outputs.params }}
python_jobs:
description: "List of Python versions to be used in the matrix of other jobs."
value: ${{ jobs.Params.outputs.python_jobs }}
jobs:
Params:
runs-on: ubuntu-latest
outputs:
params: ${{ steps.params.outputs.params }}
python_jobs: ${{ steps.params.outputs.python_jobs }}
steps:
- name: Generate 'params' and 'python_jobs'
id: params
shell: python
run: |
name = '${{ inputs.name }}'
params = {
'python_version': '${{ inputs.python_version }}',
'artifacts': {
'unittesting': f'{name}-TestReport',
'coverage': f'{name}-coverage',
'typing': f'{name}-typing',
'package': f'{name}-package',
'doc': f'{name}-doc',
}
}
print(f'::set-output name=params::{params!s}')
print("Params:")
print(params)
data = {
'3.6': { 'icon': '🔴', 'until': '23.12.2021' },
'3.7': { 'icon': '🟠', 'until': '27.06.2023' },
'3.8': { 'icon': '🟡', 'until': 'Oct. 2024' },
'3.9': { 'icon': '🟢', 'until': 'Oct. 2025' },
'3.10': { 'icon': '🟢', 'until': 'Oct. 2026' },
}
jobs = [
{'python': version, 'icon': data[version]['icon']}
for version in '${{ inputs.python_version_list }}'.split(' ')
]
print(f'::set-output name=python_jobs::{jobs!s}')
print("Python jobs:")
print(jobs)

63
.github/workflows/PublishOnPyPI.yml vendored Normal file
View File

@@ -0,0 +1,63 @@
name: Publish on PyPI
on:
workflow_call:
inputs:
python_version:
description: 'Python version.'
required: false
default: '3.10'
type: string
requirements:
description: 'Python dependencies to be installed through pip.'
required: false
default: 'wheel twine'
type: string
artifact:
description: 'Name of the package artifact.'
required: true
type: string
secrets:
PYPI_TOKEN:
description: "Token for pushing releases to PyPI"
required: false
jobs:
PublishOnPyPI:
name: 🚀 Publish to PyPI
runs-on: ubuntu-latest
steps:
- name: 📥 Download artifacts '${{ inputs.artifact }}' from 'Package' job
uses: actions/download-artifact@v2
with:
name: ${{ inputs.artifact }}
path: dist/
- name: 🐍 Setup Python ${{ inputs.python_version }}
uses: actions/setup-python@v2
with:
python-version: ${{ inputs.python_version }}
- name: ⚙ Install dependencies for packaging and release
run: |
python -m pip install -U pip
python -m pip install ${{ inputs.requirements }}
- name: ⤴ Release Python source package to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: twine upload dist/*.tar.gz
- name: ⤴ Release Python wheel package to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: twine upload dist/*.whl
- name: 🗑️ Delete packaging Artifacts
uses: geekyeggo/delete-artifact@v1
with:
name: ${{ inputs.artifact }}

View File

@@ -0,0 +1,62 @@
name: Publish to GitHub Pages
on:
workflow_call:
inputs:
doc:
description: 'Name of the documentation artifact.'
required: true
type: string
coverage:
description: 'Name of the coverage artifact.'
required: false
default: ''
type: string
typing:
description: 'Name of the typing artifact.'
required: false
default: ''
type: string
jobs:
PublishToGitHubPages:
name: 📚 Publish to GH-Pages
runs-on: ubuntu-latest
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v2
- name: 📥 Download artifacts '${{ inputs.doc }}' from 'BuildTheDocs' job
uses: actions/download-artifact@v2
with:
name: ${{ inputs.doc }}
path: public
- name: 📥 Download artifacts '${{ inputs.coverage }}' from 'Coverage' job
if: ${{ inputs.coverage != '' }}
uses: actions/download-artifact@v2
with:
name: ${{ inputs.coverage }}
path: public/coverage
- name: 📥 Download artifacts '${{ inputs.typing }}' from 'StaticTypeCheck' job
if: ${{ inputs.typing != '' }}
uses: actions/download-artifact@v2
with:
name: ${{ inputs.typing }}
path: public/typing
- name: '📓 Publish site to GitHub Pages'
if: github.event_name != 'pull_request'
run: |
cd public
touch .nojekyll
git init
cp ../.git/config ./.git/config
git add .
git config --local user.email "BuildTheDocs@GitHubActions"
git config --local user.name "GitHub Actions"
git commit -a -m "update ${{ github.sha }}"
git push -u origin +HEAD:gh-pages

44
.github/workflows/Release.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: Release
on:
workflow_call:
jobs:
Release:
name: 📝 Create 'Release Page' on GitHub
runs-on: ubuntu-latest
steps:
- name: 🔁 Extract Git tag from GITHUB_REF
id: getVariables
run: |
GIT_TAG=${GITHUB_REF#refs/*/}
RELEASE_VERSION=${GIT_TAG#v}
RELEASE_DATETIME="$(date --utc '+%d.%m.%Y - %H:%M:%S')"
# write to step outputs
echo ::set-output name=gitTag::${GIT_TAG}
echo ::set-output name=version::${RELEASE_VERSION}
echo ::set-output name=datetime::${RELEASE_DATETIME}
- name: 📑 Create Release Page
id: createReleasePage
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ github.token }}
with:
tag_name: ${{ steps.getVariables.outputs.gitTag }}
# release_name: ${{ steps.getVariables.outputs.gitTag }}
body: |
**Automated Release created on: ${{ steps.getVariables.outputs.datetime }}**
# New Features
* tbd
# Changes
* tbd
# Bug Fixes
* tbd
draft: false
prerelease: false

62
.github/workflows/StaticTypeCheck.yml vendored Normal file
View File

@@ -0,0 +1,62 @@
name: Static Type Check
on:
workflow_call:
inputs:
python_version:
description: 'Python version.'
required: false
default: '3.10'
type: string
requirements:
description: 'Python dependencies to be installed through pip.'
required: false
default: '-r tests/requirements.txt'
type: string
report:
description: 'Directory to upload as an artifact.'
required: false
default: 'htmlmypy'
type: string
commands:
description: 'Commands to run the static type checks.'
required: true
type: string
artifact:
description: 'Name of the typing artifact.'
required: true
type: string
jobs:
StaticTypeCheck:
name: 👀 Check Static Typing using Python ${{ inputs.python_version }}
runs-on: ubuntu-latest
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v2
- name: 🐍 Setup Python ${{ inputs.python_version }}
uses: actions/setup-python@v2
with:
python-version: ${{ inputs.python_version }}
- name: 🗂 Install dependencies
run: |
python -m pip install -U pip
python -m pip install ${{ inputs.requirements }}
- name: Check Static Typing
continue-on-error: true
run: ${{ inputs.commands }}
- name: 📤 Upload 'Static Typing Report' artifact
if: ${{ inputs.artifact != '' }}
continue-on-error: true
uses: actions/upload-artifact@v2
with:
name: ${{ inputs.artifact }}
path: ${{ inputs.report }}
if-no-files-found: error
retention-days: 1

58
.github/workflows/UnitTesting.yml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: Unit Testing
on:
workflow_call:
inputs:
jobs:
description: 'Space separated list of Python versions to run tests with.'
required: true
type: string
requirements:
description: 'Python dependencies to be installed through pip.'
required: false
default: '-r tests/requirements.txt'
type: string
artifact:
description: "Generate unit test report with junitxml and upload results as an artifact."
required: false
default: ''
type: string
jobs:
UnitTesting:
name: ${{ matrix.icon }} Unit Tests using Python ${{ matrix.python }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include: ${{ fromJson(inputs.jobs) }}
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v2
- name: 🐍 Setup Python ${{ matrix.python }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- name: 🔧 Install dependencies
run: |
python -m pip install -U pip
python -m pip install ${{ inputs.requirements }}
- name: ☑ Run unit tests
run: |
[ 'x${{ inputs.artifact }}' != 'x' ] && PYTEST_ARGS='--junitxml=TestReport.xml' || unset PYTEST_ARGS
python -m pytest -rA tests/unit $PYTEST_ARGS --color=yes
- name: 📤 Upload 'TestReport.xml' artifact
if: inputs.TestReport == 'true'
uses: actions/upload-artifact@v2
with:
name: ${{ inputs.artifact }}-${{ matrix.python }}
path: TestReport.xml
if-no-files-found: error
retention-days: 1

56
.github/workflows/VerifyDocs.yml vendored Normal file
View File

@@ -0,0 +1,56 @@
name: Verify examples
on:
workflow_call:
inputs:
python_version:
description: 'Python version.'
required: false
default: '3.10'
type: string
jobs:
VerifyDocs:
name: 👍 Verify example snippets using Python ${{ inputs.python_version }}
runs-on: ubuntu-latest
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v2
- name: 🐍 Setup Python
uses: actions/setup-python@v2
with:
python-version: ${{ inputs.python_version }}
- name: 🐍 Install dependencies
run: |
pip3 install .
- name: ✂ Extract code snippet from README
shell: python
run: |
from pathlib import Path
import re
ROOT = Path('.')
with (ROOT / 'README.md').open('r') as rptr:
content = rptr.read()
m = re.search(r"```py(thon)?(?P<code>.*?)```", content, re.MULTILINE|re.DOTALL)
if m is None:
raise Exception("Regular expression did not find the example in the README!")
with (ROOT / 'tests/docs/example.py').open('w') as wptr:
wptr.write(m["code"])
- name: Print example.py
run: cat tests/docs/example.py
- name: ☑ Run example snippet
working-directory: tests/docs
run: |
python3 example.py

128
ExamplePipeline.yml Normal file
View File

@@ -0,0 +1,128 @@
name: Unit Testing, Coverage Collection, Package, Release, Documentation and Publish
on:
workflow_dispatch:
jobs:
# This job is a workaround for global variables
# See https://github.com/actions/runner/issues/480
Params:
uses: pyTooling/Actions/.github/workflows/Params.yml@main
with:
name: ToolName
# Optional
python_version: '3.10'
python_version_list: '3.8 3.9 3.10'
UnitTesting:
uses: pyTooling/Actions/.github/workflows/UnitTesting.yml@main
with:
jobs: ${{ needs.Params.outputs.python_jobs }}
# Optional
requirements: '-r tests/requirements.txt'
artifact: ${{ fromJson(needs.Params.outputs.params).artifacts.unittesting }}
Coverage:
uses: pyTooling/Actions/.github/workflows/CoverageCollection.yml@main
needs:
- Params
with:
artifact: ${{ fromJson(needs.Params.outputs.params).artifacts.coverage }}
# Optional
python_version: ${{ fromJson(needs.Params.outputs.params).python_version }}
requirements: '-r tests/requirements.txt'
secrets:
codacy_token: ${{ secrets.CODACY_PROJECT_TOKEN }}
StaticTypeCheck:
uses: pyTooling/Actions/.github/workflows/StaticTypeCheck.yml@main
needs:
- Params
with:
commands: mypy --html-report htmlmypy -p ToolName
artifact: ${{ fromJson(needs.Params.outputs.params).artifacts.typing }}
# Optional
python_version: ${{ fromJson(needs.Params.outputs.params).python_version }}
requirements: '-r tests/requirements.txt'
report: 'htmlmypy'
Release:
uses: pyTooling/Actions/.github/workflows/Release.yml@main
if: startsWith(github.ref, 'refs/tags')
needs:
- UnitTesting
- Coverage
- StaticTypeCheck
Package:
uses: pyTooling/Actions/.github/workflows/Package.yml@main
if: startsWith(github.ref, 'refs/tags')
needs:
- Params
- Coverage
with:
artifact: ${{ fromJson(needs.Params.outputs.params).artifacts.package }}
# Optional
python_version: ${{ fromJson(needs.Params.outputs.params).python_version }}
requirements: 'wheel'
PublishOnPyPI:
uses: pyTooling/Actions/.github/workflows/PublishOnPyPI.yml@main
if: startsWith(github.ref, 'refs/tags')
needs:
- Params
- Release
- Package
with:
artifact: ${{ fromJson(needs.Params.outputs.params).artifacts.package }}
# Optional
python_version: ${{ fromJson(needs.Params.outputs.params).python_version }}
requirements: 'wheel twine'
secrets:
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
VerifyDocs:
uses: pyTooling/Actions/.github/workflows/VerifyDocs.yml@main
needs:
- Params
with:
# Optional
python_version: ${{ fromJson(needs.Params.outputs.params).python_version }}
BuildTheDocs:
uses: pyTooling/Actions/.github/workflows/BuildTheDocs.yml@main
needs:
- Params
- VerifyDocs
with:
artifact: ${{ fromJson(needs.Params.outputs.params).artifacts.doc }}
PublishToGitHubPages:
uses: pyTooling/Actions/.github/workflows/PublishToGitHubPages.yml@main
needs:
- Params
- BuildTheDocs
- Coverage
- StaticTypeCheck
with:
doc: ${{ fromJson(needs.Params.outputs.params).artifacts.doc }}
# Optional
coverage: ${{ fromJson(needs.Params.outputs.params).artifacts.coverage }}
typing: ${{ fromJson(needs.Params.outputs.params).artifacts.typing }}
ArtifactCleanUp:
uses: pyTooling/Actions/.github/workflows/ArtifactCleanUp.yml@main
needs:
- Params
- Coverage
- StaticTypeCheck
- BuildTheDocs
- PublishToGitHubPages
with:
package: ${{ fromJson(needs.Params.outputs.params).artifacts.package }}
remaining: |
${{ fromJson(needs.Params.outputs.params).artifacts.unittesting }}-*
${{ fromJson(needs.Params.outputs.params).artifacts.coverage }}
${{ fromJson(needs.Params.outputs.params).artifacts.typing }}
${{ fromJson(needs.Params.outputs.params).artifacts.doc }}