Skip to content

Commit 971a150

Browse files
authored
Enable trusted publishing (#337)
Updated pypi trusted publishing settings too: <img width="821" height="590" alt="image" src="https://github.com/user-attachments/assets/0c058ea8-d4e9-4ca3-ba2e-17253e89a16f" /> <!-- CURSOR_SUMMARY --> > [!NOTE] > **Medium Risk** > Moderate risk because it rewires the release/publish pipeline (trigger, permissions, artifact flow, and version gating), which could break publishing if misconfigured. It reduces secret-handling risk by removing reliance on a long-lived `PYPI_TOKEN`. > > **Overview** > Switches PyPI releases from the Speakeasy publish workflow + `PYPI_TOKEN` secret to a GitHub Releases-triggered pipeline that **builds with `uv`**, validates the release tag matches `unstructured_client._version`, and **publishes via trusted publishing (OIDC)** using `pypa/gh-action-pypi-publish`. > > Removes PyPI publishing configuration from Speakeasy (`.speakeasy/workflow*.yaml`) and stops passing `pypi_token` into the SDK generation workflow, while bumping SDK/package versioning to `0.43.2` (generator config + `_version.py`) and adding regression tests that enforce the new release workflow invariants. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 4d38845. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 39b8263 commit 971a150

File tree

8 files changed

+117
-28
lines changed

8 files changed

+117
-28
lines changed

.github/workflows/speakeasy_sdk_generation.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,4 @@ jobs:
2222
speakeasy_version: latest
2323
secrets:
2424
github_access_token: ${{ secrets.GITHUB_TOKEN }}
25-
pypi_token: ${{ secrets.PYPI_TOKEN }}
2625
speakeasy_api_key: ${{ secrets.SPEAKEASY_API_KEY }}
Lines changed: 75 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,78 @@
1-
name: Publish
1+
name: PyPI Release
2+
3+
on:
4+
release:
5+
types:
6+
- published
7+
28
permissions:
3-
checks: write
4-
contents: write
5-
pull-requests: write
6-
statuses: write
7-
"on":
8-
push:
9-
branches:
10-
- main
11-
paths:
12-
- RELEASES.md
13-
- '*/RELEASES.md'
9+
contents: read
10+
11+
concurrency:
12+
group: release
13+
cancel-in-progress: false
14+
15+
env:
16+
PYTHON_VERSION: "3.13"
17+
1418
jobs:
19+
build:
20+
runs-on: ubuntu-latest
21+
permissions:
22+
contents: read
23+
steps:
24+
- uses: actions/checkout@v5
25+
26+
- uses: astral-sh/setup-uv@v7
27+
with:
28+
python-version: ${{ env.PYTHON_VERSION }}
29+
30+
- name: Set up Python
31+
uses: actions/setup-python@v6
32+
with:
33+
python-version: ${{ env.PYTHON_VERSION }}
34+
35+
- name: Install dependencies
36+
env:
37+
UV_LOCKED: "1"
38+
UV_PYTHON: ${{ env.PYTHON_VERSION }}
39+
run: make install
40+
41+
- name: Validate version matches release tag
42+
env:
43+
TAG: ${{ github.event.release.tag_name }}
44+
run: |
45+
PKG_VERSION=$(PYTHONPATH=src uv run python - <<'PYEOF'
46+
from unstructured_client._version import __version__
47+
print(__version__)
48+
PYEOF
49+
)
50+
if [[ "$TAG" != "$PKG_VERSION" && "$TAG" != "v$PKG_VERSION" ]]; then
51+
echo "Tag '$TAG' does not match package version '$PKG_VERSION'"
52+
exit 1
53+
fi
54+
55+
- name: Build artifacts
56+
run: uv build --out-dir dist --clear
57+
58+
- name: Upload dist artifacts
59+
uses: actions/upload-artifact@v4
60+
with:
61+
name: python-package-distributions
62+
path: dist/
63+
1564
publish:
16-
uses: speakeasy-api/sdk-generation-action/.github/workflows/sdk-publish.yaml@v15
17-
secrets:
18-
github_access_token: ${{ secrets.GITHUB_TOKEN }}
19-
pypi_token: ${{ secrets.PYPI_TOKEN }}
20-
speakeasy_api_key: ${{ secrets.SPEAKEASY_API_KEY }}
65+
needs: build
66+
runs-on: ubuntu-latest
67+
permissions:
68+
contents: read
69+
id-token: write
70+
steps:
71+
- name: Download dist artifacts
72+
uses: actions/download-artifact@v4
73+
with:
74+
name: python-package-distributions
75+
path: dist/
76+
77+
- name: Publish package
78+
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # release/v1

.speakeasy/workflow.lock

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,6 @@ workflow:
3232
unstructured-python:
3333
target: python
3434
source: my-source
35-
publish:
36-
pypi:
37-
token: $PYPI_TOKEN
3835
codeSamples:
3936
output: codeSamples.yaml
4037
registry:

.speakeasy/workflow.yaml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,6 @@ targets:
1313
unstructured-python:
1414
target: python
1515
source: my-source
16-
publish:
17-
pypi:
18-
token: $PYPI_TOKEN
1916
codeSamples:
2017
output: codeSamples.yaml
2118
registry:

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
## 0.43.2
2+
3+
### Enhancements
4+
* Switch PyPI publishing to GitHub trusted publishing so releases can publish via OIDC without a long-lived `PYPI_TOKEN` secret.
5+
6+
### Features
7+
8+
### Fixes
9+
* Align release automation, package metadata, and generator config on `0.43.2` for the trusted-publishing release flow.
10+
111
## 0.43.1
212

313
### Enhancements

_test_unstructured_client/unit/test_regeneration_guards.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import re
12
from pathlib import Path
23
import tomllib
34

@@ -37,7 +38,34 @@ def test_publish_script_is_hardened():
3738

3839
assert "set -euo pipefail" in publish_script
3940
assert "sys.version_info < (3, 11)" in publish_script
40-
assert 'uv publish --token "${PYPI_TOKEN}" --check-url https://pypi.org/simple' in publish_script
41+
assert "uv build --out-dir dist --clear" in publish_script
42+
43+
44+
def test_release_workflow_uses_trusted_publishing():
45+
workflow = (REPO_ROOT / ".github" / "workflows" / "speakeasy_sdk_publish.yaml").read_text()
46+
47+
assert "release:" in workflow
48+
assert "pypa/gh-action-pypi-publish" in workflow
49+
assert "PYPI_TOKEN" not in workflow
50+
assert "upload-artifact" in workflow
51+
assert "download-artifact" in workflow
52+
assert re.search(r"publish:\n\s+needs: build", workflow)
53+
assert re.search(r"publish:\n(?:.*\n)*?\s+permissions:\n\s+contents: read\n\s+id-token: write", workflow)
54+
55+
56+
def test_release_workflow_keeps_oidc_out_of_build_job():
57+
workflow = (REPO_ROOT / ".github" / "workflows" / "speakeasy_sdk_publish.yaml").read_text()
58+
59+
build_job = workflow.split("\n publish:\n", maxsplit=1)[0]
60+
61+
assert "id-token: write" not in build_job
62+
63+
64+
def test_speakeasy_workflow_does_not_manage_pypi_publishing():
65+
workflow = (REPO_ROOT / ".speakeasy" / "workflow.yaml").read_text()
66+
67+
assert "publish:" not in workflow
68+
assert "PYPI_TOKEN" not in workflow
4169

4270

4371
def test_makefile_installs_with_locked_uv_sync():

gen.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ generation:
2727
generateNewTests: false
2828
skipResponseBodyAssertions: false
2929
python:
30-
version: 0.43.1
30+
version: 0.43.2
3131
additionalDependencies:
3232
dev:
3333
deepdiff: '>=9.0.0'

src/unstructured_client/_version.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
import importlib.metadata
44

55
__title__: str = "unstructured-client"
6-
__version__: str = "0.43.1"
6+
__version__: str = "0.43.2"
77
__openapi_doc_version__: str = "1.2.31"
88
__gen_version__: str = "2.680.0"
9-
__user_agent__: str = "speakeasy-sdk/python 0.43.1 2.680.0 1.2.31 unstructured-client"
9+
__user_agent__: str = "speakeasy-sdk/python 0.43.2 2.680.0 1.2.31 unstructured-client"
1010

1111
try:
1212
if __package__ is not None:

0 commit comments

Comments
 (0)