From 716ead599be7bff35eb295f374392a3b60f67dbe Mon Sep 17 00:00:00 2001 From: Lyu Date: Wed, 15 Jul 2026 16:43:15 -0700 Subject: [PATCH 1/3] ci: publish Python SDK with trusted publishing --- .github/workflows/ci.yml | 42 ++++++++++++++++--- .github/workflows/publish.yml | 79 +++++++++++++++++++++++++++++++++++ README.md | 16 +++---- pyproject.toml | 4 ++ 4 files changed, 129 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7f076c..6c4dbad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,17 +6,20 @@ on: pull_request: branches: [main] +permissions: + contents: read + jobs: unit-tests: name: Unit Tests runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.11", "3.12", "3.13"] + python-version: ["3.11", "3.12", "3.13", "3.14"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} @@ -28,15 +31,44 @@ jobs: - name: Run unit tests run: python -m pytest tests/ --ignore=tests/test_integration.py -v + package: + name: Package + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-python@v6 + with: + python-version: "3.11" + + - name: Install build tools + run: python -m pip install --upgrade build twine + + - name: Check Python syntax + run: python -m compileall -q insforge tests + + - name: Build distributions + run: python -m build + + - name: Check distribution metadata + run: python -m twine check dist/* + + - name: Smoke test wheel + run: | + python -m venv /tmp/insforge-wheel-test + /tmp/insforge-wheel-test/bin/python -m pip install dist/*.whl + cd /tmp + /tmp/insforge-wheel-test/bin/python -c 'import insforge; from importlib.metadata import version; print(version("insforge"))' + integration-tests: name: Integration Tests runs-on: ubuntu-latest needs: unit-tests if: github.event_name == 'push' && github.ref == 'refs/heads/main' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: "3.13" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..f6bf062 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,79 @@ +name: Publish to PyPI + +on: + push: + tags: + - "v*" + +permissions: + contents: read + +concurrency: + group: pypi-${{ github.ref }} + cancel-in-progress: false + +jobs: + build: + name: Build distributions + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-python@v6 + with: + python-version: "3.11" + + - name: Validate tag matches package version + env: + RELEASE_TAG: ${{ github.ref_name }} + run: | + package_version="$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')" + if [ "$RELEASE_TAG" != "v$package_version" ]; then + echo "Tag $RELEASE_TAG does not match package version $package_version." + exit 1 + fi + + - name: Install test and build tools + run: | + python -m pip install --upgrade -e . + python -m pip install --upgrade pytest pytest-asyncio build twine + + - name: Run unit tests + run: python -m pytest tests/ --ignore=tests/test_integration.py -v + + - name: Build distributions + run: python -m build + + - name: Check distribution metadata + run: python -m twine check dist/* + + - name: Smoke test wheel + run: | + python -m venv /tmp/insforge-wheel-test + /tmp/insforge-wheel-test/bin/python -m pip install dist/*.whl + cd /tmp + /tmp/insforge-wheel-test/bin/python -c 'import insforge; from importlib.metadata import version; print(version("insforge"))' + + - name: Upload distributions + uses: actions/upload-artifact@v7 + with: + name: python-package-distributions + path: dist/ + if-no-files-found: error + retention-days: 1 + + publish: + name: Publish distributions + needs: build + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - name: Download distributions + uses: actions/download-artifact@v8 + with: + name: python-package-distributions + path: dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@v1.14.0 diff --git a/README.md b/README.md index a3b0644..1d70415 100644 --- a/README.md +++ b/README.md @@ -257,18 +257,20 @@ python -m build This produces `dist/insforge-.tar.gz` and `dist/insforge--py3-none-any.whl`. -### Publish to PyPI +### Release to PyPI -```bash -pip install twine +Publishing uses PyPI Trusted Publishing through `.github/workflows/publish.yml`; no PyPI API token is stored in GitHub. The workflow publishes both the wheel and source distribution and produces digital attestations. -# Test PyPI (recommended first) -twine upload --repository testpypi dist/* +1. Update `project.version` in `pyproject.toml` and merge the change to `main` after CI passes. +2. Create and push an annotated tag that exactly matches the package version with a `v` prefix: -# Production PyPI -twine upload dist/* +```bash +git tag -a vX.Y.Z -m "vX.Y.Z" +git push origin vX.Y.Z ``` +The tag triggers a build, metadata validation, and publication to PyPI. PEP 440 prerelease versions are supported as long as the tag exactly matches the version in `pyproject.toml`. + ## License See [LICENSE](LICENSE) for details. diff --git a/pyproject.toml b/pyproject.toml index ecf5c14..330788f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,10 @@ dependencies = [ "pydantic>=2.0", ] +[project.urls] +Source = "https://github.com/InsForge/insforge-python" +Issues = "https://github.com/InsForge/insforge-python/issues" + [tool.setuptools] packages = [ "insforge", From aa795d1d9e7f73a74ff2043405b2edd6a137c241 Mon Sep 17 00:00:00 2001 From: Lyu Date: Wed, 15 Jul 2026 17:23:38 -0700 Subject: [PATCH 2/3] ci: create stable Python releases --- .github/workflows/publish.yml | 30 ++++++++++++++++++++++++++++++ README.md | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f6bf062..03e3421 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -16,6 +16,8 @@ jobs: build: name: Build distributions runs-on: ubuntu-latest + outputs: + stable: ${{ steps.version.outputs.stable }} steps: - uses: actions/checkout@v7 @@ -24,6 +26,7 @@ jobs: python-version: "3.11" - name: Validate tag matches package version + id: version env: RELEASE_TAG: ${{ github.ref_name }} run: | @@ -33,6 +36,12 @@ jobs: exit 1 fi + if [[ "$package_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "stable=true" >> "$GITHUB_OUTPUT" + else + echo "stable=false" >> "$GITHUB_OUTPUT" + fi + - name: Install test and build tools run: | python -m pip install --upgrade -e . @@ -77,3 +86,24 @@ jobs: - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@v1.14.0 + + release: + name: Create stable GitHub Release + if: needs.build.outputs.stable == 'true' + needs: [build, publish] + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Create GitHub Release + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ github.ref_name }} + REPOSITORY: ${{ github.repository }} + run: | + if gh release view "$RELEASE_TAG" --repo "$REPOSITORY" >/dev/null 2>&1; then + echo "GitHub Release $RELEASE_TAG already exists" + else + gh release create "$RELEASE_TAG" --repo "$REPOSITORY" --generate-notes --verify-tag + fi diff --git a/README.md b/README.md index 1d70415..b96a290 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,7 @@ git tag -a vX.Y.Z -m "vX.Y.Z" git push origin vX.Y.Z ``` -The tag triggers a build, metadata validation, and publication to PyPI. PEP 440 prerelease versions are supported as long as the tag exactly matches the version in `pyproject.toml`. +The tag triggers a build, metadata validation, and publication to PyPI. PEP 440 prerelease versions are supported as long as the tag exactly matches the version in `pyproject.toml`. After PyPI publication succeeds, stable `X.Y.Z` versions create a GitHub Release automatically; prerelease tags do not. ## License From 6664980bf595b80cc5840927e52bc3cd64afa576 Mon Sep 17 00:00:00 2001 From: Lyu Date: Thu, 16 Jul 2026 11:00:15 -0700 Subject: [PATCH 3/3] ci: pin PyPI publish action --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 03e3421..2662385 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -85,7 +85,7 @@ jobs: path: dist/ - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@v1.14.0 + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 release: name: Create stable GitHub Release