Skip to content

Publish on PyPI

Publish on PyPI #11

Workflow file for this run

name: Publish on PyPI
on:
workflow_dispatch:
inputs:
dry_run_fixture:
description: Exercise API URL approval with the checked-in fixture.
required: false
default: false
type: boolean
permissions:
contents: read
jobs:
plan:
name: Plan publish
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
api_url_approval_required: ${{ steps.api-url-check.outputs.approval_required }}
api_url_change_count: ${{ steps.api-url-check.outputs.change_count }}
api_url_diff_summary: ${{ steps.api-url-check.outputs.summary }}
changed: ${{ steps.release-plan.outputs.changed }}
reason: ${{ steps.release-plan.outputs.reason }}
run_url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
tag: ${{ steps.release-plan.outputs.tag }}
upstream_json_digest: ${{ steps.api-url-input.outputs.digest }}
version: ${{ steps.release-plan.outputs.version }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Prepare API URL check input
id: api-url-input
env:
DRY_RUN_FIXTURE: ${{ inputs.dry_run_fixture }}
run: |
if [ "$DRY_RUN_FIXTURE" = "true" ]; then
python .github/scripts/create_api_url_dry_run_baseline.py \
.tmp/api-url-dry-run-baseline.sqlite
DIGEST=$(python - <<'PY'
import hashlib
from pathlib import Path
payload = Path(".github/fixtures/api-url-compromise.json").read_bytes()
print(hashlib.sha256(payload).hexdigest())
PY
)
printf 'source_path=%s\n' \
".github/fixtures/api-url-compromise.json" >> "$GITHUB_OUTPUT"
printf 'baseline_db=%s\n' \
".tmp/api-url-dry-run-baseline.sqlite" >> "$GITHUB_OUTPUT"
printf 'baseline_version=%s\n' \
"dry-run-baseline" >> "$GITHUB_OUTPUT"
else
DIGEST=$(python .github/scripts/fetch_upstream_json.py .tmp/models.dev-api.json)
printf 'source_path=%s\n' ".tmp/models.dev-api.json" >> "$GITHUB_OUTPUT"
printf 'baseline_db=%s\n' "" >> "$GITHUB_OUTPUT"
printf 'baseline_version=%s\n' "" >> "$GITHUB_OUTPUT"
fi
printf 'digest=%s\n' "$DIGEST" >> "$GITHUB_OUTPUT"
- name: Check upstream API URL changes
id: api-url-check
env:
BASELINE_DB: ${{ steps.api-url-input.outputs.baseline_db }}
BASELINE_VERSION: ${{ steps.api-url-input.outputs.baseline_version }}
SOURCE_PATH: ${{ steps.api-url-input.outputs.source_path }}
UPSTREAM_JSON_DIGEST: ${{ steps.api-url-input.outputs.digest }}
run: |
BASELINE_ARGS=()
if [ -n "$BASELINE_DB" ]; then
BASELINE_ARGS=(
--baseline-db "$BASELINE_DB"
--baseline-version "$BASELINE_VERSION"
)
fi
python .github/scripts/check_upstream_api_urls.py \
"$SOURCE_PATH" \
"${BASELINE_ARGS[@]}" \
--digest "$UPSTREAM_JSON_DIGEST" \
--json-output .tmp/api-url-diff.json \
--markdown-output .tmp/api-url-diff.md
- name: Print upstream API URL check
run: |
python - <<'PY'
import os
from pathlib import Path
with Path(os.environ["GITHUB_STEP_SUMMARY"]).open("a") as summary:
summary.write(Path(".tmp/api-url-diff.md").read_text())
summary.write("\n")
PY
- name: Plan release
id: release-plan
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
UPSTREAM_JSON_DIGEST: ${{ steps.api-url-input.outputs.digest }}
with:
script: |
await require("./.github/scripts/plan_release")({github, context, core});
- name: Print release plan
env:
REASON: ${{ steps.release-plan.outputs.reason }}
VERSION: ${{ steps.release-plan.outputs.version }}
run: printf '%s\n' "$REASON"
api_url_change_approval:
name: Approve upstream API URL changes
needs: plan
if: ${{ needs.plan.outputs.changed == 'true' && needs.plan.outputs.api_url_approval_required == 'true' }}
runs-on: ubuntu-latest
permissions: {}
# The protected environment is the manual supply-chain gate. Its URL points
# reviewers to the plan job summary, where the API URL diff is rendered.
environment:
name: pypi-api-url-change
url: ${{ needs.plan.outputs.run_url }}
steps:
- name: Approval recorded
env:
VERSION: ${{ needs.plan.outputs.version }}
run: printf '%s\n' "Approved upstream API URL changes for $VERSION"
publish:
name: Publish
needs:
- plan
- api_url_change_approval
if: ${{ always() && !cancelled() && inputs.dry_run_fixture != true && needs.plan.outputs.changed == 'true' && (needs.plan.outputs.api_url_approval_required != 'true' || needs.api_url_change_approval.result == 'success') }}
runs-on: ubuntu-latest
permissions:
contents: write # required for creating tags and releases
id-token: write # required for Trusted Publishing (OIDC)
environment:
name: pypi
url: https://pypi.org/p/modelsdotdev
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.14"
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
enable-cache: false
- run: uv venv --python 3.14
- name: Fetch upstream JSON
id: upstream-json
env:
EXPECTED_DIGEST: ${{ needs.plan.outputs.upstream_json_digest }}
run: |
DIGEST=$(uv run python .github/scripts/fetch_upstream_json.py .tmp/models.dev-api.json)
if [ "$DIGEST" != "$EXPECTED_DIGEST" ]; then
printf '%s\n' "upstream JSON digest changed after approval planning"
printf '%s\n' "planned: $EXPECTED_DIGEST"
printf '%s\n' "current: $DIGEST"
exit 1
fi
echo "digest=$DIGEST" >> "$GITHUB_OUTPUT"
- name: Build
env:
MODELDOTDEV_BUILD_SOURCE: .tmp/models.dev-api.json
UV_DYNAMIC_VERSIONING_BYPASS: ${{ needs.plan.outputs.version }}
run: uv build
- name: Test installed wheel
run: sh .github/scripts/test_installed_wheel.sh
- name: Publish to PyPI
run: uv publish --trusted-publishing always
- name: Create GitHub release
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
RELEASE_TAG: ${{ needs.plan.outputs.tag }}
RELEASE_VERSION: ${{ needs.plan.outputs.version }}
RELEASE_COMMIT_SHA: ${{ github.sha }}
UPSTREAM_JSON_DIGEST: ${{ steps.upstream-json.outputs.digest }}
with:
script: |
await require("./.github/scripts/create_release")({github, context, core});
dry_run_publish:
name: Dry-run publish continuation
needs:
- plan
- api_url_change_approval
if: ${{ always() && !cancelled() && inputs.dry_run_fixture == true && (needs.plan.outputs.api_url_approval_required != 'true' || needs.api_url_change_approval.result == 'success') }}
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Confirm dry run completed
run: |
printf '%s\n' "Dry run complete. No build, upload, tag, or release was created."