Skip to content

Commit ab63c1b

Browse files
committed
Refactor github CI workflows
- `publish.yaml`: Publish to test.pypi.org and pypi.org in the same job. - Use a single deployment environment (`publish-pypi`) for both test and production publishing endpoints. - `ci-workflow.yaml`: A high level reusable workflow to call other workflows. - Orchestrate all CI jobs from a single workflow. - Use workflow inputs (`jobs`) to control which jobs to run. - `ci.yaml`: Configure the CI workflow to use the new ci-workflow.yaml. - Here is where the triggers and workflow configuration are set. - This file will call `ci-workflow.yaml` with the appropriate inputs. Assumes tests and code checks are run by `tox` and configured in the `pyproject.toml` file.
1 parent 63b3db6 commit ab63c1b

File tree

8 files changed

+205
-833
lines changed

8 files changed

+205
-833
lines changed

.github/workflows/ci-release.yaml

Lines changed: 0 additions & 53 deletions
This file was deleted.

.github/workflows/ci-test-build.yaml

Lines changed: 0 additions & 30 deletions
This file was deleted.

.github/workflows/ci-test.yaml

Lines changed: 0 additions & 18 deletions
This file was deleted.

.github/workflows/ci-workflow.yaml

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
name: CI Workflow
2+
3+
# A reusable github workflow to run python CI workflows, including static code
4+
# checks, automated testing (using pytest and tox), package building,
5+
# publishing to test.pypi.org and pypi.org, and creating a GitHub release.
6+
#
7+
# The workflow elements to selected are specified in the `jobs` input. The
8+
# default is to run the `test` and `build` jobs. The CI workflow is
9+
# configured in the tox-gh config (eg. pyproject.toml). See
10+
# https://github.com/tox-dev/tox-gh.
11+
#
12+
# Accepts the following inputs as json strings:
13+
# - `jobs`: the list of jobs to run,
14+
# - `os`: the list of operating systems on which to run tests, and
15+
# - `python-version`: the list of python versions on which to run tests.
16+
#
17+
# Assumes the configurations for all tools (`mypy`, `uv`, `ruff`, `pytest`,
18+
# ...) are fully specified in config files (eg `pyproject.toml`).
19+
#
20+
# These workflows use `tox`, as it can be more easily customised in your
21+
# pyproject.toml so that the same CI process runs on your local computer and
22+
# on github.
23+
#
24+
# All workflows use astral `uv` to execute actions wherever possible.
25+
26+
on:
27+
workflow_call:
28+
inputs:
29+
jobs:
30+
description: >-
31+
A json string containing the list of jobs to run. Available jobs are:
32+
- `check`: Run static code checks (using `mypy`, `ruff`, etc.).
33+
- `test`: Run tests and code checks using `tox`.
34+
- `build`: Build the python package.
35+
- `publish-test`: Publish the package to test.pypi.org.
36+
- `publish`: Publish the package to pypi.org (runs `publish-test`).
37+
- `release`: Create a GitHub release.
38+
The default is `["test", "build"]`.
39+
default: '["test", "build"]'
40+
type: string
41+
required: false
42+
os:
43+
description: >-
44+
A json string containing the list of operating systems on which to
45+
run the test matrix.
46+
default: '["ubuntu-latest", "windows-latest", "macos-latest"]'
47+
type: string
48+
required: false
49+
python-version:
50+
description: >-
51+
A json string containing the list of python versions on
52+
which to run the test matrix.
53+
default: '["3.9", "3.10", "3.11", "3.12", "3.13"]'
54+
type: string
55+
required: false
56+
57+
jobs:
58+
init:
59+
runs-on: ubuntu-latest
60+
steps:
61+
- name: Select the workflow configuration
62+
run: | # Find the matching environment variable based on the event trigger
63+
echo '${{ github.repository }}'
64+
65+
test:
66+
if: ${{ contains(fromJson(inputs.jobs), 'test') }}
67+
name: Tests
68+
uses: glenn20/python-ci/.github/workflows/test-tox.yaml@dev
69+
with:
70+
os: ${{ inputs.os }}
71+
python-version: ${{ inputs.python-version }}
72+
permissions:
73+
id-token: none
74+
contents: none
75+
76+
build:
77+
if: ${{ contains(fromJson(inputs.jobs), 'build') }}
78+
name: Build python package
79+
uses: glenn20/python-ci/.github/workflows/build.yaml@dev
80+
permissions:
81+
id-token: none
82+
contents: none
83+
84+
# The publish workflows must be located in your project repository, not in the
85+
# python-ci repository, so that trusted publishing to pypi.org can be used.
86+
# Copy the `publish.yaml` workflow from
87+
# `https://github.com/glenn20/python-ci/tree/main/examples/publish.yaml` to
88+
# your project's `.github/workflows` directory.
89+
publish:
90+
if: ${{ contains(fromJson(inputs.jobs), 'publish-test') || contains(fromJson(inputs.jobs), 'publish') }}
91+
name: Publish to pypi
92+
uses: ${{ github.repository }}/.github/workflows/publish.yaml@dev
93+
with:
94+
test-only: ${{ !contains(fromJson(inputs.jobs), 'publish') }}
95+
needs: [test, build]
96+
permissions:
97+
id-token: write # IMPORTANT: mandatory for trusted publishing!
98+
contents: none # IMPORTANT: mandatory for github release
99+
100+
release:
101+
if: ${{ contains(fromJson(inputs.jobs), 'release') }}
102+
name: Create GitHub release
103+
uses: glenn20/python-ci/.github/workflows/github-release.yaml@dev
104+
needs: [test, build]
105+
permissions:
106+
id-token: write # IMPORTANT: mandatory for github release
107+
contents: write # IMPORTANT: mandatory for github release

