-
Notifications
You must be signed in to change notification settings - Fork 17
258 lines (232 loc) · 13.1 KB
/
Copy pathmanual_regenerate_models.yaml
File metadata and controls
258 lines (232 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# This workflow regenerates Pydantic models, TypedDicts, and Literal aliases (src/apify_client/_{models,typeddicts,literals}.py) from the OpenAPI spec.
#
# It can be triggered in two ways:
# 1. Automatically via workflow_dispatch from the apify-docs CI pipeline.
# 2. Manually from the GitHub UI (without any inputs) to regenerate from the live published spec.
name: Regenerate models
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
docs_pr_author:
description: GitHub login of the apify-docs PR author (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' }}
# Message for the automated regeneration commit. Kept descriptive (and traceable to the docs PR) for the
# branch history. It is deliberately NOT reused as the PR title.
COMMIT_MESSAGE: "${{ inputs.docs_pr_number && format('update generated models from apify-docs PR #{0}', inputs.docs_pr_number) || 'update generated models from published OpenAPI spec' }}"
# Placeholder PR title — just `TODO`, so it carries no valid commit type and `pr-title-check` stays red
# until a human replaces it (the default can never be merged as-is). The required action, the apify-docs
# reference, and its link all live in the PR body, not the title.
PR_TITLE: 'TODO'
ASSIGNEE: ${{ inputs.docs_pr_author || github.actor }}
REVIEWER: vdusek
LABEL: t-tooling
steps:
- name: Validate inputs
if: inputs.docs_pr_number || inputs.docs_workflow_run_id
env:
DOCS_WORKFLOW_RUN_ID: ${{ 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 "$DOCS_WORKFLOW_RUN_ID" ]] && ! [[ "$DOCS_WORKFLOW_RUN_ID" =~ ^[0-9]+$ ]]; then
echo "::error::docs_workflow_run_id must be a numeric run ID, got: $DOCS_WORKFLOW_RUN_ID"
exit 1
fi
- name: Checkout apify-client-python
uses: actions/checkout@v7
with:
token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }}
# Record master's SHA up front: a later step checks out the auto-update branch, so the change check below
# can't compare against HEAD. Resolve master from the remote rather than the checked-out ref: the run may be
# dispatched from a non-master ref, but that check and the PR both target master, so master is the baseline.
- name: Record master ref
id: base
run: |
git fetch --depth=1 origin master
echo "sha=$(git rev-parse FETCH_HEAD)" >> "$GITHUB_OUTPUT"
# Does the auto-update branch already exist on the remote? If so, a previous dispatch opened the PR
# and we append to it. If not, we start it from master (below) so signed-commit creates it there.
- name: Determine auto-update branch state
id: branch
run: |
if git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "create=false" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
echo "create=true" >> "$GITHUB_OUTPUT"
fi
# Check out the existing branch before regenerating so the new models land as a NEW commit on top,
# mirroring the commit just pushed to the docs PR. We must switch now, while the tree is clean:
# signed-commit's own checkout is a plain `git checkout` that would refuse to overwrite the
# regenerated files (which differ between master and the branch) once they're in the working tree.
- name: Check out existing auto-update branch
if: steps.branch.outputs.exists == 'true'
run: |
git fetch --depth=1 origin "$BRANCH"
git checkout -B "$BRANCH" FETCH_HEAD
# The branch doesn't exist yet, so regenerate from the recorded master SHA rather than the dispatched
# ref (a manual run may be dispatched from a non-master ref). Regenerating on master keeps the codegen
# tooling current, and signed-commit then creates the branch (the PR head) on top of master, the PR base.
- name: Start the auto-update branch from master
if: steps.branch.outputs.exists == 'false'
run: git checkout "${{ steps.base.outputs.sha }}"
# 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@v8
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@v8.2.0
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 poe generate-models-from-file openapi-spec/openapi.json
else
uv run poe generate-models
fi
# Proceed only when the regenerated models differ from master (compared against the recorded SHA,
# since the tree may now sit on the branch), which skips empty-PR runs: a spec change that doesn't
# affect the client models, or one already merged into master. Also avoids creating an empty branch.
- name: Check for model changes
id: changes
run: |
if git diff --quiet "${{ steps.base.outputs.sha }}" -- src/apify_client/_models.py src/apify_client/_typeddicts.py src/apify_client/_literals.py; then
echo "No model changes relative to master — nothing to regenerate."
echo "has_changes=false" >> "$GITHUB_OUTPUT"
else
echo "has_changes=true" >> "$GITHUB_OUTPUT"
fi
# Append the regenerated models to the auto-update branch as a single signed ("Verified") commit
# via apify/actions/signed-commit (GitHub's createCommitOnBranch GraphQL mutation). It's added on
# top of the branch tip, never resetting to master: a fresh docs PR creates the branch
# (create-branch) and its first commit, and each later docs-PR commit triggers a dispatch that
# appends another, so the client PR mirrors the docs PR and stays open.
#
# Appending (rather than force-pushing the branch to master, as before) is what keeps the PR open:
# a "branch == master" tip makes the PR head equal its base, which GitHub auto-closes, and each
# dispatch then opens a duplicate PR. signed-commit also sets committed=false when the staged
# files match the tip, so a repeated dispatch regenerating identical models adds no commit.
- name: Commit regenerated models
id: commit
if: steps.changes.outputs.has_changes == 'true'
uses: apify/actions/signed-commit@v1.3.2
with:
message: ${{ env.COMMIT_MESSAGE }}
add: "src/apify_client/_models.py src/apify_client/_typeddicts.py src/apify_client/_literals.py"
github-token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }}
branch: ${{ env.BRANCH }}
create-branch: "${{ steps.branch.outputs.create }}"
# Ensure exactly one PR exists for this branch. It's no longer auto-closed, so an existing PR is
# reused. Only the first run (or one whose PR step a concurrent dispatch cancelled) creates it.
- name: Create or update PR
if: steps.changes.outputs.has_changes == '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
# The PR opens with a `TODO` placeholder title that fails `pr-title-check`. Spell out the required
# action: the assignee must replace both the PR title and this description before merging.
ACTION_NOTE=$'> [!IMPORTANT]\n> **Replace this PR title and description before merging.**\n> - Title: a Conventional Commits message describing the actual model changes (e.g. `chore: ...` for a routine sync, or `refactor:`/`feat:`/`fix:` when models meaningfully change).\n> - Description: summarize what changed in the models.\n\n'
if [[ -n "$DOCS_PR_NUMBER" ]]; then
DOCS_PR_URL="https://github.com/apify/apify-docs/pull/${DOCS_PR_NUMBER}"
BODY="${ACTION_NOTE}- Updates the auto-generated Pydantic models and TypedDicts based on the proposed OpenAPI specification changes."$'\n'"- Based on apify-docs PR [#${DOCS_PR_NUMBER}](${DOCS_PR_URL})."
else
BODY="${ACTION_NOTE}- Updates the auto-generated Pydantic models and TypedDicts based on the latest OpenAPI specification changes."$'\n'"- Based on the [published OpenAPI specification](https://docs.apify.com/api/openapi.json)."
fi
PR_URL=$(gh pr create \
--title "$PR_TITLE" \
--body "$BODY" \
--head "$BRANCH" \
--base master \
--reviewer "$REVIEWER" \
--assignee "$ASSIGNEE" \
--label "$LABEL")
echo "Created PR: $PR_URL"
echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT"
echo "created=true" >> "$GITHUB_OUTPUT"
fi
# Post a cross-repo comment on the docs PR pointing reviewers to the companion client-python PR.
# Only when something happened: the PR was just created, or a commit was appended. A repeated
# dispatch that changes nothing (committed=false) posts no comment, avoiding noise on the docs PR.
- name: Comment on apify-docs PR
if: inputs.docs_pr_number && (steps.pr.outputs.created == 'true' || steps.commit.outputs.committed == 'true')
env:
GH_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }}
PR_CREATED: ${{ steps.pr.outputs.created }}
PR_URL: ${{ steps.pr.outputs.pr_url }}
DOCS_PR_AUTHOR: ${{ inputs.docs_pr_author }}
run: |
MENTION=""
if [[ -n "$DOCS_PR_AUTHOR" ]]; then
MENTION="@${DOCS_PR_AUTHOR} "
fi
if [[ "$PR_CREATED" = "true" ]]; then
HEADLINE="A companion PR has been opened in \`apify-client-python\` with the regenerated models: ${PR_URL}"
else
HEADLINE="The companion \`apify-client-python\` PR has been updated with the latest spec changes: ${PR_URL}"
fi
COMMENT=$(printf '%s\n' \
"> [!IMPORTANT]" \
"> **Action required** — ${MENTION}please coordinate this docs PR with the Python API client PR linked below." \
">" \
"> Because this PR modifies the OpenAPI specification, the generated models in \`apify-client-python\` must be regenerated to stay in sync. This has already been done automatically:" \
">" \
"> ${HEADLINE}" \
">" \
"> - Please make sure to review and merge both PRs together to keep the OpenAPI spec and API clients in sync." \
"> - You can ask for review and help from the Tooling team if needed.")
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](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})." \
|| echo "Warning: Failed to post failure comment to apify/apify-docs PR #$DOCS_PR_NUMBER."