feat(python): publish sealg to PyPI so uvx sealg resolves - #23
Conversation
Wire a PyPI release path for the Python client so it is installable by name rather than only from a checkout or git subdirectory. - publish-python.yaml: build sdist+wheel and publish via PyPI Trusted Publishing (OIDC, no stored token), triggered on a dedicated `sealg-py-v*` tag so it never collides with the Rust binary's `v*` release. A tag/version match guard fails before publishing on a mismatch; workflow_dispatch does a build-only validation run. - pyproject.toml: add readme long-description, MIT license + bundled LICENSE, authors, project URLs, keywords, and classifiers for a complete PyPI page. - RELEASING.md: document the one-time Trusted Publishing setup and the `sealg-py-vX.Y.Z` release steps. - READMEs: lead install with `uvx sealg`; keep the checkout/git fallbacks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EpwFmgQPfugKFF9zugay6Y
|
ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing |
There was a problem hiding this comment.
2 issues found across 6 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name=".github/workflows/publish-python.yaml">
<violation number="1" location=".github/workflows/publish-python.yaml:33">
P3: A manual dispatch on a tag ref would actually publish: workflow_dispatch lets you pick a tag, which sets `github.ref` to `refs/tags/<tag>` and makes this `if` true. That contradicts the documented "build-only validation" purpose of dispatch. Guard on the event name instead, e.g. `if: github.event_name != 'workflow_dispatch' && startsWith(github.ref, 'refs/tags/')`.</violation>
<violation number="2" location=".github/workflows/publish-python.yaml:37">
P3: The version guard compares raw strings, so a pre-release tag like `sealg-py-v0.2.0-rc.1` only passes if python/pyproject.toml spells the version `0.2.0-rc.1` exactly. Both `0.2.0-rc.1` and `0.2.0rc1` are the same PEP 440 version, so the documented pre-release flow fails if a maintainer writes the normalized form. Normalize both sides (e.g. with `packaging.version.Version`) before comparing.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
|
||
| - name: Verify tag matches package version | ||
| # Only enforced on tag pushes; skipped for workflow_dispatch (build-only). | ||
| if: startsWith(github.ref, 'refs/tags/') |
There was a problem hiding this comment.
P3: A manual dispatch on a tag ref would actually publish: workflow_dispatch lets you pick a tag, which sets github.ref to refs/tags/<tag> and makes this if true. That contradicts the documented "build-only validation" purpose of dispatch. Guard on the event name instead, e.g. if: github.event_name != 'workflow_dispatch' && startsWith(github.ref, 'refs/tags/').
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/publish-python.yaml, line 33:
<comment>A manual dispatch on a tag ref would actually publish: workflow_dispatch lets you pick a tag, which sets `github.ref` to `refs/tags/<tag>` and makes this `if` true. That contradicts the documented "build-only validation" purpose of dispatch. Guard on the event name instead, e.g. `if: github.event_name != 'workflow_dispatch' && startsWith(github.ref, 'refs/tags/')`.</comment>
<file context>
@@ -0,0 +1,68 @@
+
+ - name: Verify tag matches package version
+ # Only enforced on tag pushes; skipped for workflow_dispatch (build-only).
+ if: startsWith(github.ref, 'refs/tags/')
+ run: |
+ tag_ver="${GITHUB_REF_NAME#sealg-py-v}"
</file context>
| if: startsWith(github.ref, 'refs/tags/') | |
| if: github.event_name != 'workflow_dispatch' && startsWith(github.ref, 'refs/tags/') |
| run: | | ||
| tag_ver="${GITHUB_REF_NAME#sealg-py-v}" | ||
| pkg_ver="$(python3 -c 'import tomllib,pathlib; print(tomllib.loads(pathlib.Path("python/pyproject.toml").read_text())["project"]["version"])')" | ||
| if [ "$tag_ver" != "$pkg_ver" ]; then |
There was a problem hiding this comment.
P3: The version guard compares raw strings, so a pre-release tag like sealg-py-v0.2.0-rc.1 only passes if python/pyproject.toml spells the version 0.2.0-rc.1 exactly. Both 0.2.0-rc.1 and 0.2.0rc1 are the same PEP 440 version, so the documented pre-release flow fails if a maintainer writes the normalized form. Normalize both sides (e.g. with packaging.version.Version) before comparing.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/publish-python.yaml, line 37:
<comment>The version guard compares raw strings, so a pre-release tag like `sealg-py-v0.2.0-rc.1` only passes if python/pyproject.toml spells the version `0.2.0-rc.1` exactly. Both `0.2.0-rc.1` and `0.2.0rc1` are the same PEP 440 version, so the documented pre-release flow fails if a maintainer writes the normalized form. Normalize both sides (e.g. with `packaging.version.Version`) before comparing.</comment>
<file context>
@@ -0,0 +1,68 @@
+ run: |
+ tag_ver="${GITHUB_REF_NAME#sealg-py-v}"
+ pkg_ver="$(python3 -c 'import tomllib,pathlib; print(tomllib.loads(pathlib.Path("python/pyproject.toml").read_text())["project"]["version"])')"
+ if [ "$tag_ver" != "$pkg_ver" ]; then
+ echo "::error::tag $GITHUB_REF_NAME implies version '$tag_ver' but python/pyproject.toml is '$pkg_ver'. Bump the version before tagging."
+ exit 1
</file context>
Summary
Wires a PyPI release path for the Python
sealgclient (python/) so it is installable by name (uvx sealg) instead of only from a checkout or git subdirectory. The package namesealgis currently free on PyPI. Auth is PyPI Trusted Publishing (OIDC) — no API token is stored.Changes
.github/workflows/publish-python.yaml(new) — builds the sdist + wheel and publishes viapypa/gh-action-pypi-publishusing OIDC Trusted Publishing. Triggered on a dedicatedsealg-py-vX.Y.Ztag namespace so it never collides with the Rust binary'svX.Y.Zrelease (release.yml). A tag/version match guard fails the run before publishing if the tag disagrees withpython/pyproject.toml.workflow_dispatchruns a build-only validation without publishing.python/pyproject.toml— adds README long-description,license = "MIT"+ bundledLICENSE, author,[project.urls], keywords, and classifiers so the PyPI page is complete. Verified the wheel metadata and thatuvx --from ./dist/*.whl sealg --helpruns.python/LICENSE(new) — copy of the repo MIT license so the wheel bundles it.RELEASING.md— documents the one-time Trusted Publishing setup (maintainer action; CI cannot do it) and thesealg-py-vX.Y.Zrelease steps.README.md/python/README.md— lead install withuvx sealg; keep the checkout and git-subdirectory fallbacks.Testing
make test) — wire-contract check passes (16 constants agree); package builds sdist + wheel; entrypoint runs from the built wheel.make ci)Important
Before the first tag can publish, a maintainer must complete the one-time PyPI setup in
RELEASING.md: add the trusted publisher (ownerEdison-Watch, repocli, workflowpublish-python.yaml, environmentpypi) as a pending publisher on PyPI, and create apypiGitHub Environment. CI cannot do this step.Related Issues
Closes #
🤖 Generated with Claude Code
https://claude.ai/code/session_01EpwFmgQPfugKFF9zugay6Y
Generated by Claude Code
Summary by cubic
Publishes the Python
sealgclient to PyPI souvx sealgresolves without a checkout or git subdirectory.sealg-py-vX.Y.Ztag namespace so Python releases don't collide with Rust binaryvX.Y.Zreleases; a tag/version mismatch fails before publishing, andworkflow_dispatchdoes a build-only validation.pyproject.tomlmetadata, bundles the MIT license, and updates the READMEs andRELEASING.md.Migration
pypiGitHub Environment before the first tag can publish; steps are inRELEASING.md.Written for commit 1b45b44. Summary will update on new commits.