diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..7d57b85 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,62 @@ +name: Publish to PyPI + +on: + release: + types: [published] + +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 + 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 + 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 diff --git a/.github/workflows/regenerate.yml b/.github/workflows/regenerate.yml index f284c77..89f51cd 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 }} @@ -32,6 +36,12 @@ 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 \ @@ -39,22 +49,41 @@ jobs: -g python \ -o . \ -t .openapi-generator-templates \ - --additional-properties=packageName=hotdata,projectName=hotdata \ + --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 rm -f .github/workflows/python.yml - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 - with: - python-version: '3.12' - - - 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 diff --git a/.openapi-generator-ignore b/.openapi-generator-ignore index 53f5e9a..dbd3899 100644 --- a/.openapi-generator-ignore +++ b/.openapi-generator-ignore @@ -1,3 +1,4 @@ .travis.yml git_push.sh README.md +setup.py diff --git a/.openapi-generator-templates/configuration.mustache b/.openapi-generator-templates/configuration.mustache index fd1a577..3a22364 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 = "0.0.0+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/__init__.py b/hotdata/__init__.py index 053e085..4ba6df7 100644 --- a/hotdata/__init__.py +++ b/hotdata/__init__.py @@ -15,7 +15,12 @@ """ # noqa: E501 -__version__ = "1.0.0" +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/hotdata/configuration.py b/hotdata/configuration.py index 904220a..c742aab 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 = "0.0.0+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: 1.0.0".\ - 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/pyproject.toml b/pyproject.toml index a2c7812..2e860b5 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"}, @@ -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 5fbaf38..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 = "1.0.0" -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"]}, -)