Skip to content

Commit 3c43378

Browse files
vdusekclaude
andcommitted
ci: add workflow to regenerate models from OpenAPI spec changes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 32ad005 commit 3c43378

File tree

1 file changed

+169
-0
lines changed

1 file changed

+169
-0
lines changed
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# This workflow regenerates Pydantic models (src/apify_client/_models.py) from the OpenAPI spec whenever
2+
# the spec changes in a apify/apify-docs PR. It is triggered via workflow_dispatch from the apify-docs CI pipeline.
3+
4+
name: Regenerate models from OpenAPI spec
5+
6+
on:
7+
workflow_dispatch:
8+
inputs:
9+
docs_pr_number:
10+
description: "PR number in apify/apify-docs that triggered this workflow"
11+
required: true
12+
type: string
13+
docs_pr_sha:
14+
description: "Commit SHA from the apify/apify-docs PR"
15+
required: true
16+
type: string
17+
18+
permissions:
19+
contents: write
20+
pull-requests: write
21+
22+
concurrency:
23+
group: regenerate-models-${{ inputs.docs_pr_number }}
24+
cancel-in-progress: true
25+
26+
jobs:
27+
regenerate-models:
28+
name: Regenerate models
29+
runs-on: ubuntu-latest
30+
31+
env:
32+
GITHUB_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }}
33+
DOCS_PR_NUMBER: ${{ inputs.docs_pr_number }}
34+
DOCS_PR_SHA: ${{ inputs.docs_pr_sha }}
35+
36+
steps:
37+
- name: Validate inputs
38+
run: |
39+
if ! [[ "$DOCS_PR_NUMBER" =~ ^[0-9]+$ ]]; then
40+
echo "::error::docs_pr_number must be a positive integer, got: $DOCS_PR_NUMBER"
41+
exit 1
42+
fi
43+
if ! [[ "$DOCS_PR_SHA" =~ ^[a-f0-9]{40}$ ]]; then
44+
echo "::error::docs_pr_sha must be a 40-character hex SHA, got: $DOCS_PR_SHA"
45+
exit 1
46+
fi
47+
48+
- name: Checkout apify-client-python
49+
uses: actions/checkout@v6
50+
51+
- name: Checkout apify-docs at PR commit
52+
uses: actions/checkout@v6
53+
with:
54+
repository: apify/apify-docs
55+
ref: ${{ inputs.docs_pr_sha }}
56+
path: apify-docs
57+
token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }}
58+
59+
- name: Set up Node.js
60+
uses: actions/setup-node@v6
61+
with:
62+
node-version: 24
63+
cache: npm
64+
cache-dependency-path: apify-docs/package-lock.json
65+
66+
# Build the bundled OpenAPI JSON from the docs repo sources. This requires Node.js because the spec
67+
# is assembled by the docs build tooling.
68+
- name: Build OpenAPI spec bundle
69+
run: |
70+
cd apify-docs
71+
corepack enable
72+
npm ci --force
73+
npm run openapi:build:json
74+
env:
75+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
76+
77+
- name: Set up uv
78+
uses: astral-sh/setup-uv@v6
79+
80+
# We call datamodel-codegen with --input pointing at the locally built spec.
81+
- name: Generate models from local spec
82+
run: uv run datamodel-codegen --input apify-docs/static/api/openapi.json
83+
84+
- name: Check for changes
85+
id: changes
86+
run: |
87+
if git diff --exit-code src/apify_client/_models.py; then
88+
echo "No changes in generated models"
89+
echo "changed=false" >> "$GITHUB_OUTPUT"
90+
else
91+
echo "Models have changed"
92+
echo "changed=true" >> "$GITHUB_OUTPUT"
93+
fi
94+
95+
- name: Configure git
96+
if: steps.changes.outputs.changed == 'true'
97+
run: |
98+
git config user.name "apify-service-account"
99+
git config user.email "apify-service-account@users.noreply.github.com"
100+
101+
- name: Create or update PR
102+
if: steps.changes.outputs.changed == 'true'
103+
id: pr
104+
run: |
105+
BRANCH="chore/update-models-docs-pr-${DOCS_PR_NUMBER}"
106+
DOCS_PR_URL="https://github.com/apify/apify-docs/pull/${DOCS_PR_NUMBER}"
107+
TITLE="chore: update generated models from apify-docs PR #${DOCS_PR_NUMBER}"
108+
109+
# -B creates the branch or resets it if it already exists (re-runs for the same docs PR).
110+
git checkout -B "$BRANCH"
111+
git add src/apify_client/_models.py
112+
git commit -m "$TITLE"
113+
git push --force origin "$BRANCH"
114+
115+
EXISTING_PR=$(gh pr list --head "$BRANCH" --json url --jq '.[0].url' 2>/dev/null || true)
116+
117+
if [ -n "$EXISTING_PR" ]; then
118+
echo "PR already exists: $EXISTING_PR"
119+
echo "pr_url=$EXISTING_PR" >> "$GITHUB_OUTPUT"
120+
echo "created=false" >> "$GITHUB_OUTPUT"
121+
else
122+
BODY=$(cat <<EOF
123+
This PR updates the auto-generated Pydantic models based on OpenAPI specification changes in [apify-docs PR #${DOCS_PR_NUMBER}](${DOCS_PR_URL}).
124+
125+
## Changes
126+
127+
- Regenerated \`src/apify_client/_models.py\` using \`datamodel-codegen\`
128+
129+
## Source
130+
131+
- apify-docs PR: ${DOCS_PR_URL}
132+
EOF
133+
)
134+
135+
PR_URL=$(gh pr create \
136+
--title "$TITLE" \
137+
--body "$BODY" \
138+
--head "$BRANCH" \
139+
--base master)
140+
echo "Created PR: $PR_URL"
141+
echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT"
142+
echo "created=true" >> "$GITHUB_OUTPUT"
143+
fi
144+
145+
# Post a cross-repo comment on the original docs PR so reviewers know about the corresponding client-python PR.
146+
- name: Comment on apify-docs PR
147+
if: steps.changes.outputs.changed == 'true'
148+
env:
149+
PR_CREATED: ${{ steps.pr.outputs.created }}
150+
PR_URL: ${{ steps.pr.outputs.pr_url }}
151+
run: |
152+
if [ "$PR_CREATED" = "true" ]; then
153+
COMMENT="A PR to update the Python client models has been created: ${PR_URL}
154+
155+
This was automatically triggered by OpenAPI specification changes in this PR."
156+
else
157+
COMMENT="The Python client model PR has been updated with the latest OpenAPI spec changes: ${PR_URL}"
158+
fi
159+
160+
gh pr comment "$DOCS_PR_NUMBER" \
161+
--repo apify/apify-docs \
162+
--body "$COMMENT"
163+
164+
- name: Comment on failure
165+
if: failure()
166+
run: |
167+
gh pr comment "$DOCS_PR_NUMBER" \
168+
--repo apify/apify-docs \
169+
--body "Python client model regeneration failed. [See workflow run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})."

0 commit comments

Comments
 (0)