diff --git a/.github/workflows/regenerate_models.yaml b/.github/workflows/regenerate_models.yaml new file mode 100644 index 00000000..ff734b6d --- /dev/null +++ b/.github/workflows/regenerate_models.yaml @@ -0,0 +1,169 @@ +# This workflow regenerates Pydantic models (src/apify_client/_models.py) from the OpenAPI spec whenever +# the spec changes in a apify/apify-docs PR. It is triggered via workflow_dispatch from the apify-docs CI pipeline. + +name: Regenerate models from OpenAPI spec + +on: + workflow_dispatch: + inputs: + docs_pr_number: + description: PR number in apify/apify-docs that triggered this workflow + required: true + type: string + docs_pr_sha: + description: Commit SHA from the apify/apify-docs PR + required: true + type: string + +permissions: + contents: write + pull-requests: write + +concurrency: + group: regenerate-models-${{ inputs.docs_pr_number }} + cancel-in-progress: true + +jobs: + regenerate-models: + name: Regenerate models + runs-on: ubuntu-latest + + env: + GITHUB_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }} + DOCS_PR_NUMBER: ${{ inputs.docs_pr_number }} + DOCS_PR_SHA: ${{ inputs.docs_pr_sha }} + + steps: + - name: Validate inputs + run: | + if ! [[ "$DOCS_PR_NUMBER" =~ ^[0-9]+$ ]]; then + echo "::error::docs_pr_number must be a positive integer, got: $DOCS_PR_NUMBER" + exit 1 + fi + if ! [[ "$DOCS_PR_SHA" =~ ^[a-f0-9]{40}$ ]]; then + echo "::error::docs_pr_sha must be a 40-character hex SHA, got: $DOCS_PR_SHA" + exit 1 + fi + + - name: Checkout apify-client-python + uses: actions/checkout@v6 + + - name: Checkout apify-docs at PR commit + uses: actions/checkout@v6 + with: + repository: apify/apify-docs + ref: ${{ inputs.docs_pr_sha }} + path: apify-docs + token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }} + + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: 24 + cache: npm + cache-dependency-path: apify-docs/package-lock.json + + # Build the bundled OpenAPI JSON from the docs repo sources. This requires Node.js because the spec + # is assembled by the docs build tooling. + - name: Build OpenAPI spec bundle + run: | + cd apify-docs + corepack enable + npm ci --force + npm run openapi:build:json + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Set up uv + uses: astral-sh/setup-uv@v6 + + # We call datamodel-codegen with --input pointing at the locally built spec. + - name: Generate models from local spec + run: uv run datamodel-codegen --input apify-docs/static/api/openapi.json + + - name: Check for changes + id: changes + run: | + if git diff --exit-code src/apify_client/_models.py; then + echo "No changes in generated models" + echo "changed=false" >> "$GITHUB_OUTPUT" + else + echo "Models have changed" + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Configure git + if: steps.changes.outputs.changed == 'true' + run: | + git config user.name "apify-service-account" + git config user.email "apify-service-account@users.noreply.github.com" + + - name: Create or update PR + if: steps.changes.outputs.changed == 'true' + id: pr + run: | + BRANCH="update-models-docs-pr-${DOCS_PR_NUMBER}" + DOCS_PR_URL="https://github.com/apify/apify-docs/pull/${DOCS_PR_NUMBER}" + TITLE="[TODO]: update generated models from apify-docs PR #${DOCS_PR_NUMBER}" + + # -B creates the branch or resets it if it already exists (re-runs for the same docs PR). + git checkout -B "$BRANCH" + git add src/apify_client/_models.py + git commit -m "$TITLE" + git push --force origin "$BRANCH" + + EXISTING_PR=$(gh pr list --head "$BRANCH" --json url --jq '.[0].url' 2>/dev/null || true) + + if [ -n "$EXISTING_PR" ]; then + echo "PR already exists: $EXISTING_PR" + echo "pr_url=$EXISTING_PR" >> "$GITHUB_OUTPUT" + echo "created=false" >> "$GITHUB_OUTPUT" + else + BODY=$(cat <> "$GITHUB_OUTPUT" + echo "created=true" >> "$GITHUB_OUTPUT" + fi + + # Post a cross-repo comment on the original docs PR so reviewers know about the corresponding client-python PR. + - name: Comment on apify-docs PR + if: steps.changes.outputs.changed == 'true' + env: + PR_CREATED: ${{ steps.pr.outputs.created }} + PR_URL: ${{ steps.pr.outputs.pr_url }} + run: | + if [ "$PR_CREATED" = "true" ]; then + COMMENT="A PR to update the Python client models has been created: ${PR_URL} + + This was automatically triggered by OpenAPI specification changes in this PR." + else + COMMENT="The Python client model PR has been updated with the latest OpenAPI spec changes: ${PR_URL}" + fi + + gh pr comment "$DOCS_PR_NUMBER" \ + --repo apify/apify-docs \ + --body "$COMMENT" + + - name: Comment on failure + if: failure() + run: | + gh pr comment "$DOCS_PR_NUMBER" \ + --repo apify/apify-docs \ + --body "Python client model regeneration failed. [See workflow run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})."