Skip to content

Commit 3ad0dc8

Browse files
authored
feat: Add publish pypi (#9)
1 parent 4428d01 commit 3ad0dc8

2 files changed

Lines changed: 155 additions & 0 deletions

File tree

.github/workflows/publish-pypi.yml

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
name: Upload Python Package
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
python-version:
7+
description: 'Python version to build with'
8+
required: false
9+
default: '3.x'
10+
type: string
11+
package-name:
12+
description: 'PyPI package name, used to construct the PyPI URL (e.g. my-package)'
13+
required: true
14+
type: string
15+
environment-name:
16+
description: 'GitHub environment to use for the PyPI publish job'
17+
required: false
18+
default: 'pypi'
19+
type: string
20+
working-directory:
21+
description: 'Working directory for the build, relative to the repo root. Useful for monorepos.'
22+
required: false
23+
default: '.'
24+
type: string
25+
use-uv:
26+
description: 'Use uv to build the package instead of pip + build'
27+
required: false
28+
default: false
29+
type: boolean
30+
31+
jobs:
32+
build:
33+
name: Build distribution
34+
runs-on: ubuntu-latest
35+
permissions:
36+
contents: read
37+
# Apply working-directory to all run steps in this job
38+
defaults:
39+
run:
40+
working-directory: ${{ inputs.working-directory }}
41+
42+
steps:
43+
- uses: actions/checkout@v4
44+
45+
# When using uv, Python is managed by uv itself via the python-version input.
46+
# When using pip, setup-python handles it instead.
47+
- name: Set up Python
48+
if: ${{ !inputs.use-uv }}
49+
uses: actions/setup-python@v5
50+
with:
51+
python-version: ${{ inputs.python-version }}
52+
53+
- name: Set up uv
54+
if: inputs.use-uv
55+
uses: astral-sh/setup-uv@v5
56+
with:
57+
python-version: ${{ inputs.python-version }}
58+
59+
- name: Install build dependencies
60+
if: ${{ !inputs.use-uv }}
61+
run: |
62+
python -m pip install --upgrade pip
63+
pip install build
64+
65+
- name: Build package
66+
run: ${{ inputs.use-uv && 'uv build' || 'python -m build' }}
67+
68+
- name: Store the distribution packages
69+
uses: actions/upload-artifact@v4
70+
with:
71+
name: python-package-distributions
72+
# upload-artifact paths are relative to the workspace root, not working-directory,
73+
# so we need to include the working-directory prefix explicitly.
74+
path: ${{ inputs.working-directory }}/dist/
75+
76+
publish-to-pypi:
77+
name: Publish Python distribution to PyPI
78+
needs: build
79+
runs-on: ubuntu-latest
80+
environment:
81+
name: ${{ inputs.environment-name }}
82+
url: https://pypi.org/p/${{ inputs.package-name }}
83+
permissions:
84+
id-token: write # required for OIDC-based PyPI publishing (no API token needed)
85+
86+
steps:
87+
- name: Download all the dists
88+
uses: actions/download-artifact@v4
89+
with:
90+
name: python-package-distributions
91+
path: dist/
92+
93+
- name: Publish distribution to PyPI
94+
uses: pypa/gh-action-pypi-publish@release/v1

docs/publish-pypi.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Publish Python Package to PyPI
2+
3+
Reusable workflow that builds a Python package and publishes it to [PyPI](https://pypi.org) using OIDC trusted publishing (no API token required).
4+
5+
The workflow has two jobs:
6+
7+
1. **Build** — runs `python -m build` and uploads the resulting `dist/` as a workflow artifact
8+
2. **Publish** — downloads the artifact and publishes to PyPI via `pypa/gh-action-pypi-publish`
9+
10+
## Inputs
11+
12+
| Input | Required | Default | Description |
13+
|----------------------|----------|----------|--------------------------------------------------------------------------------------|
14+
| `package-name` | Yes || PyPI package name, used to construct the PyPI URL (e.g. `my-package`). |
15+
| `python-version` | No | `'3.x'` | Python version to build with. |
16+
| `environment-name` | No | `'pypi'` | GitHub environment to use for the publish job. |
17+
| `working-directory` | No | `'.'` | Directory containing the package, relative to repo root. Useful for monorepos. |
18+
| `use-uv` | No | `false` | Use `uv build` instead of `pip + build`. Faster builds; uv manages Python itself. |
19+
20+
## PyPI Trusted Publishing (OIDC)
21+
22+
This workflow uses [PyPI trusted publishing](https://docs.pypi.org/trusted-publishers/) rather than an API token. Before using this workflow you must configure a trusted publisher on PyPI for your package:
23+
24+
- **Publisher:** GitHub Actions
25+
- **Repository:** your repo (e.g. `Health-Informatics-UoN/my-repo`)
26+
- **Workflow filename:** the caller workflow filename (e.g. `release.yml`)
27+
- **Environment:** matches the `environment-name` input (default: `pypi`)
28+
29+
## Usage
30+
31+
Typically chained after `semantic-release` so publishing only happens on a new release:
32+
33+
```yaml
34+
jobs:
35+
release:
36+
uses: health-informatics-uon/workflows/.github/workflows/semantic-release.yml@main
37+
secrets: inherit
38+
39+
publish-pypi:
40+
needs: release
41+
if: needs.release.outputs.release-created == 'true'
42+
uses: health-informatics-uon/workflows/.github/workflows/publish-pypi.yml@main
43+
with:
44+
package-name: my-package
45+
```
46+
47+
### With uv
48+
49+
```yaml
50+
with:
51+
package-name: my-package
52+
use-uv: true
53+
```
54+
55+
### Monorepo
56+
57+
```yaml
58+
with:
59+
package-name: my-package
60+
working-directory: packages/my-package
61+
```

0 commit comments

Comments
 (0)