Skip to content

Commit a3f5c98

Browse files
authored
[typespec-python] Add generated test regeneration workflow (#46414)
1 parent 6d0a412 commit a3f5c98

7,296 files changed

Lines changed: 1175574 additions & 6 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/CODEOWNERS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -862,5 +862,8 @@
862862
/eng/emitter-package.json @mccoyp @catalinaperalta @iscai-msft @msyyc @ChenxiJiang333
863863
/eng/emitter-package-lock.json @mccoyp @catalinaperalta @iscai-msft @msyyc @ChenxiJiang333
864864

865+
# TypeSpec Python generated tests and regeneration workflow
866+
/eng/tools/emitter/ @tadelesh @msyyc @iscai-msft @lmazuel @lirenhe
867+
865868
/pylintrc @l0lawrence @scbedd @mccoyp
866869
/sdk/**/ci.yml @msyyc @lmazuel @scbedd

.github/skills/emitter-package-update/SKILL.md

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ description: Automate bumping typespec-python version in emitter-package.json fo
77

88
Bump `@azure-tools/typespec-python` to the latest version in `emitter-package.json` and create a PR.
99

10+
## Background
11+
12+
The Python emitter ecosystem consists of two packages:
13+
- **Branded emitter** (`@azure-tools/typespec-python`): Lives in [Azure/typespec-azure](https://github.com/Azure/typespec-azure/tree/main/packages/typespec-python). This is the emitter used for Azure SDK generation.
14+
- **Unbranded emitter** (`@typespec/http-client-python`): Lives in [microsoft/typespec](https://github.com/microsoft/typespec/tree/main/packages/http-client-python). The branded emitter wraps this package.
15+
16+
When `eng/emitter-package.json` is updated on `main`, the [TypeSpec Python Regenerate Tests](../../workflows/typespec-python-regenerate.yml) workflow triggers automatically and regenerates test code at `eng/emitter/gen/`.
17+
1018
## Prerequisites
1119

1220
Before running this workflow, verify the following tools are installed:
@@ -45,15 +53,15 @@ Run npm-check-updates to find the latest `@azure-tools/typespec-python` version:
4553
npx npm-check-updates --packageFile eng/emitter-package.json
4654
```
4755

48-
Extract the target version from the output (e.g., `0.46.4`).
56+
Extract the target version from the output (e.g., `0.61.3`).
4957

5058
### 3. Create Feature Branch
5159

5260
```bash
5361
git checkout -b bump-typespec-python-{version}
5462
```
5563

56-
Replace `{version}` with the actual version number (e.g., `bump-typespec-python-0.46.4`).
64+
Replace `{version}` with the actual version number (e.g., `bump-typespec-python-0.61.3`).
5765

5866
### 4. Update Dependencies
5967

@@ -65,24 +73,41 @@ npx npm-check-updates --packageFile eng/emitter-package.json -u
6573

6674
Align `@azure-tools/openai-typespec` and `@typespec/openapi3` with the versions pinned in [azure-rest-api-specs/package.json](https://github.com/Azure/azure-rest-api-specs/blob/main/package.json) to ensure consistency between the emitter and the spec repo. Check the spec repo's versions and update `eng/emitter-package.json` accordingly (e.g., set `"@azure-tools/openai-typespec": "1.8.0"` and `"@typespec/openapi3": "1.9.0"` to match).
6775

68-
Regenerate the lock file:
76+
### 5. Regenerate Config Files
77+
78+
If you have a local clone of `typespec-azure`, regenerate the lock file using the branded emitter's `package.json` to ensure `devDependencies` are aligned:
79+
80+
```bash
81+
tsp-client generate-config-files \
82+
--package-json=<path-to-local-typespec-azure>/packages/typespec-python/package.json
83+
```
84+
85+
Otherwise, regenerate the lock file only:
6986

7087
```bash
7188
tsp-client generate-lock-file
7289
```
7390

74-
### 5. Commit Changes
91+
### 6. Commit Changes
7592

7693
```bash
7794
git add eng/emitter-package.json eng/emitter-package-lock.json
7895
git commit -m "bump typespec-python {version}"
7996
```
8097

81-
### 6. Create Pull Request
98+
### 7. Create Pull Request
8299

83100
Push branch and create PR:
84101

85102
```bash
86103
git push -u origin bump-typespec-python-{version}
87104
gh pr create --title "bump typespec-python {version}" --body "Bump @azure-tools/typespec-python to version {version}"
88105
```
106+
107+
### 8. After Merge
108+
109+
Once the PR merges to `main`, the [TypeSpec Python Regenerate Tests](../../workflows/typespec-python-regenerate.yml) workflow triggers automatically because `eng/emitter-package.json` was modified. It will:
110+
1. Install the branded emitter at the version specified in `eng/emitter-package.json`
111+
2. Regenerate all test code
112+
3. Create a follow-up PR with the updated generated files at `eng/emitter/gen/`
113+
4. Tag @iscai-msft and @msyyc as reviewers
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
name: TypeSpec Python Regenerate Tests
2+
3+
on:
4+
# Trigger when eng/emitter-package.json is updated on main (branded emitter version bump)
5+
push:
6+
branches: [main]
7+
paths:
8+
- "eng/emitter-package.json"
9+
# Allow manual triggering with emitter choice
10+
workflow_dispatch:
11+
inputs:
12+
emitter:
13+
description: "Which emitter to regenerate with"
14+
required: true
15+
type: choice
16+
options:
17+
- "branded (@azure-tools/typespec-python)"
18+
- "unbranded (@typespec/http-client-python)"
19+
default: "branded (@azure-tools/typespec-python)"
20+
version:
21+
description: "Emitter version (leave empty to use version from emitter-package.json for branded, or latest for unbranded)"
22+
required: false
23+
typespec_ref:
24+
description: "Git ref (branch, tag, or SHA) of typespec repo to build regeneration infrastructure from"
25+
required: false
26+
default: "main"
27+
typespec_repo:
28+
description: "TypeSpec repository (owner/repo) to checkout from (default: microsoft/typespec)"
29+
required: false
30+
default: "microsoft/typespec"
31+
32+
permissions:
33+
contents: write
34+
pull-requests: write
35+
issues: write
36+
37+
concurrency:
38+
group: ${{ github.workflow }}
39+
cancel-in-progress: true
40+
41+
jobs:
42+
regenerate:
43+
name: "Regenerate TypeSpec Python tests"
44+
runs-on: ubuntu-latest
45+
steps:
46+
- name: Checkout azure-sdk-for-python
47+
# SHA corresponds to actions/checkout@v6
48+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
49+
with:
50+
fetch-depth: 0
51+
52+
- name: Determine emitter to use
53+
id: emitter-info
54+
run: |
55+
set -euo pipefail
56+
if [ "${{ github.event_name }}" = "push" ]; then
57+
# Push trigger: always use branded emitter at version from emitter-package.json
58+
EMITTER_NAME="@azure-tools/typespec-python"
59+
VERSION=$(jq -r '.dependencies["@azure-tools/typespec-python"]' eng/emitter-package.json)
60+
echo "Push trigger: using branded emitter @ $VERSION"
61+
elif [[ "${{ github.event.inputs.emitter }}" == branded* ]]; then
62+
EMITTER_NAME="@azure-tools/typespec-python"
63+
VERSION="${{ github.event.inputs.version }}"
64+
if [ -z "$VERSION" ]; then
65+
VERSION=$(jq -r '.dependencies["@azure-tools/typespec-python"]' eng/emitter-package.json)
66+
echo "No version specified, using emitter-package.json version: $VERSION"
67+
fi
68+
else
69+
EMITTER_NAME="@typespec/http-client-python"
70+
VERSION="${{ github.event.inputs.version }}"
71+
if [ -z "$VERSION" ]; then
72+
VERSION=$(npm view @typespec/http-client-python version 2>/dev/null)
73+
echo "No version specified, using latest npm version: $VERSION"
74+
fi
75+
fi
76+
echo "emitter_name=$EMITTER_NAME" >> $GITHUB_OUTPUT
77+
echo "emitter_version=$VERSION" >> $GITHUB_OUTPUT
78+
echo "::notice::Regenerating with $EMITTER_NAME@$VERSION"
79+
80+
- name: Checkout microsoft/typespec
81+
# SHA corresponds to actions/checkout@v6
82+
# Checkout to "_typespec" (not "typespec") to avoid the workspace path
83+
# "azure-sdk-for-python" causing spec.includes("azure") to match all specs
84+
# in regenerate.ts, which breaks unbranded package name detection
85+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
86+
with:
87+
repository: ${{ github.event.inputs.typespec_repo || 'microsoft/typespec' }}
88+
ref: ${{ github.event.inputs.typespec_ref || 'main' }}
89+
path: _typespec
90+
fetch-depth: 0
91+
92+
- name: Setup Node.js
93+
# SHA corresponds to actions/setup-node@v6
94+
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
95+
with:
96+
node-version: lts/*
97+
98+
- name: Setup Python
99+
# SHA corresponds to actions/setup-python@v5
100+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
101+
with:
102+
python-version: "3.12"
103+
104+
- name: Build http-client-python
105+
working-directory: _typespec/packages/http-client-python
106+
run: |
107+
npm install --ignore-scripts
108+
npm run build
109+
110+
- name: Install target emitter
111+
working-directory: _typespec/packages/http-client-python
112+
run: |
113+
EMITTER="${{ steps.emitter-info.outputs.emitter_name }}"
114+
VERSION="${{ steps.emitter-info.outputs.emitter_version }}"
115+
echo "Installing $EMITTER@$VERSION"
116+
npm install "${EMITTER}@${VERSION}"
117+
118+
- name: Prepare Python environment
119+
working-directory: _typespec/packages/http-client-python
120+
run: |
121+
npm run install
122+
npm run prepare
123+
124+
- name: Regenerate tests
125+
working-directory: _typespec/packages/http-client-python
126+
run: |
127+
EMITTER="${{ steps.emitter-info.outputs.emitter_name }}"
128+
npm run regenerate -- --emitterName "$EMITTER"
129+
130+
- name: Copy regenerated tests
131+
run: |
132+
set -euo pipefail
133+
TARGET="eng/tools/emitter/gen"
134+
rm -rf "$TARGET/azure" "$TARGET/unbranded"
135+
mkdir -p "$TARGET"
136+
cp -r "_typespec/packages/http-client-python/tests/generated/azure" "$TARGET/azure"
137+
cp -r "_typespec/packages/http-client-python/tests/generated/unbranded" "$TARGET/unbranded"
138+
139+
- name: Clean up typespec checkout
140+
run: rm -rf "_typespec"
141+
142+
- name: Commit and push changes
143+
id: push-changes
144+
run: |
145+
set -euo pipefail
146+
EMITTER="${{ steps.emitter-info.outputs.emitter_name }}"
147+
VERSION="${{ steps.emitter-info.outputs.emitter_version }}"
148+
BRANCH="auto/typespec-python-regenerate"
149+
git config user.name "github-actions[bot]"
150+
git config user.email "github-actions[bot]@users.noreply.github.com"
151+
152+
git add eng/tools/emitter/gen/
153+
if git diff --cached --quiet; then
154+
echo "No changes to commit"
155+
echo "has_changes=false" >> $GITHUB_OUTPUT
156+
exit 0
157+
fi
158+
159+
git checkout -B "$BRANCH"
160+
git commit -m "[typespec-python] Regenerate tests with ${EMITTER}@${VERSION}"
161+
git push origin "$BRANCH" --force
162+
echo "has_changes=true" >> $GITHUB_OUTPUT
163+
echo "branch=$BRANCH" >> $GITHUB_OUTPUT
164+
165+
- name: Create or update Pull Request
166+
id: create-pr
167+
if: steps.push-changes.outputs.has_changes == 'true'
168+
env:
169+
GH_TOKEN: ${{ github.token }}
170+
run: |
171+
set -euo pipefail
172+
EMITTER="${{ steps.emitter-info.outputs.emitter_name }}"
173+
VERSION="${{ steps.emitter-info.outputs.emitter_version }}"
174+
BRANCH="${{ steps.push-changes.outputs.branch }}"
175+
TITLE="[typespec-python] Regenerate tests with ${EMITTER}@${VERSION}"
176+
BODY="Automated regeneration of TypeSpec Python generated tests.
177+
178+
- Emitter: \`${EMITTER}@${VERSION}\`
179+
- Workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
180+
181+
This PR was auto-generated."
182+
183+
# Check if a PR already exists for this branch
184+
EXISTING_PR=$(gh pr list --head "$BRANCH" --json number --jq '.[0].number' || echo "")
185+
if [ -n "$EXISTING_PR" ]; then
186+
echo "Updating existing PR #$EXISTING_PR"
187+
gh pr edit "$EXISTING_PR" --title "$TITLE" --body "$BODY" --add-reviewer iscai-msft,msyyc
188+
PR_NUMBER="$EXISTING_PR"
189+
else
190+
echo "Creating new PR"
191+
PR_NUMBER=$(gh pr create --head "$BRANCH" --base main \
192+
--title "$TITLE" --body "$BODY" \
193+
--reviewer iscai-msft,msyyc | grep -oP '\d+$')
194+
echo "Created PR #$PR_NUMBER"
195+
fi
196+
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
197+
198+
- name: Enable auto-merge
199+
if: steps.push-changes.outputs.has_changes == 'true'
200+
env:
201+
GH_TOKEN: ${{ github.token }}
202+
run: |
203+
gh pr merge "${{ steps.create-pr.outputs.pr_number }}" --auto --squash || \
204+
echo "::warning::Auto-merge could not be enabled (may require branch protection rules)"
205+
206+
notify-on-failure:
207+
name: "Notify on failure"
208+
needs: regenerate
209+
if: failure()
210+
runs-on: ubuntu-latest
211+
steps:
212+
- name: Send failure notification
213+
# SHA corresponds to actions/github-script@v7
214+
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b
215+
with:
216+
script: |
217+
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
218+
await github.rest.issues.create({
219+
owner: context.repo.owner,
220+
repo: context.repo.repo,
221+
title: '[typespec-python] Regeneration workflow failed',
222+
body: `The TypeSpec Python test regeneration workflow failed.\n\n` +
223+
`- **Run:** ${runUrl}\n` +
224+
`- **Trigger:** ${context.eventName}\n\n` +
225+
`cc @iscai-msft @msyyc`,
226+
labels: ['typespec-python'],
227+
assignees: ['iscai-msft', 'msyyc']
228+
});

.vscode/cspell.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@
295295
"inotify",
296296
"ints",
297297
"iohttp",
298+
"iscai",
298299
"IOHTTP",
299300
"IOLRO",
300301
"incompat",
@@ -348,6 +349,7 @@
348349
"msauth",
349350
"msrest",
350351
"msrestazure",
352+
"msyyc",
351353
"MSSQL",
352354
"MSRC",
353355
"mutex",

doc/dev/static_type_checking.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ It also walks through the setup necessary to run mypy and pyright, static type c
55

66
For the TL;DR version, please see the [Static Type Checking Cheat Sheet](https://github.com/Azure/azure-sdk-for-python/blob/main/doc/dev/static_type_checking_cheat_sheet.md).
77

8-
> Note: If you are seeing typing errors in **generated code**, please first try to regenerate with the latest version of the generator. If typing errors persist, open an issue in the [generator repo](https://github.com/Azure/autorest.python).
8+
> Note: If you are seeing typing errors in **generated code**, please first try to regenerate with the latest version of the generator. If typing errors persist, open an issue in the [TypeSpec repo](https://github.com/microsoft/typespec) for TypeSpec-based SDKs or the [autorest.python repo](https://github.com/Azure/autorest.python) for swagger-based SDKs.
99
1010
## Table of contents
1111
- [Intro to typing in Python](#intro-to-typing-in-python)

eng/tools/emitter/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# TypeSpec Python Emitter
2+
3+
This directory holds files related to the TypeSpec Python emitter (`@azure-tools/typespec-python` and `@typespec/http-client-python`).
4+
5+
## Contents
6+
7+
- **`gen/`** — Auto-generated Python SDK test code produced by the emitter.
8+
These files are regenerated automatically by the [typespec-python-regenerate](https://github.com/Azure/azure-sdk-for-python/blob/main/.github/workflows/typespec-python-regenerate.yml) workflow whenever the emitter version in `eng/emitter-package.json` is updated.
9+
10+
> **Do not edit files in `gen/` by hand.** They will be overwritten on the next regeneration.

eng/tools/emitter/gen/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# TypeSpec Python Generated Tests
2+
3+
This folder contains the generated Python SDK test code produced by
4+
`@typespec/http-client-python` against the [http-specs](https://github.com/microsoft/typespec/tree/main/packages/http-specs)
5+
and [azure-http-specs](https://github.com/Azure/typespec-azure/tree/main/packages/azure-http-specs) test suites.
6+
7+
## Structure
8+
9+
```
10+
azure/ # Generated from azure-http-specs (branded/Azure flavor)
11+
unbranded/ # Generated from http-specs (unbranded flavor)
12+
```
13+
14+
## Regeneration
15+
16+
These files are automatically regenerated by the
17+
[`typespec-python-regenerate.yml`](https://github.com/Azure/azure-sdk-for-python/blob/main/.github/workflows/typespec-python-regenerate.yml) workflow.
18+
19+
To regenerate manually, trigger the workflow via GitHub Actions or run locally:
20+
21+
```bash
22+
# From the microsoft/typespec repo (packages/http-client-python):
23+
npm install --ignore-scripts
24+
npm run build
25+
npm run install
26+
npm run prepare
27+
npm run regenerate
28+
29+
# Then copy tests/generated/{azure,unbranded} to this folder
30+
```
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Release History
2+
3+
## 1.0.0b1 (1970-01-01)
4+
5+
### Other Changes
6+
7+
- Initial version

0 commit comments

Comments
 (0)