From 890f2f6c7d50f863ace843ed4eeeb363d8c0d49c Mon Sep 17 00:00:00 2001 From: Zac Farrell Date: Sat, 25 Apr 2026 08:15:08 -0700 Subject: [PATCH 1/8] ci: add PyPI publish workflow via OIDC trusted publishing --- .github/workflows/publish.yml | 59 +++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..e02da7c --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,59 @@ +name: Publish to PyPI + +on: + release: + types: [published] + workflow_dispatch: + +jobs: + build: + name: Build distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 + with: + python-version: '3.12' + + - name: Install build tooling + run: python -m pip install --upgrade build twine + + - name: Verify tag matches pyproject version + if: github.event_name == 'release' + run: | + tag="${GITHUB_REF_NAME#v}" + pkg_version=$(python -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])") + if [ "$tag" != "$pkg_version" ]; then + echo "Release tag ($tag) does not match pyproject.toml version ($pkg_version)" >&2 + exit 1 + fi + + - name: Build sdist and wheel + run: python -m build + + - name: Check distribution metadata + run: python -m twine check --strict dist/* + + - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 + with: + name: dist + path: dist/ + + publish: + name: Publish to PyPI + needs: build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/hotdata + permissions: + id-token: write + steps: + - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 + with: + name: dist + path: dist/ + + - name: Publish via Trusted Publishing + uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 From 27df97be5bd824f048ca341287157dd3d719f823 Mon Sep 17 00:00:00 2001 From: Zac Farrell Date: Sat, 25 Apr 2026 09:24:40 -0700 Subject: [PATCH 2/8] chore: drop initial release version to 0.0.1 --- hotdata/__init__.py | 2 +- hotdata/configuration.py | 2 +- pyproject.toml | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hotdata/__init__.py b/hotdata/__init__.py index 053e085..4252ec4 100644 --- a/hotdata/__init__.py +++ b/hotdata/__init__.py @@ -15,7 +15,7 @@ """ # noqa: E501 -__version__ = "1.0.0" +__version__ = "0.0.1" # Define package exports __all__ = [ diff --git a/hotdata/configuration.py b/hotdata/configuration.py index 904220a..a13edf0 100644 --- a/hotdata/configuration.py +++ b/hotdata/configuration.py @@ -543,7 +543,7 @@ def to_debug_report(self) -> str: "OS: {env}\n"\ "Python Version: {pyversion}\n"\ "Version of the API: 1.0.0\n"\ - "SDK Package Version: 1.0.0".\ + "SDK Package Version: 0.0.1".\ format(env=sys.platform, pyversion=sys.version) def get_host_settings(self) -> List[HostSetting]: diff --git a/pyproject.toml b/pyproject.toml index a2c7812..ea487e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "hotdata" -version = "1.0.0" +version = "0.0.1" description = "Official Python client for the Hotdata API" authors = [ {name = "Hotdata",email = "developers@hotdata.dev"}, diff --git a/setup.py b/setup.py index 5fbaf38..971782b 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ # prerequisite: setuptools # http://pypi.python.org/pypi/setuptools NAME = "hotdata" -VERSION = "1.0.0" +VERSION = "0.0.1" PYTHON_REQUIRES = ">= 3.9" REQUIRES = [ "urllib3 >= 2.1.0, < 3.0.0", From 3d64dd6c8f522ec1f95ead0ecc8dd689b28fbf4f Mon Sep 17 00:00:00 2001 From: Zac Farrell Date: Sat, 25 Apr 2026 09:27:33 -0700 Subject: [PATCH 3/8] ci: pass packageVersion from pyproject.toml when regenerating client --- .github/workflows/regenerate.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/regenerate.yml b/.github/workflows/regenerate.yml index 4b3d639..b314ad7 100644 --- a/.github/workflows/regenerate.yml +++ b/.github/workflows/regenerate.yml @@ -32,13 +32,19 @@ jobs: - name: Clean existing source run: rm -rf src/ + - name: Read package version from pyproject.toml + id: pkg + run: | + version=$(python3 -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])") + echo "version=$version" >> "$GITHUB_OUTPUT" + - name: Generate client run: | npx @openapitools/openapi-generator-cli generate \ -i openapi.yaml \ -g python \ -o . \ - --additional-properties=packageName=hotdata,projectName=hotdata \ + --additional-properties=packageName=hotdata,projectName=hotdata,packageVersion=${{ steps.pkg.outputs.version }} \ --skip-validate-spec - name: Clean up generated artifacts From bc56d7df5d4cc1e5d9716229fbdbfb1be5d7b17d Mon Sep 17 00:00:00 2001 From: Zac Farrell Date: Sat, 25 Apr 2026 09:44:05 -0700 Subject: [PATCH 4/8] refactor: make pyproject.toml the single source of truth for version --- .openapi-generator-ignore | 2 ++ hotdata/__init__.py | 7 +++++- pyproject.toml | 7 ++++++ setup.py | 49 --------------------------------------- 4 files changed, 15 insertions(+), 50 deletions(-) delete mode 100644 setup.py diff --git a/.openapi-generator-ignore b/.openapi-generator-ignore index 53f5e9a..c6898a6 100644 --- a/.openapi-generator-ignore +++ b/.openapi-generator-ignore @@ -1,3 +1,5 @@ .travis.yml git_push.sh README.md +setup.py +hotdata/__init__.py diff --git a/hotdata/__init__.py b/hotdata/__init__.py index 4252ec4..4ba6df7 100644 --- a/hotdata/__init__.py +++ b/hotdata/__init__.py @@ -15,7 +15,12 @@ """ # noqa: E501 -__version__ = "0.0.1" +from importlib.metadata import PackageNotFoundError, version as _pkg_version + +try: + __version__ = _pkg_version("hotdata") +except PackageNotFoundError: # running from a source checkout without install + __version__ = "0.0.0+unknown" # Define package exports __all__ = [ diff --git a/pyproject.toml b/pyproject.toml index ea487e3..2e860b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,13 @@ mypy = ">= 1.5" requires = ["setuptools"] build-backend = "setuptools.build_meta" +[tool.setuptools.packages.find] +include = ["hotdata*"] +exclude = ["test*", "tests*", "docs*"] + +[tool.setuptools.package-data] +hotdata = ["py.typed"] + [tool.pylint.'MESSAGES CONTROL'] extension-pkg-whitelist = "pydantic" diff --git a/setup.py b/setup.py deleted file mode 100644 index 971782b..0000000 --- a/setup.py +++ /dev/null @@ -1,49 +0,0 @@ -""" - Hotdata API - - Powerful data platform API for datasets, queries, and analytics. - - The version of the OpenAPI document: 1.0.0 - Contact: developers@hotdata.dev - Generated by OpenAPI Generator (https://openapi-generator.tech) - - Do not edit the class manually. -""" # noqa: E501 - - -from setuptools import setup, find_packages # noqa: H301 - -# To install the library, run the following -# -# python setup.py install -# -# prerequisite: setuptools -# http://pypi.python.org/pypi/setuptools -NAME = "hotdata" -VERSION = "0.0.1" -PYTHON_REQUIRES = ">= 3.9" -REQUIRES = [ - "urllib3 >= 2.1.0, < 3.0.0", - "python-dateutil >= 2.8.2", - "pydantic >= 2", - "typing-extensions >= 4.7.1", -] - -setup( - name=NAME, - version=VERSION, - description="Official Python client for the Hotdata API", - author="Hotdata", - author_email="developers@hotdata.dev", - url="https://github.com/hotdata-dev/sdk-python", - keywords=["hotdata", "api-client", "data-platform"], - install_requires=REQUIRES, - packages=find_packages(exclude=["test", "tests"]), - include_package_data=True, - license="MIT", - long_description_content_type='text/markdown', - long_description="""\ - Powerful data platform API for datasets, queries, and analytics. - """, # noqa: E501 - package_data={"hotdata": ["py.typed"]}, -) From 1e6168ab8e2bf74c810d890e99404fa81a0402c1 Mon Sep 17 00:00:00 2001 From: Zac Farrell Date: Sat, 25 Apr 2026 09:55:06 -0700 Subject: [PATCH 5/8] ci: drop workflow_dispatch from publish and resolve SDK version at runtime --- .github/workflows/publish.yml | 2 -- .openapi-generator-templates/configuration.mustache | 9 +++++++-- hotdata/configuration.py | 9 +++++++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e02da7c..95b3ec1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,7 +3,6 @@ name: Publish to PyPI on: release: types: [published] - workflow_dispatch: jobs: build: @@ -20,7 +19,6 @@ jobs: run: python -m pip install --upgrade build twine - name: Verify tag matches pyproject version - if: github.event_name == 'release' run: | tag="${GITHUB_REF_NAME#v}" pkg_version=$(python -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])") diff --git a/.openapi-generator-templates/configuration.mustache b/.openapi-generator-templates/configuration.mustache index fd1a577..44bb716 100644 --- a/.openapi-generator-templates/configuration.mustache +++ b/.openapi-generator-templates/configuration.mustache @@ -11,6 +11,7 @@ from logging import FileHandler import multiprocessing {{/async}} import sys +from importlib.metadata import PackageNotFoundError, version as _pkg_version from typing import Any, ClassVar, Dict, List, Literal, Optional, TypedDict, Union from typing_extensions import NotRequired, Self @@ -726,12 +727,16 @@ conf = {{{packageName}}}.Configuration( :return: The report for debugging. """ + try: + sdk_version = _pkg_version("{{packageName}}") + except PackageNotFoundError: + sdk_version = "unknown" return "Python SDK Debug Report:\n"\ "OS: {env}\n"\ "Python Version: {pyversion}\n"\ "Version of the API: {{version}}\n"\ - "SDK Package Version: {{packageVersion}}".\ - format(env=sys.platform, pyversion=sys.version) + "SDK Package Version: {sdk_version}".\ + format(env=sys.platform, pyversion=sys.version, sdk_version=sdk_version) def get_host_settings(self) -> List[HostSetting]: """Gets an array of host settings diff --git a/hotdata/configuration.py b/hotdata/configuration.py index a13edf0..5b62d51 100644 --- a/hotdata/configuration.py +++ b/hotdata/configuration.py @@ -17,6 +17,7 @@ from logging import FileHandler import multiprocessing import sys +from importlib.metadata import PackageNotFoundError, version as _pkg_version from typing import Any, ClassVar, Dict, List, Literal, Optional, TypedDict, Union from typing_extensions import NotRequired, Self @@ -539,12 +540,16 @@ def to_debug_report(self) -> str: :return: The report for debugging. """ + try: + sdk_version = _pkg_version("hotdata") + except PackageNotFoundError: + sdk_version = "unknown" return "Python SDK Debug Report:\n"\ "OS: {env}\n"\ "Python Version: {pyversion}\n"\ "Version of the API: 1.0.0\n"\ - "SDK Package Version: 0.0.1".\ - format(env=sys.platform, pyversion=sys.version) + "SDK Package Version: {sdk_version}".\ + format(env=sys.platform, pyversion=sys.version, sdk_version=sdk_version) def get_host_settings(self) -> List[HostSetting]: """Gets an array of host settings From 302d78af9942739d991da2b7d2742c6460583b8e Mon Sep 17 00:00:00 2001 From: Zac Farrell Date: Sat, 25 Apr 2026 10:03:48 -0700 Subject: [PATCH 6/8] ci: regenerate __init__.py and patch __version__ post-generate --- .github/workflows/regenerate.yml | 20 ++++++++++++++++++++ .openapi-generator-ignore | 1 - 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/workflows/regenerate.yml b/.github/workflows/regenerate.yml index b259661..2732881 100644 --- a/.github/workflows/regenerate.yml +++ b/.github/workflows/regenerate.yml @@ -48,6 +48,26 @@ jobs: --additional-properties=packageName=hotdata,projectName=hotdata,packageVersion=${{ steps.pkg.outputs.version }} \ --skip-validate-spec + - name: Patch generated __version__ to read from package metadata + run: | + python3 - <<'PY' + import re, pathlib, sys + p = pathlib.Path("hotdata/__init__.py") + src = p.read_text() + replacement = ( + 'from importlib.metadata import PackageNotFoundError, version as _pkg_version\n' + '\n' + 'try:\n' + ' __version__ = _pkg_version("hotdata")\n' + 'except PackageNotFoundError: # running from a source checkout without install\n' + ' __version__ = "0.0.0+unknown"\n' + ) + new, n = re.subn(r'^__version__ = "[^"]*"\n', replacement, src, count=1, flags=re.MULTILINE) + if n != 1: + sys.exit("Failed to patch __version__ line in hotdata/__init__.py") + p.write_text(new) + PY + - name: Clean up generated artifacts run: | rm -f openapi.yaml diff --git a/.openapi-generator-ignore b/.openapi-generator-ignore index c6898a6..dbd3899 100644 --- a/.openapi-generator-ignore +++ b/.openapi-generator-ignore @@ -2,4 +2,3 @@ git_push.sh README.md setup.py -hotdata/__init__.py From 810449bed6461fc986e17c369c780a1c35ce0214 Mon Sep 17 00:00:00 2001 From: Zac Farrell Date: Sat, 25 Apr 2026 10:06:20 -0700 Subject: [PATCH 7/8] ci: pin python version, harden tag check, align fallback strings --- .github/workflows/publish.yml | 5 +++++ .github/workflows/regenerate.yml | 8 ++++---- .openapi-generator-templates/configuration.mustache | 2 +- hotdata/configuration.py | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 95b3ec1..7d57b85 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -20,6 +20,11 @@ jobs: - name: Verify tag matches pyproject version run: | + # Release tags must start with `v` followed by a PEP 440 version (e.g. v1.2.3, v1.2.3a1). + if [[ ! "$GITHUB_REF_NAME" =~ ^v[0-9] ]]; then + echo "Release tag '$GITHUB_REF_NAME' must start with 'v' followed by a digit (e.g. v1.0.0)" >&2 + exit 1 + fi tag="${GITHUB_REF_NAME#v}" pkg_version=$(python -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])") if [ "$tag" != "$pkg_version" ]; then diff --git a/.github/workflows/regenerate.yml b/.github/workflows/regenerate.yml index 2732881..3f28489 100644 --- a/.github/workflows/regenerate.yml +++ b/.github/workflows/regenerate.yml @@ -19,6 +19,10 @@ jobs: with: token: ${{ steps.app-token.outputs.token }} + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 + with: + python-version: '3.12' + - name: Fetch merged OpenAPI spec env: GH_TOKEN: ${{ steps.app-token.outputs.token }} @@ -73,10 +77,6 @@ jobs: rm -f openapi.yaml rm -f .github/workflows/python.yml - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 - with: - python-version: '3.12' - - name: Verify generated client imports run: | pip install -r requirements.txt diff --git a/.openapi-generator-templates/configuration.mustache b/.openapi-generator-templates/configuration.mustache index 44bb716..3a22364 100644 --- a/.openapi-generator-templates/configuration.mustache +++ b/.openapi-generator-templates/configuration.mustache @@ -730,7 +730,7 @@ conf = {{{packageName}}}.Configuration( try: sdk_version = _pkg_version("{{packageName}}") except PackageNotFoundError: - sdk_version = "unknown" + sdk_version = "0.0.0+unknown" return "Python SDK Debug Report:\n"\ "OS: {env}\n"\ "Python Version: {pyversion}\n"\ diff --git a/hotdata/configuration.py b/hotdata/configuration.py index 5b62d51..c742aab 100644 --- a/hotdata/configuration.py +++ b/hotdata/configuration.py @@ -543,7 +543,7 @@ def to_debug_report(self) -> str: try: sdk_version = _pkg_version("hotdata") except PackageNotFoundError: - sdk_version = "unknown" + sdk_version = "0.0.0+unknown" return "Python SDK Debug Report:\n"\ "OS: {env}\n"\ "Python Version: {pyversion}\n"\ From fbd31c8ec9ee647bc2f116d9944e22dc44fdb18a Mon Sep 17 00:00:00 2001 From: Zac Farrell Date: Sat, 25 Apr 2026 10:22:20 -0700 Subject: [PATCH 8/8] ci: build wheel and import from installed dist in regen verification --- .github/workflows/regenerate.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/regenerate.yml b/.github/workflows/regenerate.yml index 3f28489..89f51cd 100644 --- a/.github/workflows/regenerate.yml +++ b/.github/workflows/regenerate.yml @@ -77,10 +77,13 @@ jobs: rm -f openapi.yaml rm -f .github/workflows/python.yml - - name: Verify generated client imports + - name: Verify built wheel installs and imports run: | - pip install -r requirements.txt - python -c "import hotdata" + python -m pip install --upgrade build + python -m build + python -m pip install dist/*.whl + # cd away from the source tree so the import resolves against the installed wheel. + cd /tmp && python -c "import hotdata; print(hotdata.__version__)" - name: Create PR uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8