Skip to content

Commit dcf76fe

Browse files
authored
ci: publish to PyPI via OIDC trusted publishing (#46)
* ci: add PyPI publish workflow via OIDC trusted publishing * chore: drop initial release version to 0.0.1 * ci: pass packageVersion from pyproject.toml when regenerating client * refactor: make pyproject.toml the single source of truth for version * ci: drop workflow_dispatch from publish and resolve SDK version at runtime * ci: regenerate __init__.py and patch __version__ post-generate * ci: pin python version, harden tag check, align fallback strings * ci: build wheel and import from installed dist in regen verification
1 parent 3b9010e commit dcf76fe

8 files changed

Lines changed: 128 additions & 63 deletions

File tree

.github/workflows/publish.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: Publish to PyPI
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
jobs:
8+
build:
9+
name: Build distribution
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
13+
14+
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
15+
with:
16+
python-version: '3.12'
17+
18+
- name: Install build tooling
19+
run: python -m pip install --upgrade build twine
20+
21+
- name: Verify tag matches pyproject version
22+
run: |
23+
# Release tags must start with `v` followed by a PEP 440 version (e.g. v1.2.3, v1.2.3a1).
24+
if [[ ! "$GITHUB_REF_NAME" =~ ^v[0-9] ]]; then
25+
echo "Release tag '$GITHUB_REF_NAME' must start with 'v' followed by a digit (e.g. v1.0.0)" >&2
26+
exit 1
27+
fi
28+
tag="${GITHUB_REF_NAME#v}"
29+
pkg_version=$(python -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")
30+
if [ "$tag" != "$pkg_version" ]; then
31+
echo "Release tag ($tag) does not match pyproject.toml version ($pkg_version)" >&2
32+
exit 1
33+
fi
34+
35+
- name: Build sdist and wheel
36+
run: python -m build
37+
38+
- name: Check distribution metadata
39+
run: python -m twine check --strict dist/*
40+
41+
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
42+
with:
43+
name: dist
44+
path: dist/
45+
46+
publish:
47+
name: Publish to PyPI
48+
needs: build
49+
runs-on: ubuntu-latest
50+
environment:
51+
name: pypi
52+
url: https://pypi.org/p/hotdata
53+
permissions:
54+
id-token: write
55+
steps:
56+
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
57+
with:
58+
name: dist
59+
path: dist/
60+
61+
- name: Publish via Trusted Publishing
62+
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0

.github/workflows/regenerate.yml

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ jobs:
1919
with:
2020
token: ${{ steps.app-token.outputs.token }}
2121

22+
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
23+
with:
24+
python-version: '3.12'
25+
2226
- name: Fetch merged OpenAPI spec
2327
env:
2428
GH_TOKEN: ${{ steps.app-token.outputs.token }}
@@ -32,29 +36,54 @@ jobs:
3236
- name: Clean existing source
3337
run: rm -rf src/
3438

39+
- name: Read package version from pyproject.toml
40+
id: pkg
41+
run: |
42+
version=$(python3 -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")
43+
echo "version=$version" >> "$GITHUB_OUTPUT"
44+
3545
- name: Generate client
3646
run: |
3747
npx @openapitools/openapi-generator-cli generate \
3848
-i openapi.yaml \
3949
-g python \
4050
-o . \
4151
-t .openapi-generator-templates \
42-
--additional-properties=packageName=hotdata,projectName=hotdata \
52+
--additional-properties=packageName=hotdata,projectName=hotdata,packageVersion=${{ steps.pkg.outputs.version }} \
4353
--skip-validate-spec
4454
55+
- name: Patch generated __version__ to read from package metadata
56+
run: |
57+
python3 - <<'PY'
58+
import re, pathlib, sys
59+
p = pathlib.Path("hotdata/__init__.py")
60+
src = p.read_text()
61+
replacement = (
62+
'from importlib.metadata import PackageNotFoundError, version as _pkg_version\n'
63+
'\n'
64+
'try:\n'
65+
' __version__ = _pkg_version("hotdata")\n'
66+
'except PackageNotFoundError: # running from a source checkout without install\n'
67+
' __version__ = "0.0.0+unknown"\n'
68+
)
69+
new, n = re.subn(r'^__version__ = "[^"]*"\n', replacement, src, count=1, flags=re.MULTILINE)
70+
if n != 1:
71+
sys.exit("Failed to patch __version__ line in hotdata/__init__.py")
72+
p.write_text(new)
73+
PY
74+
4575
- name: Clean up generated artifacts
4676
run: |
4777
rm -f openapi.yaml
4878
rm -f .github/workflows/python.yml
4979
50-
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
51-
with:
52-
python-version: '3.12'
53-
54-
- name: Verify generated client imports
80+
- name: Verify built wheel installs and imports
5581
run: |
56-
pip install -r requirements.txt
57-
python -c "import hotdata"
82+
python -m pip install --upgrade build
83+
python -m build
84+
python -m pip install dist/*.whl
85+
# cd away from the source tree so the import resolves against the installed wheel.
86+
cd /tmp && python -c "import hotdata; print(hotdata.__version__)"
5887
5988
- name: Create PR
6089
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8

.openapi-generator-ignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
.travis.yml
22
git_push.sh
33
README.md
4+
setup.py

.openapi-generator-templates/configuration.mustache

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ from logging import FileHandler
1111
import multiprocessing
1212
{{/async}}
1313
import sys
14+
from importlib.metadata import PackageNotFoundError, version as _pkg_version
1415
from typing import Any, ClassVar, Dict, List, Literal, Optional, TypedDict, Union
1516
from typing_extensions import NotRequired, Self
1617

@@ -726,12 +727,16 @@ conf = {{{packageName}}}.Configuration(
726727

727728
:return: The report for debugging.
728729
"""
730+
try:
731+
sdk_version = _pkg_version("{{packageName}}")
732+
except PackageNotFoundError:
733+
sdk_version = "0.0.0+unknown"
729734
return "Python SDK Debug Report:\n"\
730735
"OS: {env}\n"\
731736
"Python Version: {pyversion}\n"\
732737
"Version of the API: {{version}}\n"\
733-
"SDK Package Version: {{packageVersion}}".\
734-
format(env=sys.platform, pyversion=sys.version)
738+
"SDK Package Version: {sdk_version}".\
739+
format(env=sys.platform, pyversion=sys.version, sdk_version=sdk_version)
735740

736741
def get_host_settings(self) -> List[HostSetting]:
737742
"""Gets an array of host settings

hotdata/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@
1515
""" # noqa: E501
1616

1717

18-
__version__ = "1.0.0"
18+
from importlib.metadata import PackageNotFoundError, version as _pkg_version
19+
20+
try:
21+
__version__ = _pkg_version("hotdata")
22+
except PackageNotFoundError: # running from a source checkout without install
23+
__version__ = "0.0.0+unknown"
1924

2025
# Define package exports
2126
__all__ = [

hotdata/configuration.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from logging import FileHandler
1818
import multiprocessing
1919
import sys
20+
from importlib.metadata import PackageNotFoundError, version as _pkg_version
2021
from typing import Any, ClassVar, Dict, List, Literal, Optional, TypedDict, Union
2122
from typing_extensions import NotRequired, Self
2223

@@ -539,12 +540,16 @@ def to_debug_report(self) -> str:
539540
540541
:return: The report for debugging.
541542
"""
543+
try:
544+
sdk_version = _pkg_version("hotdata")
545+
except PackageNotFoundError:
546+
sdk_version = "0.0.0+unknown"
542547
return "Python SDK Debug Report:\n"\
543548
"OS: {env}\n"\
544549
"Python Version: {pyversion}\n"\
545550
"Version of the API: 1.0.0\n"\
546-
"SDK Package Version: 1.0.0".\
547-
format(env=sys.platform, pyversion=sys.version)
551+
"SDK Package Version: {sdk_version}".\
552+
format(env=sys.platform, pyversion=sys.version, sdk_version=sdk_version)
548553

549554
def get_host_settings(self) -> List[HostSetting]:
550555
"""Gets an array of host settings

pyproject.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "hotdata"
3-
version = "1.0.0"
3+
version = "0.0.1"
44
description = "Official Python client for the Hotdata API"
55
authors = [
66
{name = "Hotdata",email = "developers@hotdata.dev"},
@@ -36,6 +36,13 @@ mypy = ">= 1.5"
3636
requires = ["setuptools"]
3737
build-backend = "setuptools.build_meta"
3838

39+
[tool.setuptools.packages.find]
40+
include = ["hotdata*"]
41+
exclude = ["test*", "tests*", "docs*"]
42+
43+
[tool.setuptools.package-data]
44+
hotdata = ["py.typed"]
45+
3946
[tool.pylint.'MESSAGES CONTROL']
4047
extension-pkg-whitelist = "pydantic"
4148

setup.py

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

0 commit comments

Comments
 (0)