Files
codex/.github/workflows/python-sdk-release.yml
Ahmed Ibrahim 96c2b4377f Gate Python SDK publishing on runtime availability and verify PyPI files (#44055)
## Why

The SDK publish job needs to wait for runtime wheels to become available on PyPI, and release reruns need to tolerate SDK files that have already been uploaded.

## What changed

- Require runtime publication and verification before publishing the SDK, and enable `skip-existing` for SDK uploads.
- Share a PyPI verifier between runtime and SDK releases. Require the exact expected artifact set, including the SDK wheel and source distribution.
- Canonicalize versions with `packaging.version.Version` and retry registry errors, malformed responses, and incomplete artifact sets with a bounded retry loop.

## Testing

Add unit tests for transient failures and malformed responses, waiting for complete SDK artifacts, canonical version lookup, and retry exhaustion when runtime wheels are missing. Run them in repository checks.

GitOrigin-RevId: 9cb2ddced4ce6d509ebfd130511ab3f39239dd62
2026-09-09 04:47:38 +00:00

220 lines
7.5 KiB
YAML

name: python-sdk-release
on:
push:
tags:
- "python-v*"
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
jobs:
resolve-python-release:
if: github.repository == 'openai/codex'
name: resolve-python-release
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
runtime_version: ${{ steps.python_release.outputs.runtime_version }}
sdk_version: ${{ steps.python_release.outputs.sdk_version }}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Validate SDK tag and resolve pinned runtime
id: python_release
shell: bash
run: |
set -euo pipefail
python3 - <<'PY'
import os
import re
import tomllib
from pathlib import Path
sdk_version = os.environ["GITHUB_REF_NAME"].removeprefix("python-v")
if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:b[0-9]+)?", sdk_version):
raise SystemExit(
"Python SDK release tags must identify a stable or beta release, "
"for example python-v0.147.0 or python-v0.1.0b1."
)
pyproject = tomllib.loads(Path("sdk/python/pyproject.toml").read_text())
prefix = "openai-codex-cli-bin=="
runtime_versions = [
dependency.removeprefix(prefix)
for dependency in pyproject["project"]["dependencies"]
if dependency.startswith(prefix)
]
if len(runtime_versions) != 1:
raise SystemExit(
f"Expected exactly one pinned {prefix} dependency, found {runtime_versions}"
)
if "b" not in sdk_version and sdk_version != runtime_versions[0]:
raise SystemExit(
f"Stable SDK version {sdk_version} must match pinned runtime {runtime_versions[0]}."
)
with Path(os.environ["GITHUB_OUTPUT"]).open("a") as output:
print(f"runtime_version={runtime_versions[0]}", file=output)
print(f"sdk_version={sdk_version}", file=output)
PY
prepare-python-runtime:
name: prepare-python-runtime
needs: resolve-python-release
permissions:
contents: read
uses: ./.github/workflows/python-runtime-build.yml
with:
runtime_version: ${{ needs.resolve-python-release.outputs.runtime_version }}
# Always publish the exact pinned runtime from this top-level workflow before
# building the SDK package. PyPI does not support reusable workflows as
# Trusted Publishers.
publish-python-runtime:
if: github.repository == 'openai/codex'
name: publish-python-runtime
needs:
- prepare-python-runtime
- resolve-python-release
runs-on: ubuntu-latest
environment: pypi
permissions:
contents: read
id-token: write # Required for PyPI trusted publishing.
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Download Python runtime wheels
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: python-runtime-wheels
path: dist/python-runtime
- name: Publish Python runtime wheels to PyPI
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
with:
packages-dir: dist/python-runtime
skip-existing: true
- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: "0.11.3"
- name: Verify Python runtime wheels are available on PyPI
env:
PYTHON_RUNTIME_VERSION: ${{ needs.resolve-python-release.outputs.runtime_version }}
run: |
uv run --no-project --with packaging==26.2 python .github/scripts/verify_pypi_release.py \
openai-codex-cli-bin "$PYTHON_RUNTIME_VERSION"
build-python-sdk:
if: github.repository == 'openai/codex'
name: build-python-sdk
needs:
- publish-python-runtime
- resolve-python-release
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Build Python SDK package
shell: bash
env:
SDK_VERSION: ${{ needs.resolve-python-release.outputs.sdk_version }}
run: |
set -euo pipefail
# Build in a glibc Linux image so release type generation installs
# the pinned manylinux runtime wheel.
docker run --rm \
--user "$(id -u):$(id -g)" \
-e HOME=/tmp/codex-python-sdk-home \
-e UV_LINK_MODE=copy \
-e SDK_VERSION \
-e SDK_STAGE_DIR="${RUNNER_TEMP}/openai-codex" \
-e SDK_DIST_DIR="${GITHUB_WORKSPACE}/dist/python-sdk" \
-v "${GITHUB_WORKSPACE}:${GITHUB_WORKSPACE}" \
-v "${RUNNER_TEMP}:${RUNNER_TEMP}" \
-w "${GITHUB_WORKSPACE}/sdk/python" \
python:3.12-slim \
sh -euxc '
python -m venv /tmp/release-tools
/tmp/release-tools/bin/python -m pip install build twine uv==0.11.3
/tmp/release-tools/bin/uv sync --group dev --frozen
/tmp/release-tools/bin/uv run --frozen --no-sync python scripts/update_sdk_artifacts.py \
stage-sdk "${SDK_STAGE_DIR}" \
--sdk-version "${SDK_VERSION}"
/tmp/release-tools/bin/python -m build \
--wheel \
--sdist \
--outdir "${SDK_DIST_DIR}" \
"${SDK_STAGE_DIR}"
/tmp/release-tools/bin/python -m twine check --strict "${SDK_DIST_DIR}/"*
'
- name: Upload Python SDK package
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: python-sdk-package
path: dist/python-sdk/*
if-no-files-found: error
publish-python-sdk:
name: publish-python-sdk
needs:
- build-python-sdk
- publish-python-runtime
- resolve-python-release
runs-on: ubuntu-latest
environment: pypi
permissions:
contents: read
id-token: write # Required for PyPI trusted publishing.
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Download Python SDK package
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: python-sdk-package
path: dist/python-sdk
- name: Publish Python SDK to PyPI
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
with:
packages-dir: dist/python-sdk
skip-existing: true
- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: "0.11.3"
- name: Verify Python SDK is available on PyPI
env:
SDK_VERSION: ${{ needs.resolve-python-release.outputs.sdk_version }}
run: uv run --no-project --with packaging==26.2 python .github/scripts/verify_pypi_release.py openai-codex "$SDK_VERSION"