.github/workflows/ci.yaml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
name: CI workflow for python projects
2+
3+
# Run the CI test workflow of jobs which includes:
4+
# - `check`: Run static code checks (using `mypy`, `ruff`, etc.).
5+
# - `test`: Run tests (and code checks) using `tox`.
6+
# - `build`: Build the python package.
7+
# - `publish-test`: Publish the package to test.pypi.org.
8+
# - `publish`: Publish the package to pypi.org (runs `publish-test`).
9+
# - `release`: Create a GitHub release.
10+
11+
on:
12+
push:
13+
branches: ["**"] # Push commits to any branch
14+
tags: ["v[0-9]*"] # Publish on tags matching "v*", eg. "v1.0.0"
15+
16+
# Configure the workflows here. Each environment variable name should be a
17+
# wildcard matching the
18+
# `on-<github.event_name>-<github.ref_type>-<github.ref_name>` format. For
19+
# example, if the event is a push to a tag `v1.0.0`, the environment variable
20+
# `on-push-tag-v*` will match. The value of the matching variable will be
21+
# written to $GITHUB_OUTPUT to set the jobs, python versions, and operating
22+
# systems to run the workflow on. The first match found is used.
23+
env:
24+
on-push-tag-*: | # Push version tag matching "v*", eg. "v1.0.0"
25+
jobs=["test", "build", "publish", "release"]
26+
python-version=["3.9", "3.10", "3.11", "3.12", "3.13"]
27+
os=["ubuntu-latest"]
28+
on-push-branch-main: | # Push commits to main branch
29+
jobs=["test", "build", "publish-test"]
30+
python-version=["3.9", "3.10", "3.11", "3.12", "3.13"]
31+
os=["ubuntu-latest"]
32+
on-push-branch-*: | # Push commits to other branches
33+
jobs=["test", "build", "publish-test"]
34+
python-version=["3.9", "3.13"]
35+
os=["ubuntu-latest"]
36+
37+
jobs:
38+
config:
39+
# Select the workflow config based on the event trigger.
40+
runs-on: ubuntu-latest
41+
outputs:
42+
jobs: ${{ steps.config.outputs.jobs }}
43+
python-version: ${{ steps.config.outputs.python-version }}
44+
os: ${{ steps.config.outputs.os }}
45+
steps:
46+
- name: Select the workflow configuration
47+
id: config
48+
run: | # Find the matching environment variable based on the event trigger
49+
tag="on-${{ github.event_name }}-${{ github.ref_type }}-${{ github.ref_name }}"
50+
for key in $(echo '${{ toJson(env) }}' | jq -r 'keys_unsorted[]'); do
51+
if [[ "$tag" == $key ]]; then
52+
# Write value of the matching environment variable to $GITHUB_OUTPUT
53+
echo '${{ toJson(env) }}' | jq -r ".[\"$key\"]" >> $GITHUB_OUTPUT
54+
exit 0 # Stop after the first match
55+
fi
56+
done
57+
echo "No matching environment variable found for '$tag'."
58+
59+
ci-workflow:
60+
name: CI workflow
61+
needs: config
62+
uses: ./.github/workflows/ci-workflow.yaml
63+
with:
64+
jobs: ${{ needs.config.outputs.jobs }}
65+
os: ${{ needs.config.outputs.os }}
66+
python-version: ${{ needs.config.outputs.python-version }}
67+
permissions:
68+
id-token: write # Required for trusted publishing!
69+
contents: write # Required for creating a GitHub release

