ci: add Python SDK publish workflow with version validation#700
Conversation
- Trigger on python-sdk-v* tags (dedicated Python SDK version tag) - Strong version check: tag version must match pyproject.toml and __init__.py - Detect sdk/python/ changes since previous python-sdk-v* tag - Run tests, build, twine check, then publish to PyPI - Skip publish if no sdk/python/ changes detected
| echo "Tag version: $TAG_VERSION" | ||
|
|
||
| # Extract version from pyproject.toml | ||
| PYPROJECT_VERSION=$(grep -m1 '^version' pyproject.toml | sed 's/version *= *"\(.*\)"/\1/') |
There was a problem hiding this comment.
Fragile version extraction: The pattern ^version matches any line starting with version (e.g., version_format, or a value under [tool.*] sections). Consider using Python's tomllib (available in Python 3.11) for structured TOML parsing:
PYPROJECT_VERSION=$(python3 -c "import tomllib; f=open('pyproject.toml','rb'); print(tomllib.load(f)['project']['version'])")| echo "pyproject.toml version: $PYPROJECT_VERSION" | ||
|
|
||
| # Extract version from __init__.py | ||
| INIT_VERSION=$(grep -m1 '__version__' cubesandbox/__init__.py | sed 's/.*__version__ *= *"\(.*\)".*/\1/') |
There was a problem hiding this comment.
Fragile version extraction: grep -m1 '__version__' matches comment lines containing __version__ (e.g., # Check __version__ before release). Anchor with ^ to only match actual assignments:
INIT_VERSION=$(grep -m1 '^__version__' cubesandbox/__init__.py | sed 's/^__version__ *= *"\(.*\)".*/\1/')| if: steps.check_changes.outputs.changed == 'true' | ||
| working-directory: sdk/python | ||
| run: | | ||
| pip install -e ".[dev]" 2>/dev/null || pip install -e . |
There was a problem hiding this comment.
Error silencing: 2>/dev/null discards ALL stderr from the first pip install, including real failure messages. If .[dev] install fails and the fallback pip install -e . succeeds (installing without test dependencies), subsequent python -m pytest will fail with a confusing ModuleNotFoundError (or worse — silently pass without coverage). Remove the 2>/dev/null so real errors surface immediately.
|
|
||
| - name: Publish to PyPI | ||
| if: steps.check_changes.outputs.changed == 'true' | ||
| uses: pypa/gh-action-pypi-publish@release/v1 |
There was a problem hiding this comment.
Mutable reference: release/v1 is a floating branch whose commit can change without notice. For a step handling authentication credentials, pin to an immutable commit SHA. Dependabot can manage updates.
| uses: pypa/gh-action-pypi-publish@release/v1 | ||
| with: | ||
| packages-dir: sdk/python/dist/ | ||
| password: ${{ secrets.CUBE_PYPI_TOKEN }} |
There was a problem hiding this comment.
Consider OIDC trusted publishing: Using a long-lived PyPI token creates a permanent credential that, if leaked, allows anyone to publish malicious packages under the project name. PyPI's OIDC trusted publishing eliminates this risk — pypa/gh-action-pypi-publish supports it natively when no password is provided. Add id-token: write to the job's permissions block and remove this line.
Summary
Add
.github/workflows/publish-python-sdk.ymlto automate publishing the Python SDK to PyPI.Changes from PR #638
v*→python-sdk-v*(dedicated Python SDK version tag, decoupled from repo-level tags)pyproject.tomlversion ANDcubesandbox/__init__.py__version__— fails fast if mismatchedpython-sdk-v*tag (notv*), only publishes whensdk/python/has actual changesWorkflow steps
python-sdk-v*tag pushpyproject.tomland__init__.py— exit 1 on mismatchsdk/python/since lastpython-sdk-v*tag — skip if no changespython -m build)twine check)Required secrets
CUBE_PYPI_TOKEN— PyPI upload tokenTag convention
Use tags like
python-sdk-v0.3.0where0.3.0matches the version inpyproject.toml.Closes #638