Skip to content

Commit 64458de

Browse files
Add Automatic Deployment Workflow (#79)
* Add first version of automatic deployment workflow * changed python to python3 * pin version of github action * pin version of github action * pinning version * setuptools version * deactivate twine check * pin gh-action-pypi-publish to newest version * testing other ubuntu version * refactored workflow
1 parent bead445 commit 64458de

3 files changed

Lines changed: 156 additions & 2 deletions

File tree

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
name: Publish to PyPI
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
repository:
7+
description: "Target index"
8+
type: choice
9+
options: [pypi, testpypi]
10+
default: testpypi
11+
ref:
12+
description: "Git ref to build (branch/tag/SHA). Leave empty to use the UI-selected ref."
13+
required: false
14+
default: ""
15+
16+
concurrency:
17+
group: pypi-publish
18+
cancel-in-progress: false
19+
20+
jobs:
21+
build:
22+
runs-on: ubuntu-latest
23+
container:
24+
image: cubertgmbh/cuvis_pyil:3.4.1-ubuntu24.04
25+
26+
permissions:
27+
contents: read
28+
29+
steps:
30+
- name: Checkout
31+
uses: actions/checkout@v5
32+
with:
33+
ref: ${{ inputs.ref || github.ref }}
34+
35+
# Optional hard stop: only allow repo admins to proceed
36+
- name: Enforce admin-only trigger
37+
uses: actions/github-script@v7
38+
with:
39+
script: |
40+
const owner = context.repo.owner;
41+
const repo = context.repo.repo;
42+
const username = context.actor;
43+
44+
const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
45+
owner, repo, username
46+
});
47+
48+
core.info(`Actor permission: ${data.permission}`);
49+
if (data.permission !== "admin") {
50+
core.setFailed(`Only repository admins may publish. (${username} has: ${data.permission})`);
51+
}
52+
53+
- name: Install build tooling
54+
run: python3 -m pip install -U build twine
55+
56+
- name: Read package name/version from pyproject.toml
57+
id: meta
58+
run: |
59+
python3 - <<'PY'
60+
import sys, json
61+
try:
62+
import tomllib # py3.11+
63+
except ModuleNotFoundError:
64+
import tomli as tomllib # fallback if needed
65+
from pathlib import Path
66+
67+
data = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))
68+
proj = data.get("project", {})
69+
name = proj.get("name")
70+
version = proj.get("version")
71+
if not name or not version:
72+
print("Missing [project].name or [project].version in pyproject.toml", file=sys.stderr)
73+
sys.exit(2)
74+
75+
print(f"name={name}")
76+
print(f"version={version}")
77+
with open("pkg_meta.json", "w", encoding="utf-8") as f:
78+
json.dump({"name": name, "version": version}, f)
79+
PY
80+
echo "name=$(python3 -c "import json; print(json.load(open('pkg_meta.json'))['name'])")" >> "$GITHUB_OUTPUT"
81+
echo "version=$(python3 -c "import json; print(json.load(open('pkg_meta.json'))['version'])")" >> "$GITHUB_OUTPUT"
82+
83+
- name: Abort if this version already exists on the target index
84+
env:
85+
NAME: ${{ steps.meta.outputs.name }}
86+
VERSION: ${{ steps.meta.outputs.version }}
87+
TARGET: ${{ inputs.repository }}
88+
run: |
89+
python3 - <<'PY'
90+
import json, os, sys, urllib.request, urllib.error
91+
92+
name = os.environ["NAME"]
93+
version = os.environ["VERSION"]
94+
target = os.environ["TARGET"]
95+
96+
base = "https://pypi.org/pypi" if target == "pypi" else "https://test.pypi.org/pypi"
97+
url = f"{base}/{name}/json"
98+
99+
try:
100+
with urllib.request.urlopen(url) as resp:
101+
data = json.load(resp)
102+
except urllib.error.HTTPError as e:
103+
if e.code == 404:
104+
print(f"{name} not found on {target}; OK to publish {version}.")
105+
sys.exit(0)
106+
raise
107+
108+
releases = data.get("releases", {})
109+
if version in releases and releases[version]:
110+
print(f"Version {name}=={version} already exists on {target}. Aborting.")
111+
sys.exit(1)
112+
113+
print(f"Version {name}=={version} not present on {target}; OK to publish.")
114+
PY
115+
116+
- name: Build sdist and wheel
117+
run: |
118+
python3 -m build
119+
120+
- name: Upload build artifacts
121+
uses: actions/upload-artifact@v4
122+
with:
123+
name: python-package-distributions
124+
path: dist/
125+
126+
publish:
127+
needs: build
128+
runs-on: ubuntu-latest
129+
130+
environment: ${{ inputs.repository }}
131+
132+
permissions:
133+
contents: read
134+
id-token: write # required for PyPI Trusted Publishing (OIDC)
135+
136+
steps:
137+
- name: Download build artifacts
138+
uses: actions/download-artifact@v4
139+
with:
140+
name: python-package-distributions
141+
path: dist/
142+
143+
- name: Publish to PyPI
144+
uses: pypa/gh-action-pypi-publish@v1.13.0
145+
with:
146+
repository-url: ${{ inputs.repository == 'pypi' && 'https://upload.pypi.org/legacy/' || 'https://test.pypi.org/legacy/' }}

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/dist
22
/build
3-
/.venv
3+
/.venv*
44
/.eggs
55
/cuvis.egg-info
66
/MANIFEST.in
@@ -9,3 +9,7 @@
99
/venv
1010
/cuvis/__pycache__
1111
/.venv310
12+
13+
/tests/__pycache__
14+
/.claude
15+

prebuild.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44

55
def get_git_commit_hash():
66
try:
7-
return subprocess.check_output(["git", "rev-parse", "HEAD"]).decode("ascii").strip()
7+
return (
8+
subprocess.check_output(["git", "rev-parse", "HEAD"])
9+
.decode("ascii")
10+
.strip()
11+
)
812
except subprocess.CalledProcessError:
913
return "unknown"
1014

0 commit comments

Comments
 (0)