diff --git a/.github/workflows/manual_regenerate_models.yaml b/.github/workflows/manual_regenerate_models.yaml new file mode 100644 index 00000000..480a9266 --- /dev/null +++ b/.github/workflows/manual_regenerate_models.yaml @@ -0,0 +1,174 @@ +# This workflow regenerates Pydantic models (src/apify_client/_models.py) from the OpenAPI spec. +# +# It can be triggered in two ways: +# 1. Automatically via workflow_dispatch from the apify-docs CI pipeline (with docs_pr_number and docs_workflow_run_id). +# 2. Manually from the GitHub UI (without any inputs) to regenerate from the live published spec. + +name: Regenerate models from OpenAPI spec + +on: + workflow_dispatch: + inputs: + docs_pr_number: + description: PR number in apify/apify-docs that triggered this workflow (optional for manual runs) + required: false + type: string + docs_workflow_run_id: + description: Workflow run ID in apify/apify-docs that built the OpenAPI spec artifact (optional for manual runs) + required: false + type: string + +permissions: + contents: write + pull-requests: write + +concurrency: + group: regenerate-models-${{ inputs.docs_pr_number || 'manual' }} + cancel-in-progress: true + +jobs: + regenerate-models: + name: Regenerate models + runs-on: ubuntu-latest + + env: + DOCS_PR_NUMBER: ${{ inputs.docs_pr_number }} + BRANCH: ${{ inputs.docs_pr_number && format('update-models-docs-pr-{0}', inputs.docs_pr_number) || 'update-models-manual' }} + TITLE: "${{ inputs.docs_pr_number && format('[TODO]: update generated models from apify-docs PR #{0}', inputs.docs_pr_number) || '[TODO]: update generated models from published OpenAPI spec' }}" + + steps: + - name: Validate inputs + if: inputs.docs_pr_number || inputs.docs_workflow_run_id + run: | + if [[ -n "$DOCS_PR_NUMBER" ]] && ! [[ "$DOCS_PR_NUMBER" =~ ^[1-9][0-9]*$ ]]; then + echo "::error::docs_pr_number must be a positive integer, got: $DOCS_PR_NUMBER" + exit 1 + fi + if [[ -n "${{ inputs.docs_workflow_run_id }}" ]] && ! [[ "${{ inputs.docs_workflow_run_id }}" =~ ^[0-9]+$ ]]; then + echo "::error::docs_workflow_run_id must be a numeric run ID, got: ${{ inputs.docs_workflow_run_id }}" + exit 1 + fi + + - name: Checkout apify-client-python + uses: actions/checkout@v6 + with: + token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }} + + # Download the pre-built OpenAPI spec artifact from the apify-docs workflow run. + # Skipped for manual runs — datamodel-codegen will fetch from the published spec URL instead. + - name: Download OpenAPI spec artifact + if: inputs.docs_workflow_run_id + uses: actions/download-artifact@v4 + with: + name: openapi-bundles + path: openapi-spec + repository: apify/apify-docs + run-id: ${{ inputs.docs_workflow_run_id }} + github-token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }} + + - name: Set up uv + uses: astral-sh/setup-uv@v7 + with: + python-version: "3.14" + + - name: Install dependencies + run: uv run poe install-dev + + # When a docs workflow run ID is provided, use the downloaded artifact. + # Otherwise, datamodel-codegen fetches from the default URL configured in pyproject.toml. + - name: Generate models from OpenAPI spec + run: | + if [[ -f openapi-spec/openapi.json ]]; then + uv run datamodel-codegen --input openapi-spec/openapi.json + else + uv run datamodel-codegen + fi + + - name: Commit model changes + id: commit + uses: EndBug/add-and-commit@v9 + with: + add: src/apify_client/_models.py + author_name: apify-service-account + author_email: apify-service-account@users.noreply.github.com + message: ${{ env.TITLE }} + new_branch: ${{ env.BRANCH }} + push: --force + + - name: Create or update PR + if: steps.commit.outputs.committed == 'true' + id: pr + env: + GH_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }} + run: | + 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 + if [[ -n "$DOCS_PR_NUMBER" ]]; then + DOCS_PR_URL="https://github.com/apify/apify-docs/pull/${DOCS_PR_NUMBER}" + 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.commit.outputs.committed == 'true' && inputs.docs_pr_number + env: + GH_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }} + 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() && inputs.docs_pr_number + env: + GH_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }} + 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 }})." \ + || echo "Warning: Failed to post failure comment to apify/apify-docs PR #$DOCS_PR_NUMBER."