From f8e370fae4676fb2676e7913acb5ba2aebeec1c3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Jan 2026 18:30:55 +0000 Subject: [PATCH] Add GitHub Actions workflow for publishing Python packages to PyPI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses #1028 — the ag_ui_adk wheel on PyPI had broken file permissions because it was built out-of-band. This workflow provides a consistent, repeatable publish process with a permission verification step to prevent the issue from recurring. Supports all six Python packages via a dropdown selector, with dry-run mode and trusted publishing support. https://claude.ai/code/session_01JT4yq4v53Coq865SaxX4Er --- .github/workflows/publish-python-package.yml | 101 +++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 .github/workflows/publish-python-package.yml diff --git a/.github/workflows/publish-python-package.yml b/.github/workflows/publish-python-package.yml new file mode 100644 index 000000000..233a74aab --- /dev/null +++ b/.github/workflows/publish-python-package.yml @@ -0,0 +1,101 @@ +name: Publish Python Package to PyPI + +on: + # Manual trigger only - no automatic publishing + workflow_dispatch: + inputs: + package: + description: 'Package to publish' + required: true + type: choice + options: + - sdks/python + - integrations/adk-middleware/python + - integrations/agent-spec/python + - integrations/aws-strands/python + - integrations/crew-ai/python + - integrations/langgraph/python + dry_run: + description: 'Run in dry-run mode (build without uploading)' + required: false + type: boolean + default: false + +jobs: + publish: + runs-on: ubuntu-latest + + permissions: + contents: read + id-token: write # Required for PyPI trusted publishing + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + version: ">=0.8.0" + + - name: Set up Python + run: uv python install 3.12 + + - name: Install dependencies + working-directory: ${{ inputs.package }} + run: uv sync + + - name: Run tests + working-directory: ${{ inputs.package }} + run: uv run python -m pytest + + - name: Build package + working-directory: ${{ inputs.package }} + run: uv build + + - name: Verify wheel permissions + working-directory: ${{ inputs.package }} + run: | + echo "## Wheel file permissions check" + uv run python -c " + import zipfile, glob, sys + whl = glob.glob('dist/*.whl')[0] + print(f'Checking {whl}') + bad = [] + for info in zipfile.ZipFile(whl).infolist(): + perms = (info.external_attr >> 16) & 0o777 + readable = perms & 0o444 + print(f' {oct(perms):>8s} {info.filename}') + if not readable: + bad.append(info.filename) + if bad: + print(f'ERROR: {len(bad)} file(s) missing read permissions:') + for f in bad: + print(f' - {f}') + sys.exit(1) + print('All files have correct permissions.') + " + + - name: Publish to PyPI + if: inputs.dry_run == false + working-directory: ${{ inputs.package }} + run: uv publish + env: + UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + + - name: Summary + if: success() + working-directory: ${{ inputs.package }} + run: | + NAME=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['name'])") + VERSION=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])") + + if [ "${{ inputs.dry_run }}" = "true" ]; then + echo "## ✅ Dry-run Complete!" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Built **${NAME} ${VERSION}** successfully. No artifacts were uploaded." >> $GITHUB_STEP_SUMMARY + else + echo "## ✅ Published ${NAME} ${VERSION} to PyPI" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Install with: \`pip install ${NAME}==${VERSION}\`" >> $GITHUB_STEP_SUMMARY + fi