From 3842a5ea01e8ecb7d6b159b9236c0c60ceff5de3 Mon Sep 17 00:00:00 2001 From: Justin Beckwith Date: Fri, 5 Jun 2026 09:47:36 -0700 Subject: [PATCH] ci: use PyPI trusted publishing (#3365) --- .github/workflows/create-releases.yml | 55 ++++++++++++++++++++++++--- .github/workflows/publish-pypi.yml | 47 +++++++++++++++++++---- CONTRIBUTING.md | 5 +-- bin/publish-pypi | 6 --- 4 files changed, 91 insertions(+), 22 deletions(-) delete mode 100644 bin/publish-pypi diff --git a/.github/workflows/create-releases.yml b/.github/workflows/create-releases.yml index 7c42b4d6a6..9d65ef4e8e 100644 --- a/.github/workflows/create-releases.yml +++ b/.github/workflows/create-releases.yml @@ -10,6 +10,10 @@ jobs: if: github.ref == 'refs/heads/main' && github.repository == 'openai/openai-python' runs-on: ubuntu-latest environment: publish + outputs: + releases_created: ${{ steps.release.outputs.releases_created }} + permissions: + contents: read steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -20,16 +24,55 @@ jobs: repo: ${{ github.event.repository.full_name }} stainless-api-key: ${{ secrets.STAINLESS_API_KEY }} + build: + name: build + needs: release + if: ${{ needs.release.outputs.releases_created == 'true' }} + runs-on: ubuntu-latest + # Build distributions without OIDC access so package build code cannot mint + # a PyPI publishing token. The publish job handles only the upload. + permissions: + contents: read + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - name: Set up Rye - if: ${{ steps.release.outputs.releases_created }} uses: eifinger/setup-rye@c694239a43768373e87d0103d7f547027a23f3c8 with: version: '0.44.0' enable-cache: true - - name: Publish to PyPI - if: ${{ steps.release.outputs.releases_created }} + - name: Build package run: | - bash ./bin/publish-pypi - env: - PYPI_TOKEN: ${{ secrets.OPENAI_PYPI_TOKEN || secrets.PYPI_TOKEN }} + mkdir -p dist + rye build --clean + + - name: Upload package distributions + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: python-package-distributions + path: dist/ + if-no-files-found: error + retention-days: 1 + + publish: + name: publish + needs: build + runs-on: ubuntu-latest + environment: publish + # PyPI Trusted Publishing requires id-token: write. Keep it scoped to this + # minimal upload-only job rather than the build job. + permissions: + contents: read + id-token: write + + steps: + - name: Download package distributions + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: python-package-distributions + path: dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index a7c62c4c4d..d23cd66942 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -5,10 +5,14 @@ on: workflow_dispatch: jobs: - publish: - name: publish + build: + name: build + if: github.ref == 'refs/heads/main' && github.repository == 'openai/openai-python' runs-on: ubuntu-latest - environment: publish + # Build distributions without OIDC access so package build code cannot mint + # a PyPI publishing token. The publish job handles only the upload. + permissions: + contents: read steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 @@ -19,8 +23,37 @@ jobs: version: '0.44.0' enable-cache: true - - name: Publish to PyPI + - name: Build package run: | - bash ./bin/publish-pypi - env: - PYPI_TOKEN: ${{ secrets.OPENAI_PYPI_TOKEN || secrets.PYPI_TOKEN }} + mkdir -p dist + rye build --clean + + - name: Upload package distributions + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: python-package-distributions + path: dist/ + if-no-files-found: error + retention-days: 1 + + publish: + name: publish + needs: build + if: github.ref == 'refs/heads/main' && github.repository == 'openai/openai-python' + runs-on: ubuntu-latest + environment: publish + # PyPI Trusted Publishing requires id-token: write. Keep it scoped to this + # minimal upload-only job rather than the build job. + permissions: + contents: read + id-token: write + + steps: + - name: Download package distributions + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: python-package-distributions + path: dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 253b9ce5e6..f92377d459 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -119,9 +119,8 @@ the changes aren't made through the automated pipeline, you may want to make rel ### Publish with a GitHub workflow -You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/openai/openai-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up. +You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/openai/openai-python/actions/workflows/publish-pypi.yml). PyPI publishing uses Trusted Publishing, so the PyPI project must trust this repository's GitHub Actions workflow and the `publish` environment. ### Publish manually -If you need to manually release a package, you can run the `bin/publish-pypi` script with a `PYPI_TOKEN` set on -the environment. +If you need to retry a PyPI release, use the `Publish PyPI` GitHub action. Local manual publishing is not the standard release path because the GitHub workflow uses OIDC instead of a long-lived PyPI token. diff --git a/bin/publish-pypi b/bin/publish-pypi deleted file mode 100644 index 826054e924..0000000000 --- a/bin/publish-pypi +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -set -eux -mkdir -p dist -rye build --clean -rye publish --yes --token=$PYPI_TOKEN