.github/workflows/publish.yaml

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
name: Publish python package
22

33
# description: |- A github reusable workflow to publish a python package to
4-
# pypi.org or test.pypi.org.
4+
# pypi.org and test.pypi.org.
55
#
66
# Input options:
7-
# - `pypi: test.pypi`: Publish to test.pypi.org (default).
8-
# - `pypi: upload.pypi`: Publish to pypi.org.
7+
# - `test-only`:
8+
# - If true, publish only to test.pypi.org (default).
9+
# - If false, publish to both pypi.org and test.pypi.org.
910
#
1011
# The workflow invokes the github composite action
1112
# `glenn20/python-ci/publish@v1` to publish the package.
@@ -14,42 +15,45 @@ name: Publish python package
1415
# 1. For trusted publishing, the publishing workflow must be in the project
1516
# repository, so copy this workflow file to
1617
# `.github/workflows/publish.yaml` in your repository.
17-
# 2. Create the `publish-test.pypi` and `publish-upload.pypi` Environments in
18-
# your github repository (Settings->Environments->New Environment).
18+
# 2. Create the `publish-pypi` Environment in your github repository
19+
# (Settings->Environments->New Environment).
1920
# 3. Add the name of this workflow file (publish.yaml) as a "trusted
2021
# publisher" on your pypi and test.pypi project pages (add the name of the
2122
# relevant Environment for additional access control).
22-
# 4. Call this workflow from a parent workflow with the `pypi` input set to
23-
# "upload.pypi" or "test.pypi" (default).
24-
#
25-
# Invoke with `uses: ./.github/workflows/publish.yaml` from a parent workflow.
23+
# 4. Call this workflow from a parent workflow with
24+
# `uses: ./.github/workflows/publish.yaml`.
2625

2726
on:
2827
workflow_call:
2928
inputs:
30-
pypi:
31-
description: 'Publish to `upload.pypi` or `test.pypi`:'
32-
default: 'test.pypi'
29+
test-only:
30+
description: 'Only publish to `test.pypi` if set (default).'
31+
default: true
3332
required: false
34-
type: string
33+
type: boolean
3534
workflow_dispatch:
3635
inputs:
37-
pypi:
38-
description: 'Set to "upload.pypi" or "test.pypi" (default).'
39-
options: ['test.pypi', 'upload.pypi']
40-
type: choice
36+
test-only:
37+
description: 'Only publish to "test.pypi" if set (default).'
38+
default: true
39+
type: boolean
4140

4241
jobs:
4342
publish:
44-
name: Publish to ${{ inputs.pypi }}
43+
name: Publish to ${{ inputs.test-only && 'test.' || '' }}pypi.org
4544
runs-on: ubuntu-latest
4645
environment:
47-
name: publish-${{ inputs.pypi }}
48-
url: https://${{ inputs.pypi }}.org/p/${{ steps.publish.outputs.package-name }}
46+
name: publish-pypi
47+
url: https://${{ inputs.test-only && 'test.' || '' }}pypi.org/p/${{ steps.publish-test.outputs.package-name }}
4948
permissions:
50-
id-token: write # IMPORTANT: mandatory for trusted publishing
49+
id-token: write # Required for trusted publishing
5150
steps:
52-
- uses: glenn20/python-ci/publish@dev
51+
- uses: glenn20/python-ci/publish@v1
52+
id: publish-test
53+
with:
54+
pypi: test.pypi
55+
- uses: glenn20/python-ci/publish@v1
56+
if: ${{ !inputs.test-only }}
5357
id: publish
5458
with:
55-
pypi: ${{ inputs.pypi }}
59+
pypi: upload.pypi

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ run.omit = ["_version.py"]
8383
report.skip_covered = false
8484
append = true
8585

86-
[tool.tox.gh.python] # For Github Actions workflow - see
86+
# For Github Actions workflow - see https://github.com/tox-dev/tox-gh
87+
[tool.tox.gh.python]
8788
"3.13" = ["clean", "typing", "lint", "format", "3.13"]
8889
"3.12" = ["3.12"]
8990
"3.11" = ["3.11"]
@@ -111,7 +112,6 @@ dependency_groups = ["test"] # Everything we need to run the tox tests
111112
labels = ["test"]
112113
package = "editable" # "editable" runs a bit faster then "wheel"
113114
runner = "uv-venv-runner" # We love uv
114-
passenv = ["GITHUB_ACTIONS"] # So conftest.py knows we are running in github actions
115115

116116
[tool.tox.env.3.13]
117117
# Run coverage tests only on the latest python version

0 commit comments

Comments
 (0)