From abd0279cb0fa98d3e7d1caa8ea6551d6b912a2c8 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Wed, 29 Apr 2026 10:43:20 -0700 Subject: [PATCH 1/8] don't reload modules --- .../http-client-python/emitter/src/emitter.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/http-client-python/emitter/src/emitter.ts b/packages/http-client-python/emitter/src/emitter.ts index 49110135355..82e7259b33b 100644 --- a/packages/http-client-python/emitter/src/emitter.ts +++ b/packages/http-client-python/emitter/src/emitter.ts @@ -235,7 +235,16 @@ async function onEmitMain(context: EmitContext) { if (typeof window !== "undefined") { // Running in browser with Pyodide - fileURLToPath and other filesystem operations are browser-incompatible - const pyodide = await setupPyodideCallBrowser(); + const pyodide = await browserPyodidePromise; + + if (!pyodide) { + reportDiagnostic(program, { + code: "browser-runtime-load-failed", + target: NoTarget, + format: { details: "" }, + }); + return; + } const yamlFilePath = "/yaml/python-yaml-path.yaml"; pyodide.FS.mkdirTree("/yaml"); @@ -325,6 +334,9 @@ async function onEmitMain(context: EmitContext) { } } +const browserPyodidePromise: Promise | null = + typeof window !== "undefined" ? setupPyodideCallBrowser() : null; + async function setupPyodideCallBrowser() { const pyodide = await loadPyodide({ indexURL: `https://cdn.jsdelivr.net/pyodide/v${PYODIDE_VERSION}/full/`, @@ -334,7 +346,7 @@ async function setupPyodideCallBrowser() { pyodide.FS.mkdirTree("/generator"); await pyodide.loadPackage("micropip"); const micropip = pyodide.pyimport("micropip"); - await micropip.install(getBrowserPygenWheelUrl()); + await micropip.install("/pygen-0.1.0-py3-none-any.whl"); return pyodide; } From 8e857e599dd3c01350a63abf4ee95e3691e76250 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Wed, 29 Apr 2026 10:44:30 -0700 Subject: [PATCH 2/8] minior --- packages/http-client-python/emitter/src/emitter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/http-client-python/emitter/src/emitter.ts b/packages/http-client-python/emitter/src/emitter.ts index 82e7259b33b..9ad1b7a28e7 100644 --- a/packages/http-client-python/emitter/src/emitter.ts +++ b/packages/http-client-python/emitter/src/emitter.ts @@ -346,7 +346,7 @@ async function setupPyodideCallBrowser() { pyodide.FS.mkdirTree("/generator"); await pyodide.loadPackage("micropip"); const micropip = pyodide.pyimport("micropip"); - await micropip.install("/pygen-0.1.0-py3-none-any.whl"); + await micropip.install(getBrowserPygenWheelUrl()); return pyodide; } From 7f333cf8b7c0921667f4aacbec71dfdf53c7e3b1 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Wed, 29 Apr 2026 11:27:35 -0700 Subject: [PATCH 3/8] clear mem between runs --- .../http-client-python/emitter/src/emitter.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/http-client-python/emitter/src/emitter.ts b/packages/http-client-python/emitter/src/emitter.ts index 9ad1b7a28e7..3ecd85c5f57 100644 --- a/packages/http-client-python/emitter/src/emitter.ts +++ b/packages/http-client-python/emitter/src/emitter.ts @@ -249,6 +249,7 @@ async function onEmitMain(context: EmitContext) { const yamlFilePath = "/yaml/python-yaml-path.yaml"; pyodide.FS.mkdirTree("/yaml"); pyodide.FS.mkdirTree("/output"); + clearMemfsDirectory(pyodide, "/output"); pyodide.FS.writeFile(yamlFilePath, jsyaml.dump(parsedYamlMap)); await runPyodideGeneration(pyodide, "/output", yamlFilePath, commandArgs); @@ -337,6 +338,22 @@ async function onEmitMain(context: EmitContext) { const browserPyodidePromise: Promise | null = typeof window !== "undefined" ? setupPyodideCallBrowser() : null; +function clearMemfsDirectory(pyodide: PyodideInterface, dir: string): void { + const entries: string[] = pyodide.FS.readdir(dir).filter( + (entry: string) => entry !== "." && entry !== "..", + ); + for (const entry of entries) { + const fullPath = `${dir}/${entry}`; + const stats = pyodide.FS.stat(fullPath); + if (pyodide.FS.isDir(stats.mode)) { + clearMemfsDirectory(pyodide, fullPath); + pyodide.FS.rmdir(fullPath); + } else { + pyodide.FS.unlink(fullPath); + } + } +} + async function setupPyodideCallBrowser() { const pyodide = await loadPyodide({ indexURL: `https://cdn.jsdelivr.net/pyodide/v${PYODIDE_VERSION}/full/`, From 666926fa127a7f0b9a67c843ac12a798a2d13346 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Wed, 29 Apr 2026 11:27:51 -0700 Subject: [PATCH 4/8] increase python emitter debounce --- packages/playground-website/src/main.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/playground-website/src/main.tsx b/packages/playground-website/src/main.tsx index 6ce6da3025b..3eaa9e3476e 100644 --- a/packages/playground-website/src/main.tsx +++ b/packages/playground-website/src/main.tsx @@ -58,6 +58,7 @@ const App = () => { {...PlaygroundManifest} samples={samples} emitterViewers={{ "@typespec/openapi3": [SwaggerUIViewer] }} + emitterOptions={{ "@typespec/http-client-python": { debounce: 1000 } }} importConfig={{ useShim: true }} footer={} commandBarItems={[importItem]} From 93edabffbbf49bcc63413efab71f313f02801dba Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Wed, 29 Apr 2026 13:14:46 -0700 Subject: [PATCH 5/8] temp --- .../templates/stages/emitter-stages.yml | 114 +++++++++++------- 1 file changed, 72 insertions(+), 42 deletions(-) diff --git a/eng/emitters/pipelines/templates/stages/emitter-stages.yml b/eng/emitters/pipelines/templates/stages/emitter-stages.yml index 2b0a5721387..3f2694ba27d 100644 --- a/eng/emitters/pipelines/templates/stages/emitter-stages.yml +++ b/eng/emitters/pipelines/templates/stages/emitter-stages.yml @@ -370,48 +370,9 @@ stages: ArtifactPath: $(buildArtifactsPath) LanguageShortName: ${{ parameters.LanguageShortName }} - - ${{ if parameters.UploadPlaygroundBundle }}: - - task: NodeTool@0 - displayName: Use Node 22.x for playground bundle - inputs: - versionSpec: "22.x" - - script: npm ci - displayName: Install emitter dependencies for playground bundle - workingDirectory: $(Build.SourcesDirectory)/${{ parameters.PackagePath }} - - ${{ if parameters.BuildPrereleaseVersion }}: - - script: | - CURRENT_VERSION=$(node -p "require('./package.json').version") - NEW_VERSION="${CURRENT_VERSION}-alpha.${BUILD_BUILDNUMBER}" - echo "Setting version to $NEW_VERSION" - npm version "$NEW_VERSION" --no-git-tag-version - displayName: Stamp build version for playground bundle - workingDirectory: $(Build.SourcesDirectory)/${{ parameters.PackagePath }} - - ${{ else }}: - - script: | - CURRENT_VERSION=$(node -p "require('./package.json').version") - NEW_VERSION="${CURRENT_VERSION}-beta.${BUILD_BUILDNUMBER}" - echo "Setting version to $NEW_VERSION" - npm version "$NEW_VERSION" --no-git-tag-version - displayName: Stamp build version for playground bundle - workingDirectory: $(Build.SourcesDirectory)/${{ parameters.PackagePath }} - - script: npm run ${{ parameters.PlaygroundBundleBuildScript }} - displayName: Build emitter for playground bundle - workingDirectory: $(Build.SourcesDirectory)/${{ parameters.PackagePath }} - - script: npm install -g pnpm - displayName: Install pnpm for playground bundle upload - - script: pnpm install --filter "@typespec/bundle-uploader..." - displayName: Install bundle-uploader dependencies - workingDirectory: $(Build.SourcesDirectory) - - script: pnpm --filter "@typespec/bundle-uploader..." build - displayName: Build bundle-uploader - workingDirectory: $(Build.SourcesDirectory) - - task: AzureCLI@1 - displayName: Upload playground bundle - inputs: - azureSubscription: "Azure SDK Engineering System" - scriptLocation: inlineScript - inlineScript: node ./eng/emitters/scripts/upload-bundled-emitter.js ${{ parameters.PackagePath }} - workingDirectory: $(Build.SourcesDirectory) + # Playground upload moved to separate UploadPlayground stage below. + # - ${{ if parameters.UploadPlaygroundBundle }}: + # - task: NodeTool@0 ... - ${{ if and(parameters.PlaygroundServerDockerfile, parameters.PlaygroundServerAppName) }}: - task: AzureCLI@1 @@ -450,3 +411,72 @@ stages: - output: pipelineArtifact path: $(Build.ArtifactStagingDirectory) artifact: publish_artifacts + + # Temporary separate stage for playground bundle upload so it can run independently + # of the Publish stage (which may fail on unrelated steps). + - ${{ if parameters.UploadPlaygroundBundle }}: + - stage: ${{ parameters.StagePrefix }}_UploadPlayground + displayName: ${{ parameters.StagePrefix }} - Upload Playground Bundle + condition: and(succeeded(), ${{ parameters.Condition }}) + dependsOn: + - ${{ parameters.DependsOn }} + - ${{ parameters.StagePrefix }}_Build + variables: + selfRepositoryPath: $(Build.SourcesDirectory) + pool: + name: $(LINUXPOOL) + image: $(LINUXVMIMAGE) + os: linux + jobs: + - job: UploadPlayground + steps: + - checkout: self + + - task: NodeTool@0 + displayName: Use Node 22.x for playground bundle + inputs: + versionSpec: "22.x" + + - script: npm ci + displayName: Install emitter dependencies for playground bundle + workingDirectory: $(Build.SourcesDirectory)/${{ parameters.PackagePath }} + + - ${{ if parameters.BuildPrereleaseVersion }}: + - script: | + CURRENT_VERSION=$(node -p "require('./package.json').version") + NEW_VERSION="${CURRENT_VERSION}-alpha.${BUILD_BUILDNUMBER}" + echo "Setting version to $NEW_VERSION" + npm version "$NEW_VERSION" --no-git-tag-version + displayName: Stamp build version for playground bundle + workingDirectory: $(Build.SourcesDirectory)/${{ parameters.PackagePath }} + - ${{ else }}: + - script: | + CURRENT_VERSION=$(node -p "require('./package.json').version") + NEW_VERSION="${CURRENT_VERSION}-beta.${BUILD_BUILDNUMBER}" + echo "Setting version to $NEW_VERSION" + npm version "$NEW_VERSION" --no-git-tag-version + displayName: Stamp build version for playground bundle + workingDirectory: $(Build.SourcesDirectory)/${{ parameters.PackagePath }} + + - script: npm run ${{ parameters.PlaygroundBundleBuildScript }} + displayName: Build emitter for playground bundle + workingDirectory: $(Build.SourcesDirectory)/${{ parameters.PackagePath }} + + - script: npm install -g pnpm + displayName: Install pnpm for playground bundle upload + + - script: pnpm install --filter "@typespec/bundle-uploader..." + displayName: Install bundle-uploader dependencies + workingDirectory: $(Build.SourcesDirectory) + + - script: pnpm --filter "@typespec/bundle-uploader..." build + displayName: Build bundle-uploader + workingDirectory: $(Build.SourcesDirectory) + + - task: AzureCLI@1 + displayName: Upload playground bundle + inputs: + azureSubscription: "Azure SDK Engineering System" + scriptLocation: inlineScript + inlineScript: node ./eng/emitters/scripts/upload-bundled-emitter.js ${{ parameters.PackagePath }} + workingDirectory: $(Build.SourcesDirectory) From 8d93a12b02fe82161459af667c078a70bcab3b31 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Wed, 29 Apr 2026 13:35:20 -0700 Subject: [PATCH 6/8] minor --- packages/playground-website/src/main.tsx | 1 - website/src/components/playground-component/playground.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/playground-website/src/main.tsx b/packages/playground-website/src/main.tsx index 3eaa9e3476e..6ce6da3025b 100644 --- a/packages/playground-website/src/main.tsx +++ b/packages/playground-website/src/main.tsx @@ -58,7 +58,6 @@ const App = () => { {...PlaygroundManifest} samples={samples} emitterViewers={{ "@typespec/openapi3": [SwaggerUIViewer] }} - emitterOptions={{ "@typespec/http-client-python": { debounce: 1000 } }} importConfig={{ useShim: true }} footer={} commandBarItems={[importItem]} diff --git a/website/src/components/playground-component/playground.tsx b/website/src/components/playground-component/playground.tsx index dc55e0f64cc..14bab6ced4d 100644 --- a/website/src/components/playground-component/playground.tsx +++ b/website/src/components/playground-component/playground.tsx @@ -37,7 +37,7 @@ export const WebsitePlayground = ({ versionData }: WebsitePlaygroundProps) => { libraries={imports} emitterViewers={{ "@typespec/openapi3": [SwaggerUIViewer] }} emitterOptions={{ - "@typespec/http-client-python": { debounce: 500 }, + "@typespec/http-client-python": { debounce: 1000 }, "@typespec/http-client-csharp": { debounce: 500 }, }} importConfig={{ useShim: true }} From 3351f0013c4904221c018ffe84e754ab694ea4d9 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Wed, 29 Apr 2026 13:36:28 -0700 Subject: [PATCH 7/8] undo smth --- .../templates/stages/emitter-stages.yml | 114 +++++++----------- 1 file changed, 42 insertions(+), 72 deletions(-) diff --git a/eng/emitters/pipelines/templates/stages/emitter-stages.yml b/eng/emitters/pipelines/templates/stages/emitter-stages.yml index 3f2694ba27d..2b0a5721387 100644 --- a/eng/emitters/pipelines/templates/stages/emitter-stages.yml +++ b/eng/emitters/pipelines/templates/stages/emitter-stages.yml @@ -370,9 +370,48 @@ stages: ArtifactPath: $(buildArtifactsPath) LanguageShortName: ${{ parameters.LanguageShortName }} - # Playground upload moved to separate UploadPlayground stage below. - # - ${{ if parameters.UploadPlaygroundBundle }}: - # - task: NodeTool@0 ... + - ${{ if parameters.UploadPlaygroundBundle }}: + - task: NodeTool@0 + displayName: Use Node 22.x for playground bundle + inputs: + versionSpec: "22.x" + - script: npm ci + displayName: Install emitter dependencies for playground bundle + workingDirectory: $(Build.SourcesDirectory)/${{ parameters.PackagePath }} + - ${{ if parameters.BuildPrereleaseVersion }}: + - script: | + CURRENT_VERSION=$(node -p "require('./package.json').version") + NEW_VERSION="${CURRENT_VERSION}-alpha.${BUILD_BUILDNUMBER}" + echo "Setting version to $NEW_VERSION" + npm version "$NEW_VERSION" --no-git-tag-version + displayName: Stamp build version for playground bundle + workingDirectory: $(Build.SourcesDirectory)/${{ parameters.PackagePath }} + - ${{ else }}: + - script: | + CURRENT_VERSION=$(node -p "require('./package.json').version") + NEW_VERSION="${CURRENT_VERSION}-beta.${BUILD_BUILDNUMBER}" + echo "Setting version to $NEW_VERSION" + npm version "$NEW_VERSION" --no-git-tag-version + displayName: Stamp build version for playground bundle + workingDirectory: $(Build.SourcesDirectory)/${{ parameters.PackagePath }} + - script: npm run ${{ parameters.PlaygroundBundleBuildScript }} + displayName: Build emitter for playground bundle + workingDirectory: $(Build.SourcesDirectory)/${{ parameters.PackagePath }} + - script: npm install -g pnpm + displayName: Install pnpm for playground bundle upload + - script: pnpm install --filter "@typespec/bundle-uploader..." + displayName: Install bundle-uploader dependencies + workingDirectory: $(Build.SourcesDirectory) + - script: pnpm --filter "@typespec/bundle-uploader..." build + displayName: Build bundle-uploader + workingDirectory: $(Build.SourcesDirectory) + - task: AzureCLI@1 + displayName: Upload playground bundle + inputs: + azureSubscription: "Azure SDK Engineering System" + scriptLocation: inlineScript + inlineScript: node ./eng/emitters/scripts/upload-bundled-emitter.js ${{ parameters.PackagePath }} + workingDirectory: $(Build.SourcesDirectory) - ${{ if and(parameters.PlaygroundServerDockerfile, parameters.PlaygroundServerAppName) }}: - task: AzureCLI@1 @@ -411,72 +450,3 @@ stages: - output: pipelineArtifact path: $(Build.ArtifactStagingDirectory) artifact: publish_artifacts - - # Temporary separate stage for playground bundle upload so it can run independently - # of the Publish stage (which may fail on unrelated steps). - - ${{ if parameters.UploadPlaygroundBundle }}: - - stage: ${{ parameters.StagePrefix }}_UploadPlayground - displayName: ${{ parameters.StagePrefix }} - Upload Playground Bundle - condition: and(succeeded(), ${{ parameters.Condition }}) - dependsOn: - - ${{ parameters.DependsOn }} - - ${{ parameters.StagePrefix }}_Build - variables: - selfRepositoryPath: $(Build.SourcesDirectory) - pool: - name: $(LINUXPOOL) - image: $(LINUXVMIMAGE) - os: linux - jobs: - - job: UploadPlayground - steps: - - checkout: self - - - task: NodeTool@0 - displayName: Use Node 22.x for playground bundle - inputs: - versionSpec: "22.x" - - - script: npm ci - displayName: Install emitter dependencies for playground bundle - workingDirectory: $(Build.SourcesDirectory)/${{ parameters.PackagePath }} - - - ${{ if parameters.BuildPrereleaseVersion }}: - - script: | - CURRENT_VERSION=$(node -p "require('./package.json').version") - NEW_VERSION="${CURRENT_VERSION}-alpha.${BUILD_BUILDNUMBER}" - echo "Setting version to $NEW_VERSION" - npm version "$NEW_VERSION" --no-git-tag-version - displayName: Stamp build version for playground bundle - workingDirectory: $(Build.SourcesDirectory)/${{ parameters.PackagePath }} - - ${{ else }}: - - script: | - CURRENT_VERSION=$(node -p "require('./package.json').version") - NEW_VERSION="${CURRENT_VERSION}-beta.${BUILD_BUILDNUMBER}" - echo "Setting version to $NEW_VERSION" - npm version "$NEW_VERSION" --no-git-tag-version - displayName: Stamp build version for playground bundle - workingDirectory: $(Build.SourcesDirectory)/${{ parameters.PackagePath }} - - - script: npm run ${{ parameters.PlaygroundBundleBuildScript }} - displayName: Build emitter for playground bundle - workingDirectory: $(Build.SourcesDirectory)/${{ parameters.PackagePath }} - - - script: npm install -g pnpm - displayName: Install pnpm for playground bundle upload - - - script: pnpm install --filter "@typespec/bundle-uploader..." - displayName: Install bundle-uploader dependencies - workingDirectory: $(Build.SourcesDirectory) - - - script: pnpm --filter "@typespec/bundle-uploader..." build - displayName: Build bundle-uploader - workingDirectory: $(Build.SourcesDirectory) - - - task: AzureCLI@1 - displayName: Upload playground bundle - inputs: - azureSubscription: "Azure SDK Engineering System" - scriptLocation: inlineScript - inlineScript: node ./eng/emitters/scripts/upload-bundled-emitter.js ${{ parameters.PackagePath }} - workingDirectory: $(Build.SourcesDirectory) From 0532f052d099b9721122349e6b1c9cf6321b13e6 Mon Sep 17 00:00:00 2001 From: jennypng <63012604+JennyPng@users.noreply.github.com> Date: Wed, 29 Apr 2026 14:53:22 -0700 Subject: [PATCH 8/8] chronus --- .../changes/python-module-loading-2026-3-29-14-53-8.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .chronus/changes/python-module-loading-2026-3-29-14-53-8.md diff --git a/.chronus/changes/python-module-loading-2026-3-29-14-53-8.md b/.chronus/changes/python-module-loading-2026-3-29-14-53-8.md new file mode 100644 index 00000000000..ad1db7ca43d --- /dev/null +++ b/.chronus/changes/python-module-loading-2026-3-29-14-53-8.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/http-client-python" +--- + +Update Python emitter browser behavior to only load Pyodide once instead of on every emit