Repository navigation
feat(ci): publish Python SDK to PyPI via trusted publishing #454
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,70 @@ | ||||||||||||||||
| name: Publish Python SDK | ||||||||||||||||
|
|
||||||||||||||||
| on: | ||||||||||||||||
| push: | ||||||||||||||||
| branches: [main] | ||||||||||||||||
| paths: | ||||||||||||||||
| - "packages/python-sdk/pyproject.toml" | ||||||||||||||||
| - "packages/python-sdk/agentstate/**" | ||||||||||||||||
| workflow_dispatch: | ||||||||||||||||
| inputs: | ||||||||||||||||
| dry_run: | ||||||||||||||||
| description: "Dry run (skip actual publish)" | ||||||||||||||||
| required: false | ||||||||||||||||
| default: "false" | ||||||||||||||||
|
|
||||||||||||||||
| permissions: | ||||||||||||||||
| contents: read | ||||||||||||||||
| id-token: write | ||||||||||||||||
|
|
||||||||||||||||
| jobs: | ||||||||||||||||
| publish: | ||||||||||||||||
| name: Build & Publish | ||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||
| defaults: | ||||||||||||||||
| run: | ||||||||||||||||
| working-directory: packages/python-sdk | ||||||||||||||||
|
|
||||||||||||||||
| steps: | ||||||||||||||||
| - uses: actions/checkout@v7 | ||||||||||||||||
|
|
||||||||||||||||
| - uses: actions/setup-python@v7 | ||||||||||||||||
| with: | ||||||||||||||||
| python-version: "3.14" | ||||||||||||||||
|
|
||||||||||||||||
| - name: Install test dependencies | ||||||||||||||||
| run: python -m pip install -e ".[dev]" | ||||||||||||||||
|
|
||||||||||||||||
| - name: Unit tests | ||||||||||||||||
| run: python -m pytest -q | ||||||||||||||||
|
|
||||||||||||||||
| - name: Install build tooling | ||||||||||||||||
| run: python -m pip install build | ||||||||||||||||
|
|
||||||||||||||||
| - name: Build distributions | ||||||||||||||||
| run: python -m build | ||||||||||||||||
|
|
||||||||||||||||
| - name: Check if version already published | ||||||||||||||||
| id: check-version | ||||||||||||||||
| run: | | ||||||||||||||||
| PKG_NAME=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['name'])") | ||||||||||||||||
| PKG_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])") | ||||||||||||||||
| echo "pkg_name=${PKG_NAME}" >> "$GITHUB_OUTPUT" | ||||||||||||||||
| echo "pkg_version=${PKG_VERSION}" >> "$GITHUB_OUTPUT" | ||||||||||||||||
| if curl -sf "https://pypi.org/pypi/${PKG_NAME}/${PKG_VERSION}/json" > /dev/null; then | ||||||||||||||||
| echo "already_published=true" >> "$GITHUB_OUTPUT" | ||||||||||||||||
| echo "::notice::${PKG_NAME}==${PKG_VERSION} already on PyPI — skipping publish" | ||||||||||||||||
| else | ||||||||||||||||
| echo "already_published=false" >> "$GITHUB_OUTPUT" | ||||||||||||||||
| echo "Publishing ${PKG_NAME}==${PKG_VERSION}" | ||||||||||||||||
| fi | ||||||||||||||||
|
|
||||||||||||||||
| - name: Publish (dry run) | ||||||||||||||||
| if: steps.check-version.outputs.already_published == 'false' && github.event.inputs.dry_run == 'true' | ||||||||||||||||
| run: echo "::notice::Dry run — skipping publish of ${{ steps.check-version.outputs.pkg_name }}==${{ steps.check-version.outputs.pkg_version }}" | ||||||||||||||||
|
|
||||||||||||||||
| - name: Publish | ||||||||||||||||
| if: steps.check-version.outputs.already_published == 'false' && github.event.inputs.dry_run != 'true' | ||||||||||||||||
| uses: pypa/gh-action-pypi-publish@release/v1 | ||||||||||||||||
| with: | ||||||||||||||||
| packages-to-upload: packages/python-sdk/dist/* | ||||||||||||||||
|
Comment on lines
+68
to
+70
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): The check-then-publish sequence is not atomic: two workflow runs for the same package version can both receive a 404 from PyPI and then both attempt to publish, causing one run to fail with a duplicate-release error. Triggers: When multiple pushes or manual dispatches for the same version run concurrently. Suggested fix: Add workflow concurrency serialization or pass
Suggested change
|
||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (bug_risk): Any PyPI lookup failure other than a successful response, including a transient 5xx, timeout, rate limit, or network outage, is treated as
already_published=false, so the workflow proceeds to publishing instead of failing or retrying the version check.Triggers: When PyPI is temporarily unavailable or returns a non-404 error.
Suggested fix: Distinguish a genuine 404 from other curl/HTTP failures and fail or retry the check for unexpected responses.