name: Release (Python) # A PR labeled python-release-dry-run or a manual run with publish=false builds # and validates the complete release without registry credentials. Publication # is accepted only from a manual run on the matching python-v* tag when the # private publisher-repository identity and public-PyPI switch are configured. on: workflow_dispatch: inputs: publish: description: Publish the validated wheels to public PyPI. Must run from a python-v* tag. required: true type: boolean default: false pull_request: types: [labeled] permissions: contents: read concurrency: # Public runs stay globally serialized across tags. Dry runs remain isolated # by ref so they do not block an intentional publication. group: ${{ github.event_name == 'workflow_dispatch' && inputs.publish && 'python-publication' || format('{0}-{1}', github.workflow, github.ref) }} cancel-in-progress: false jobs: build: name: Build four wheels if: github.event_name == 'workflow_dispatch' || github.event.label.name == 'python-release-dry-run' uses: ./.github/workflows/build-exe-for-python-sdk.yml with: targets: node24-linux-x64,node24-linux-arm64,node24-macos-arm64 release: true python-compat: name: Python ${{ matrix.python }} / installed SDK needs: build runs-on: ubuntu-latest timeout-minutes: 10 strategy: fail-fast: false matrix: python: ['3.10', '3.14'] steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: actions/setup-python@v6.3.0 with: python-version: ${{ matrix.python }} - uses: actions/download-artifact@v8 with: pattern: deepseek_harness_* path: dist merge-multiple: true - name: Resolve installed wheel version id: compatibility-version run: | python - <<'PY' >> "$GITHUB_OUTPUT" import runpy release = runpy.run_path("scripts/build-python-release.py") repository_version = release["repository_version"]() print(f"version={release['pep440_version'](repository_version)}") PY - name: Install and run the published entry path run: | python -m pip install --find-links dist "deepseek-harness-sdk==${{ steps.compatibility-version.outputs.version }}" python scripts/smoke-python-runtime.py --scenario sdk-default validate: name: Validate release candidate needs: [build, python-compat] runs-on: ubuntu-latest timeout-minutes: 10 outputs: version: ${{ steps.version.outputs.version }} steps: - uses: actions/checkout@v6 with: persist-credentials: false - uses: actions/setup-python@v6.3.0 with: python-version: '3.10' - name: Resolve release version id: version run: | python3 - <<'PY' >> "$GITHUB_OUTPUT" import runpy release = runpy.run_path("scripts/build-python-release.py") repository_version = release["repository_version"]() wheel_version = release["pep440_version"](repository_version) print(f"repository-version={repository_version}") print(f"version={wheel_version}") PY - name: Authorize publication request env: PUBLISH: ${{ github.event_name == 'workflow_dispatch' && inputs.publish }} PUBLIC_PYPI_RELEASE_ENABLED: ${{ vars.PUBLIC_PYPI_RELEASE_ENABLED }} PYPI_PUBLISHER_REPOSITORY: ${{ vars.PYPI_PUBLISHER_REPOSITORY }} REPOSITORY: ${{ github.repository }} REF_NAME: ${{ github.ref_name }} REF_TYPE: ${{ github.ref_type }} REPOSITORY_VERSION: ${{ steps.version.outputs.repository-version }} run: | set -euo pipefail if [ "$PUBLISH" = true ]; then [ -n "$PYPI_PUBLISHER_REPOSITORY" ] || { echo "::error::Set the repository variable PYPI_PUBLISHER_REPOSITORY before publication." exit 1 } [ "$REPOSITORY" = "$PYPI_PUBLISHER_REPOSITORY" ] || { echo "::error::This repository is not the configured PyPI publisher repository." exit 1 } [ "$PUBLIC_PYPI_RELEASE_ENABLED" = true ] || { echo "::error::Set PUBLIC_PYPI_RELEASE_ENABLED=true before public publication." exit 1 } [ "$REF_TYPE" = tag ] && [ "$REF_NAME" = "python-v$REPOSITORY_VERSION" ] || { echo "::error::Publication must run from tag python-v$REPOSITORY_VERSION." exit 1 } fi - uses: actions/download-artifact@v8 with: pattern: deepseek_harness_* path: dist merge-multiple: true - name: Check release contents env: VERSION: ${{ steps.version.outputs.version }} run: | set -euo pipefail expected="$(mktemp)" actual="$(mktemp)" printf '%s\n' \ "deepseek_harness_runtime_bin-$VERSION-py3-none-macosx_14_0_arm64.whl" \ "deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_aarch64.whl" \ "deepseek_harness_runtime_bin-$VERSION-py3-none-manylinux_2_28_x86_64.whl" \ "deepseek_harness_sdk-$VERSION-py3-none-any.whl" > "$expected" find dist -maxdepth 1 -type f -name '*.whl' -exec basename {} \; | sort > "$actual" diff -u "$expected" "$actual" while IFS= read -r wheel; do size="$(stat -c '%s' "dist/$wheel")" [ "$size" -lt 100000000 ] || { echo "::error::$wheel is $size bytes; public PyPI accepts at most 100000000 bytes by default." exit 1 } done < "$actual" - name: Validate package metadata run: | python -m pip install twine==6.2.0 python -m twine check dist/*.whl - name: Record artifact hashes run: | cd dist sha256sum *.whl | sort -k2 > SHA256SUMS cat SHA256SUMS - uses: actions/upload-artifact@v7 with: name: python-release-${{ steps.version.outputs.version }} path: dist/* if-no-files-found: error retention-days: 7 publish-runtime: name: Publish runtime wheels to public PyPI if: github.event_name == 'workflow_dispatch' && inputs.publish needs: validate runs-on: ubuntu-latest timeout-minutes: 10 environment: pypi-runtime permissions: contents: read id-token: write steps: - uses: actions/download-artifact@v8 with: name: python-release-${{ needs.validate.outputs.version }} path: dist - name: Select runtime wheels run: | mkdir -p dist/runtime mv dist/deepseek_harness_runtime_bin-*.whl dist/runtime/ - name: Publish runtime wheels uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: dist/runtime/ # Public attestations reveal the private publisher repository. OIDC # authentication remains enabled without uploading that provenance. attestations: false # Keep the SDK in a dependent job. If its upload fails after the immutable # runtime files arrive, "re-run failed jobs" resumes here without attempting # to overwrite the runtime release. publish-sdk: name: Publish SDK wheel to public PyPI if: github.event_name == 'workflow_dispatch' && inputs.publish needs: [validate, publish-runtime] runs-on: ubuntu-latest timeout-minutes: 10 environment: pypi permissions: contents: read id-token: write steps: - uses: actions/download-artifact@v8 with: name: python-release-${{ needs.validate.outputs.version }} path: dist - name: Select SDK wheel run: | mkdir -p dist/sdk mv dist/deepseek_harness_sdk-*.whl dist/sdk/ - name: Publish SDK wheel uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: dist/sdk/ attestations: false