Skip to content

Commit 7c0391d

Browse files
vdusekclaude
andauthored
ci: add workflow to regenerate models from OpenAPI spec (#694)
## Summary - Add `workflow_dispatch` workflow that regenerates Pydantic models from an `apify-docs` PR's OpenAPI spec - Builds the spec locally, runs `datamodel-codegen`, and opens a PR if models changed - Comments back on the source `apify-docs` PR with a link ## Issues - Closes: #618 ## Context - apify/apify-docs#2381 - #694 --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6aaf8b4 commit 7c0391d

File tree

1 file changed

+174
-0
lines changed

1 file changed

+174
-0
lines changed
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# This workflow regenerates Pydantic models (src/apify_client/_models.py) from the OpenAPI spec.
2+
#
3+
# It can be triggered in two ways:
4+
# 1. Automatically via workflow_dispatch from the apify-docs CI pipeline (with docs_pr_number and docs_workflow_run_id).
5+
# 2. Manually from the GitHub UI (without any inputs) to regenerate from the live published spec.
6+
7+
name: Regenerate models from OpenAPI spec
8+
9+
on:
10+
workflow_dispatch:
11+
inputs:
12+
docs_pr_number:
13+
description: PR number in apify/apify-docs that triggered this workflow (optional for manual runs)
14+
required: false
15+
type: string
16+
docs_workflow_run_id:
17+
description: Workflow run ID in apify/apify-docs that built the OpenAPI spec artifact (optional for manual runs)
18+
required: false
19+
type: string
20+
21+
permissions:
22+
contents: write
23+
pull-requests: write
24+
25+
concurrency:
26+
group: regenerate-models-${{ inputs.docs_pr_number || 'manual' }}
27+
cancel-in-progress: true
28+
29+
jobs:
30+
regenerate-models:
31+
name: Regenerate models
32+
runs-on: ubuntu-latest
33+
34+
env:
35+
DOCS_PR_NUMBER: ${{ inputs.docs_pr_number }}
36+
BRANCH: ${{ inputs.docs_pr_number && format('update-models-docs-pr-{0}', inputs.docs_pr_number) || 'update-models-manual' }}
37+
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' }}"
38+
39+
steps:
40+
- name: Validate inputs
41+
if: inputs.docs_pr_number || inputs.docs_workflow_run_id
42+
run: |
43+
if [[ -n "$DOCS_PR_NUMBER" ]] && ! [[ "$DOCS_PR_NUMBER" =~ ^[1-9][0-9]*$ ]]; then
44+
echo "::error::docs_pr_number must be a positive integer, got: $DOCS_PR_NUMBER"
45+
exit 1
46+
fi
47+
if [[ -n "${{ inputs.docs_workflow_run_id }}" ]] && ! [[ "${{ inputs.docs_workflow_run_id }}" =~ ^[0-9]+$ ]]; then
48+
echo "::error::docs_workflow_run_id must be a numeric run ID, got: ${{ inputs.docs_workflow_run_id }}"
49+
exit 1
50+
fi
51+
52+
- name: Checkout apify-client-python
53+
uses: actions/checkout@v6
54+
with:
55+
token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }}
56+
57+
# Download the pre-built OpenAPI spec artifact from the apify-docs workflow run.
58+
# Skipped for manual runs — datamodel-codegen will fetch from the published spec URL instead.
59+
- name: Download OpenAPI spec artifact
60+
if: inputs.docs_workflow_run_id
61+
uses: actions/download-artifact@v4
62+
with:
63+
name: openapi-bundles
64+
path: openapi-spec
65+
repository: apify/apify-docs
66+
run-id: ${{ inputs.docs_workflow_run_id }}
67+
github-token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }}
68+
69+
- name: Set up uv
70+
uses: astral-sh/setup-uv@v7
71+
with:
72+
python-version: "3.14"
73+
74+
- name: Install dependencies
75+
run: uv run poe install-dev
76+
77+
# When a docs workflow run ID is provided, use the downloaded artifact.
78+
# Otherwise, datamodel-codegen fetches from the default URL configured in pyproject.toml.
79+
- name: Generate models from OpenAPI spec
80+
run: |
81+
if [[ -f openapi-spec/openapi.json ]]; then
82+
uv run datamodel-codegen --input openapi-spec/openapi.json
83+
else
84+
uv run datamodel-codegen
85+
fi
86+
87+
- name: Commit model changes
88+
id: commit
89+
uses: EndBug/add-and-commit@v9
90+
with:
91+
add: src/apify_client/_models.py
92+
author_name: apify-service-account
93+
author_email: apify-service-account@users.noreply.github.com
94+
message: ${{ env.TITLE }}
95+
new_branch: ${{ env.BRANCH }}
96+
push: --force
97+
98+
- name: Create or update PR
99+
if: steps.commit.outputs.committed == 'true'
100+
id: pr
101+
env:
102+
GH_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }}
103+
run: |
104+
EXISTING_PR=$(gh pr list --head "$BRANCH" --json url --jq '.[0].url' 2>/dev/null || true)
105+
106+
if [[ -n "$EXISTING_PR" ]]; then
107+
echo "PR already exists: $EXISTING_PR"
108+
echo "pr_url=$EXISTING_PR" >> "$GITHUB_OUTPUT"
109+
echo "created=false" >> "$GITHUB_OUTPUT"
110+
else
111+
if [[ -n "$DOCS_PR_NUMBER" ]]; then
112+
DOCS_PR_URL="https://github.com/apify/apify-docs/pull/${DOCS_PR_NUMBER}"
113+
BODY=$(cat <<EOF
114+
This PR updates the auto-generated Pydantic models based on OpenAPI specification changes in [apify-docs PR #${DOCS_PR_NUMBER}](${DOCS_PR_URL}).
115+
116+
## Changes
117+
118+
- Regenerated \`src/apify_client/_models.py\` using \`datamodel-codegen\`
119+
120+
## Source
121+
122+
- apify-docs PR: ${DOCS_PR_URL}
123+
EOF
124+
)
125+
else
126+
BODY=$(cat <<EOF
127+
This PR updates the auto-generated Pydantic models from the published OpenAPI specification.
128+
129+
## Changes
130+
131+
- Regenerated \`src/apify_client/_models.py\` using \`datamodel-codegen\`
132+
EOF
133+
)
134+
fi
135+
136+
PR_URL=$(gh pr create \
137+
--title "$TITLE" \
138+
--body "$BODY" \
139+
--head "$BRANCH" \
140+
--base master)
141+
echo "Created PR: $PR_URL"
142+
echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT"
143+
echo "created=true" >> "$GITHUB_OUTPUT"
144+
fi
145+
146+
# Post a cross-repo comment on the original docs PR so reviewers know about the corresponding client-python PR.
147+
- name: Comment on apify-docs PR
148+
if: steps.commit.outputs.committed == 'true' && inputs.docs_pr_number
149+
env:
150+
GH_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }}
151+
PR_CREATED: ${{ steps.pr.outputs.created }}
152+
PR_URL: ${{ steps.pr.outputs.pr_url }}
153+
run: |
154+
if [[ "$PR_CREATED" = "true" ]]; then
155+
COMMENT="A PR to update the Python client models has been created: ${PR_URL}
156+
157+
This was automatically triggered by OpenAPI specification changes in this PR."
158+
else
159+
COMMENT="The Python client model PR has been updated with the latest OpenAPI spec changes: ${PR_URL}"
160+
fi
161+
162+
gh pr comment "$DOCS_PR_NUMBER" \
163+
--repo apify/apify-docs \
164+
--body "$COMMENT"
165+
166+
- name: Comment on failure
167+
if: failure() && inputs.docs_pr_number
168+
env:
169+
GH_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }}
170+
run: |
171+
gh pr comment "$DOCS_PR_NUMBER" \
172+
--repo apify/apify-docs \
173+
--body "Python client model regeneration failed. [See workflow run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})." \
174+
|| echo "Warning: Failed to post failure comment to apify/apify-docs PR #$DOCS_PR_NUMBER."

0 commit comments

Comments
 (0)