@typespec/http-client-python 0.28.3 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/emitter/code-model.d.ts.map +1 -1
- package/dist/emitter/code-model.js +1 -0
- package/dist/emitter/code-model.js.map +1 -1
- package/dist/emitter/constants.d.ts +6 -0
- package/dist/emitter/constants.d.ts.map +1 -0
- package/dist/emitter/constants.js +27 -0
- package/dist/emitter/constants.js.map +1 -0
- package/dist/emitter/emitter.d.ts.map +1 -1
- package/dist/emitter/emitter.js +169 -94
- package/dist/emitter/emitter.js.map +1 -1
- package/dist/emitter/http.d.ts.map +1 -1
- package/dist/emitter/http.js +33 -1
- package/dist/emitter/http.js.map +1 -1
- package/dist/emitter/lib.d.ts +33 -2
- package/dist/emitter/lib.d.ts.map +1 -1
- package/dist/emitter/lib.js +17 -0
- package/dist/emitter/lib.js.map +1 -1
- package/dist/emitter/run-python3.js +1 -1
- package/dist/emitter/run-python3.js.map +1 -1
- package/dist/emitter/types.d.ts.map +1 -1
- package/dist/emitter/types.js +31 -5
- package/dist/emitter/types.js.map +1 -1
- package/emitter/src/code-model.ts +1 -0
- package/emitter/src/constants.ts +27 -0
- package/emitter/src/emitter.ts +216 -96
- package/emitter/src/http.ts +29 -1
- package/emitter/src/lib.ts +20 -0
- package/emitter/src/run-python3.ts +1 -1
- package/emitter/src/types.ts +35 -5
- package/emitter/temp/tsconfig.tsbuildinfo +1 -1
- package/emitter/test/utils.test.ts +3 -0
- package/emitter/tsconfig.build.json +2 -1
- package/emitter/tsconfig.json +2 -1
- package/eng/scripts/ci/config/eslint-ci.config.mjs +10 -0
- package/eng/scripts/ci/config/mypy.ini +1 -1
- package/eng/scripts/ci/config/pylintrc +1 -1
- package/eng/scripts/ci/config/pyrightconfig.json +1 -1
- package/eng/scripts/ci/dev_requirements.txt +1 -1
- package/eng/scripts/ci/lint.ts +11 -4
- package/eng/scripts/ci/regenerate-common.ts +407 -136
- package/eng/scripts/ci/regenerate.ts +103 -194
- package/eng/scripts/ci/run-tests.ts +153 -48
- package/eng/scripts/ci/run_apiview.py +22 -16
- package/eng/scripts/ci/run_mypy.py +28 -18
- package/eng/scripts/ci/run_pylint.py +23 -16
- package/eng/scripts/ci/run_pyright.py +5 -2
- package/eng/scripts/ci/run_sphinx_build.py +24 -9
- package/eng/scripts/ci/util.py +61 -25
- package/eng/scripts/setup/__pycache__/package_manager.cpython-311.pyc +0 -0
- package/eng/scripts/setup/__pycache__/venvtools.cpython-311.pyc +0 -0
- package/eng/scripts/setup/build_pygen_wheel.py +3 -3
- package/eng/scripts/setup/install.py +3 -3
- package/eng/scripts/setup/install.ts +1 -1
- package/eng/scripts/setup/prepare.py +2 -2
- package/eng/scripts/setup/run-python3.ts +1 -1
- package/eng/scripts/setup/run_batch.py +197 -0
- package/eng/scripts/setup/venvtools.py +0 -1
- package/generator/build/lib/pygen/__init__.py +1 -3
- package/generator/build/lib/pygen/codegen/__init__.py +0 -1
- package/generator/build/lib/pygen/codegen/models/base.py +0 -1
- package/generator/build/lib/pygen/codegen/models/client.py +2 -1
- package/generator/build/lib/pygen/codegen/models/code_model.py +1 -0
- package/generator/build/lib/pygen/codegen/models/enum_type.py +0 -1
- package/generator/build/lib/pygen/codegen/models/model_type.py +1 -7
- package/generator/build/lib/pygen/codegen/models/parameter.py +16 -14
- package/generator/build/lib/pygen/codegen/serializers/__init__.py +27 -12
- package/generator/build/lib/pygen/codegen/serializers/builder_serializer.py +12 -3
- package/generator/build/lib/pygen/codegen/serializers/general_serializer.py +6 -2
- package/generator/build/lib/pygen/codegen/serializers/import_serializer.py +17 -4
- package/generator/build/lib/pygen/codegen/serializers/parameter_serializer.py +17 -11
- package/generator/build/lib/pygen/codegen/templates/model_base.py.jinja2 +19 -7
- package/generator/build/lib/pygen/codegen/templates/packaging_templates/README.md.jinja2 +11 -3
- package/generator/build/lib/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2 +1 -1
- package/generator/build/lib/pygen/codegen/templates/serialization.py.jinja2 +5 -1
- package/generator/build/lib/pygen/preprocess/__init__.py +37 -30
- package/generator/component-detection-pip-report.json +7 -7
- package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
- package/generator/pygen/__init__.py +1 -3
- package/generator/pygen/codegen/__init__.py +0 -1
- package/generator/pygen/codegen/models/base.py +0 -1
- package/generator/pygen/codegen/models/client.py +2 -1
- package/generator/pygen/codegen/models/code_model.py +1 -0
- package/generator/pygen/codegen/models/enum_type.py +0 -1
- package/generator/pygen/codegen/models/model_type.py +1 -7
- package/generator/pygen/codegen/models/parameter.py +16 -14
- package/generator/pygen/codegen/serializers/__init__.py +27 -12
- package/generator/pygen/codegen/serializers/builder_serializer.py +12 -3
- package/generator/pygen/codegen/serializers/general_serializer.py +6 -2
- package/generator/pygen/codegen/serializers/import_serializer.py +17 -4
- package/generator/pygen/codegen/serializers/parameter_serializer.py +17 -11
- package/generator/pygen/codegen/templates/model_base.py.jinja2 +19 -7
- package/generator/pygen/codegen/templates/packaging_templates/README.md.jinja2 +11 -3
- package/generator/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2 +1 -1
- package/generator/pygen/codegen/templates/serialization.py.jinja2 +5 -1
- package/generator/pygen/preprocess/__init__.py +37 -30
- package/generator/pygen.egg-info/PKG-INFO +1 -2
- package/generator/pygen.egg-info/requires.txt +1 -1
- package/generator/setup.py +1 -2
- package/package.json +24 -9
package/emitter/src/emitter.ts
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
import { createSdkContext } from "@azure-tools/typespec-client-generator-core";
|
|
2
|
-
import { EmitContext, NoTarget } from "@typespec/compiler";
|
|
2
|
+
import { EmitContext, emitFile, joinPaths, NoTarget } from "@typespec/compiler";
|
|
3
3
|
import { execSync } from "child_process";
|
|
4
4
|
import fs from "fs";
|
|
5
|
+
import jsyaml from "js-yaml";
|
|
5
6
|
import os from "os";
|
|
6
7
|
import path, { dirname } from "path";
|
|
7
|
-
import { loadPyodide } from "pyodide";
|
|
8
|
+
import { loadPyodide, PyodideInterface } from "pyodide";
|
|
8
9
|
import { fileURLToPath } from "url";
|
|
10
|
+
import pkgJson from "../../package.json" with { type: "json" };
|
|
9
11
|
import { emitCodeModel } from "./code-model.js";
|
|
12
|
+
import {
|
|
13
|
+
blackExcludeDirs,
|
|
14
|
+
BLOB_STORAGE_BASE_URL,
|
|
15
|
+
PACKAGE_NAME,
|
|
16
|
+
PYGEN_WHEEL_FILENAME,
|
|
17
|
+
PYODIDE_VERSION,
|
|
18
|
+
} from "./constants.js";
|
|
10
19
|
import { saveCodeModelAsYaml } from "./external-process.js";
|
|
11
20
|
import { PythonEmitterOptions, PythonSdkContext, reportDiagnostic } from "./lib.js";
|
|
12
21
|
import { runPython3 } from "./run-python3.js";
|
|
13
22
|
import { getRootNamespace, md2Rst } from "./utils.js";
|
|
14
23
|
|
|
24
|
+
function getBrowserPygenWheelUrl(): string {
|
|
25
|
+
return `${BLOB_STORAGE_BASE_URL}/${PACKAGE_NAME}/${pkgJson.version}/generator/dist/${PYGEN_WHEEL_FILENAME}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
15
28
|
function addDefaultOptions(sdkContext: PythonSdkContext) {
|
|
16
29
|
const defaultOptions = {
|
|
17
30
|
"package-version": "1.0.0b1",
|
|
@@ -29,21 +42,24 @@ function addDefaultOptions(sdkContext: PythonSdkContext) {
|
|
|
29
42
|
const packageName = namespace.replace(/\./g, "-");
|
|
30
43
|
options["package-name"] = packageName;
|
|
31
44
|
}
|
|
32
|
-
|
|
33
|
-
// if they pass in a flavor other than azure, we want to ignore the value
|
|
34
|
-
(options as any).flavor = undefined;
|
|
35
|
-
}
|
|
45
|
+
// Set flavor based on namespace or passed option
|
|
36
46
|
if (getRootNamespace(sdkContext).toLowerCase().includes("azure")) {
|
|
37
47
|
(options as any).flavor = "azure";
|
|
48
|
+
} else if ((options as any).flavor !== "azure") {
|
|
49
|
+
// Explicitly set unbranded flavor when not azure
|
|
50
|
+
(options as any).flavor = "unbranded";
|
|
38
51
|
}
|
|
39
52
|
|
|
40
53
|
if (
|
|
41
54
|
options["package-pprint-name"] !== undefined &&
|
|
42
55
|
!options["package-pprint-name"].startsWith('"')
|
|
43
56
|
) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
57
|
+
// Only add quotes for shell compatibility when NOT using emit-yaml-only mode
|
|
58
|
+
// (emit-yaml-only passes options via JSON config files, not shell)
|
|
59
|
+
const needsShellQuoting = !options["use-pyodide"] && !options["emit-yaml-only"];
|
|
60
|
+
options["package-pprint-name"] = needsShellQuoting
|
|
61
|
+
? `"${options["package-pprint-name"]}"`
|
|
62
|
+
: `${options["package-pprint-name"]}`;
|
|
47
63
|
}
|
|
48
64
|
}
|
|
49
65
|
|
|
@@ -102,6 +118,60 @@ function walkThroughNodes(yamlMap: Record<string, any>): Record<string, any> {
|
|
|
102
118
|
return yamlMap;
|
|
103
119
|
}
|
|
104
120
|
|
|
121
|
+
const pyodideGenerationCode = `
|
|
122
|
+
async def main():
|
|
123
|
+
import warnings
|
|
124
|
+
with warnings.catch_warnings():
|
|
125
|
+
from pygen import preprocess, codegen, black
|
|
126
|
+
preprocess.PreProcessPlugin(output_folder=outputFolder, tsp_file=yamlFile, **commandArgs).process()
|
|
127
|
+
codegen.CodeGenerator(output_folder=outputFolder, tsp_file=yamlFile, **commandArgs).process()
|
|
128
|
+
black.BlackScriptPlugin(output_folder=outputFolder, **commandArgs).process()
|
|
129
|
+
|
|
130
|
+
await main()`;
|
|
131
|
+
|
|
132
|
+
async function runPyodideGeneration(
|
|
133
|
+
pyodide: PyodideInterface,
|
|
134
|
+
outputFolder: string,
|
|
135
|
+
yamlFile: string,
|
|
136
|
+
commandArgs: Record<string, string>,
|
|
137
|
+
) {
|
|
138
|
+
const globals = pyodide.toPy({
|
|
139
|
+
outputFolder,
|
|
140
|
+
yamlFile,
|
|
141
|
+
commandArgs,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
await pyodide.runPythonAsync(pyodideGenerationCode, { globals });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function copyPyodideOutputToHost(
|
|
148
|
+
context: EmitContext<PythonEmitterOptions>,
|
|
149
|
+
pyodide: PyodideInterface,
|
|
150
|
+
memfsDir: string,
|
|
151
|
+
relativeDir: string = "",
|
|
152
|
+
) {
|
|
153
|
+
const entries = pyodide.FS.readdir(memfsDir).filter(
|
|
154
|
+
(entry: string) => entry !== "." && entry !== "..",
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
for (const entry of entries) {
|
|
158
|
+
const memfsPath = `${memfsDir}/${entry}`;
|
|
159
|
+
const relativePath = relativeDir ? `${relativeDir}/${entry}` : entry;
|
|
160
|
+
const stats = pyodide.FS.stat(memfsPath);
|
|
161
|
+
|
|
162
|
+
if (pyodide.FS.isDir(stats.mode)) {
|
|
163
|
+
await copyPyodideOutputToHost(context, pyodide, memfsPath, relativePath);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const content = pyodide.FS.readFile(memfsPath, { encoding: "utf8" });
|
|
168
|
+
await emitFile(context.program, {
|
|
169
|
+
path: joinPaths(context.emitterOutputDir, relativePath),
|
|
170
|
+
content,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
105
175
|
export async function $onEmit(context: EmitContext<PythonEmitterOptions>) {
|
|
106
176
|
try {
|
|
107
177
|
await onEmitMain(context);
|
|
@@ -122,13 +192,21 @@ export async function $onEmit(context: EmitContext<PythonEmitterOptions>) {
|
|
|
122
192
|
async function onEmitMain(context: EmitContext<PythonEmitterOptions>) {
|
|
123
193
|
const program = context.program;
|
|
124
194
|
const sdkContext = await createPythonSdkContext(context);
|
|
125
|
-
|
|
195
|
+
|
|
126
196
|
const outputDir = context.emitterOutputDir;
|
|
127
197
|
addDefaultOptions(sdkContext);
|
|
128
198
|
const yamlMap = emitCodeModel(sdkContext);
|
|
129
199
|
const parsedYamlMap = walkThroughNodes(yamlMap);
|
|
130
200
|
|
|
131
|
-
|
|
201
|
+
// Python emitter requires an SDK client in the TypeSpec
|
|
202
|
+
if (sdkContext.sdkPackage.clients.length === 0) {
|
|
203
|
+
reportDiagnostic(program, {
|
|
204
|
+
code: "no-sdk-clients",
|
|
205
|
+
target: NoTarget,
|
|
206
|
+
});
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
132
210
|
const resolvedOptions = sdkContext.emitContext.options;
|
|
133
211
|
const commandArgs: Record<string, string> = {};
|
|
134
212
|
if (resolvedOptions["packaging-files-config"]) {
|
|
@@ -155,99 +233,141 @@ async function onEmitMain(context: EmitContext<PythonEmitterOptions>) {
|
|
|
155
233
|
commandArgs["from-typespec"] = "true";
|
|
156
234
|
commandArgs["models-mode"] = (resolvedOptions as any)["models-mode"] ?? "dpg";
|
|
157
235
|
|
|
158
|
-
if (
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
236
|
+
if (typeof window !== "undefined") {
|
|
237
|
+
// Running in browser with Pyodide - fileURLToPath and other filesystem operations are browser-incompatible
|
|
238
|
+
const pyodide = await browserPyodidePromise;
|
|
239
|
+
|
|
240
|
+
if (!pyodide) {
|
|
241
|
+
reportDiagnostic(program, {
|
|
242
|
+
code: "browser-runtime-load-failed",
|
|
243
|
+
target: NoTarget,
|
|
244
|
+
format: { details: "" },
|
|
245
|
+
});
|
|
246
|
+
return;
|
|
168
247
|
}
|
|
169
248
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
249
|
+
const yamlFilePath = "/yaml/python-yaml-path.yaml";
|
|
250
|
+
pyodide.FS.mkdirTree("/yaml");
|
|
251
|
+
pyodide.FS.mkdirTree("/output");
|
|
252
|
+
clearMemfsDirectory(pyodide, "/output");
|
|
253
|
+
pyodide.FS.writeFile(yamlFilePath, jsyaml.dump(parsedYamlMap));
|
|
254
|
+
|
|
255
|
+
await runPyodideGeneration(pyodide, "/output", yamlFilePath, commandArgs);
|
|
256
|
+
await copyPyodideOutputToHost(context, pyodide, "/output");
|
|
257
|
+
} else {
|
|
258
|
+
const root = path.join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
259
|
+
const yamlPath = await saveCodeModelAsYaml("python-yaml-path", parsedYamlMap);
|
|
260
|
+
|
|
261
|
+
if (!program.compilerOptions.noEmit && !program.hasError()) {
|
|
262
|
+
// If emit-yaml-only mode, just copy YAML to output dir for batch processing
|
|
263
|
+
if (resolvedOptions["emit-yaml-only"]) {
|
|
264
|
+
if (!fs.existsSync(outputDir)) {
|
|
265
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
266
|
+
}
|
|
267
|
+
// Copy YAML to output dir with command args embedded
|
|
268
|
+
// Use unique filename to avoid conflicts when multiple specs share output dir
|
|
269
|
+
const configId = path.basename(yamlPath, ".yaml");
|
|
270
|
+
const batchConfig = { yamlPath, commandArgs, outputDir };
|
|
271
|
+
fs.writeFileSync(
|
|
272
|
+
path.join(outputDir, `.tsp-codegen-${configId}.json`),
|
|
273
|
+
JSON.stringify(batchConfig, null, 2),
|
|
274
|
+
);
|
|
275
|
+
return;
|
|
176
276
|
}
|
|
177
|
-
//
|
|
178
|
-
pyodide.
|
|
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
|
-
|
|
277
|
+
// if not using pyodide and there's no venv, we try to create venv
|
|
278
|
+
if (!resolvedOptions["use-pyodide"] && !fs.existsSync(path.join(root, "venv"))) {
|
|
279
|
+
try {
|
|
280
|
+
await runPython3(path.join(root, "/eng/scripts/setup/install.py"));
|
|
281
|
+
await runPython3(path.join(root, "/eng/scripts/setup/prepare.py"));
|
|
282
|
+
} catch {
|
|
283
|
+
// if the python env is not ready, we use pyodide instead
|
|
284
|
+
resolvedOptions["use-pyodide"] = true;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (resolvedOptions["use-pyodide"]) {
|
|
289
|
+
// here we run with pyodide
|
|
290
|
+
const pyodide = await setupPyodideCall(root);
|
|
291
|
+
// create the output folder if not exists
|
|
292
|
+
if (!fs.existsSync(outputDir)) {
|
|
293
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
294
|
+
}
|
|
295
|
+
// mount output folder to pyodide
|
|
296
|
+
pyodide.FS.mkdirTree("/output");
|
|
297
|
+
pyodide.FS.mount(pyodide.FS.filesystems.NODEFS, { root: outputDir }, "/output");
|
|
298
|
+
// mount yaml file to pyodide
|
|
299
|
+
pyodide.FS.mkdirTree("/yaml");
|
|
300
|
+
pyodide.FS.mount(pyodide.FS.filesystems.NODEFS, { root: path.dirname(yamlPath) }, "/yaml");
|
|
301
|
+
await runPyodideGeneration(
|
|
302
|
+
pyodide,
|
|
303
|
+
"/output",
|
|
304
|
+
`/yaml/${path.basename(yamlPath)}`,
|
|
305
|
+
commandArgs,
|
|
306
|
+
);
|
|
206
307
|
} else {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
308
|
+
// here we run with native python
|
|
309
|
+
let venvPath = path.join(root, "venv");
|
|
310
|
+
if (fs.existsSync(path.join(venvPath, "bin"))) {
|
|
311
|
+
venvPath = path.join(venvPath, "bin", "python");
|
|
312
|
+
} else if (fs.existsSync(path.join(venvPath, "Scripts"))) {
|
|
313
|
+
venvPath = path.join(venvPath, "Scripts", "python.exe");
|
|
314
|
+
} else {
|
|
315
|
+
reportDiagnostic(program, {
|
|
316
|
+
code: "pyodide-flag-conflict",
|
|
317
|
+
target: NoTarget,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
commandArgs["output-folder"] = outputDir;
|
|
321
|
+
commandArgs["tsp-file"] = yamlPath;
|
|
322
|
+
const commandFlags = Object.entries(commandArgs)
|
|
323
|
+
.map(([key, value]) => `--${key}=${value}`)
|
|
324
|
+
.join(" ");
|
|
325
|
+
const command = `${venvPath} ${root}/eng/scripts/setup/run_tsp.py ${commandFlags}`;
|
|
326
|
+
execSync(command);
|
|
327
|
+
|
|
328
|
+
const excludePattern = blackExcludeDirs.join("|");
|
|
329
|
+
execSync(
|
|
330
|
+
`${venvPath} -m black --line-length=120 --quiet --fast ${outputDir} --exclude "${excludePattern}"`,
|
|
331
|
+
);
|
|
332
|
+
await checkForPylintIssues(outputDir, excludePattern);
|
|
211
333
|
}
|
|
212
|
-
commandArgs["output-folder"] = outputDir;
|
|
213
|
-
commandArgs["tsp-file"] = yamlPath;
|
|
214
|
-
const commandFlags = Object.entries(commandArgs)
|
|
215
|
-
.map(([key, value]) => `--${key}=${value}`)
|
|
216
|
-
.join(" ");
|
|
217
|
-
const command = `${venvPath} ${root}/eng/scripts/setup/run_tsp.py ${commandFlags}`;
|
|
218
|
-
execSync(command);
|
|
219
|
-
|
|
220
|
-
const blackExcludeDirs = [
|
|
221
|
-
"__pycache__/",
|
|
222
|
-
"node_modules/",
|
|
223
|
-
"venv/",
|
|
224
|
-
"env/",
|
|
225
|
-
".direnv",
|
|
226
|
-
".eggs",
|
|
227
|
-
".git",
|
|
228
|
-
".hg",
|
|
229
|
-
".tox",
|
|
230
|
-
".venv",
|
|
231
|
-
".eggs",
|
|
232
|
-
".mypy_cache",
|
|
233
|
-
".pytest_cache",
|
|
234
|
-
".vscode",
|
|
235
|
-
".*_build/",
|
|
236
|
-
"/build/",
|
|
237
|
-
"dist",
|
|
238
|
-
".nox",
|
|
239
|
-
".svn",
|
|
240
|
-
"TempTypeSpecFiles/",
|
|
241
|
-
];
|
|
242
|
-
const excludePattern = blackExcludeDirs.join("|");
|
|
243
|
-
execSync(
|
|
244
|
-
`${venvPath} -m black --line-length=120 --quiet --fast ${outputDir} --exclude "${excludePattern}"`,
|
|
245
|
-
);
|
|
246
|
-
await checkForPylintIssues(outputDir, excludePattern);
|
|
247
334
|
}
|
|
248
335
|
}
|
|
249
336
|
}
|
|
250
337
|
|
|
338
|
+
const browserPyodidePromise: Promise<PyodideInterface> | null =
|
|
339
|
+
typeof window !== "undefined" ? setupPyodideCallBrowser() : null;
|
|
340
|
+
|
|
341
|
+
function clearMemfsDirectory(pyodide: PyodideInterface, dir: string): void {
|
|
342
|
+
const entries: string[] = pyodide.FS.readdir(dir).filter(
|
|
343
|
+
(entry: string) => entry !== "." && entry !== "..",
|
|
344
|
+
);
|
|
345
|
+
for (const entry of entries) {
|
|
346
|
+
const fullPath = `${dir}/${entry}`;
|
|
347
|
+
const stats = pyodide.FS.stat(fullPath);
|
|
348
|
+
if (pyodide.FS.isDir(stats.mode)) {
|
|
349
|
+
clearMemfsDirectory(pyodide, fullPath);
|
|
350
|
+
pyodide.FS.rmdir(fullPath);
|
|
351
|
+
} else {
|
|
352
|
+
pyodide.FS.unlink(fullPath);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
async function setupPyodideCallBrowser() {
|
|
358
|
+
const pyodide = await loadPyodide({
|
|
359
|
+
indexURL: `https://cdn.jsdelivr.net/pyodide/v${PYODIDE_VERSION}/full/`,
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// use default MEMFS for browser, since NODEFS is not supported
|
|
363
|
+
pyodide.FS.mkdirTree("/generator");
|
|
364
|
+
await pyodide.loadPackage("micropip");
|
|
365
|
+
const micropip = pyodide.pyimport("micropip");
|
|
366
|
+
await micropip.install(getBrowserPygenWheelUrl());
|
|
367
|
+
|
|
368
|
+
return pyodide;
|
|
369
|
+
}
|
|
370
|
+
|
|
251
371
|
async function setupPyodideCall(root: string) {
|
|
252
372
|
const pyodide = await loadPyodide({
|
|
253
373
|
indexURL: path.dirname(fileURLToPath(import.meta.resolve("pyodide"))),
|
|
@@ -277,7 +397,7 @@ async function setupPyodideCall(root: string) {
|
|
|
277
397
|
);
|
|
278
398
|
await pyodide.loadPackage("micropip");
|
|
279
399
|
const micropip = pyodide.pyimport("micropip");
|
|
280
|
-
await micropip.install(
|
|
400
|
+
await micropip.install(`emfs:/generator/dist/${PYGEN_WHEEL_FILENAME}`);
|
|
281
401
|
fs.closeSync(fd);
|
|
282
402
|
fs.unlinkSync(micropipLockPath);
|
|
283
403
|
break;
|
package/emitter/src/http.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NoTarget } from "@typespec/compiler";
|
|
1
|
+
import { getNamespaceFullName, NoTarget } from "@typespec/compiler";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
getHttpOperationParameter,
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
SdkQueryParameter,
|
|
20
20
|
SdkServiceMethod,
|
|
21
21
|
SdkServiceResponseHeader,
|
|
22
|
+
SdkType,
|
|
22
23
|
UsageFlags,
|
|
23
24
|
} from "@azure-tools/typespec-client-generator-core";
|
|
24
25
|
import { HttpStatusCodeRange } from "@typespec/http";
|
|
@@ -41,6 +42,32 @@ export enum ReferredByOperationTypes {
|
|
|
41
42
|
NonPagingOnly = 2,
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
function isEtagType(type: SdkType): boolean {
|
|
46
|
+
if (type.kind === "nullable") return isEtagType(type.type);
|
|
47
|
+
const raw = type.__raw;
|
|
48
|
+
if (!raw || raw.kind !== "Scalar") return false;
|
|
49
|
+
return (
|
|
50
|
+
raw.name === "eTag" &&
|
|
51
|
+
raw.namespace !== undefined &&
|
|
52
|
+
getNamespaceFullName(raw.namespace) === "Azure.Core"
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getEtagRole(parameter: SdkHeaderParameter): string | undefined {
|
|
57
|
+
const name = parameter.name.toLowerCase();
|
|
58
|
+
const wire = parameter.serializedName.toLowerCase();
|
|
59
|
+
// Standard If-Match / If-None-Match headers work with any type
|
|
60
|
+
if (wire === "if-match") return "ifMatch";
|
|
61
|
+
if (wire === "if-none-match") return "ifNoneMatch";
|
|
62
|
+
// Non-standard headers require Azure.Core.eTag type
|
|
63
|
+
if (!isEtagType(parameter.type)) return undefined;
|
|
64
|
+
if (name.includes("nonematch") || name.includes("none_match")) return "ifNoneMatch";
|
|
65
|
+
if (name.includes("match")) return "ifMatch";
|
|
66
|
+
if (wire.endsWith("-if-none-match")) return "ifNoneMatch";
|
|
67
|
+
if (wire.endsWith("-if-match")) return "ifMatch";
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
44
71
|
function isContentTypeParameter(parameter: SdkHeaderParameter) {
|
|
45
72
|
return parameter.serializedName.toLowerCase() === "content-type";
|
|
46
73
|
}
|
|
@@ -496,6 +523,7 @@ function emitHttpHeaderParameter(
|
|
|
496
523
|
delimiter,
|
|
497
524
|
explode,
|
|
498
525
|
clientDefaultValue,
|
|
526
|
+
etagRole: getEtagRole(parameter),
|
|
499
527
|
};
|
|
500
528
|
}
|
|
501
529
|
|
package/emitter/src/lib.ts
CHANGED
|
@@ -25,6 +25,7 @@ export interface PythonEmitterOptions {
|
|
|
25
25
|
"use-pyodide"?: boolean;
|
|
26
26
|
"keep-setup-py"?: boolean;
|
|
27
27
|
"clear-output-folder"?: boolean;
|
|
28
|
+
"emit-yaml-only"?: boolean;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
export interface PythonSdkContext extends SdkContext<PythonEmitterOptions> {
|
|
@@ -110,6 +111,12 @@ export const PythonEmitterOptionsSchema: JSONSchemaType<PythonEmitterOptions> =
|
|
|
110
111
|
description:
|
|
111
112
|
"Whether to clear the output folder before generating the code. Defaults to `false`.",
|
|
112
113
|
},
|
|
114
|
+
"emit-yaml-only": {
|
|
115
|
+
type: "boolean",
|
|
116
|
+
nullable: true,
|
|
117
|
+
description:
|
|
118
|
+
"Emit YAML code model only, without running Python generator. For batch processing.",
|
|
119
|
+
},
|
|
113
120
|
},
|
|
114
121
|
required: [],
|
|
115
122
|
};
|
|
@@ -131,6 +138,19 @@ const libDef = {
|
|
|
131
138
|
"Python is not installed. Please follow https://www.python.org/ to install Python or set 'use-pyodide' to true.",
|
|
132
139
|
},
|
|
133
140
|
},
|
|
141
|
+
"no-sdk-clients": {
|
|
142
|
+
severity: "error",
|
|
143
|
+
messages: {
|
|
144
|
+
default:
|
|
145
|
+
"The Python emitter did not find any SDK clients in this TypeSpec program. The current Python generator expects at least one client/service to generate code.",
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
"browser-runtime-load-failed": {
|
|
149
|
+
severity: "error",
|
|
150
|
+
messages: {
|
|
151
|
+
default: paramMessage`Failed to initialize the browser Python runtime.${"details"}`,
|
|
152
|
+
},
|
|
153
|
+
},
|
|
134
154
|
"invalid-paging-items": {
|
|
135
155
|
severity: "warning",
|
|
136
156
|
messages: {
|
|
@@ -11,7 +11,7 @@ import { patchPythonPath } from "./system-requirements.js";
|
|
|
11
11
|
|
|
12
12
|
export async function runPython3(...args: string[]) {
|
|
13
13
|
const command = await patchPythonPath(["python", ...args], {
|
|
14
|
-
version: ">=3.
|
|
14
|
+
version: ">=3.10",
|
|
15
15
|
environmentVariable: "AUTOREST_PYTHON_EXE",
|
|
16
16
|
});
|
|
17
17
|
cp.execSync(command.join(" "), {
|
package/emitter/src/types.ts
CHANGED
|
@@ -249,6 +249,7 @@ function emitProperty(
|
|
|
249
249
|
isMultipartFileInput: isMultipartFileInput,
|
|
250
250
|
xmlMetadata: getXmlMetadata(property),
|
|
251
251
|
encode: property.encode,
|
|
252
|
+
clientDefaultValue: property.clientDefaultValue,
|
|
252
253
|
};
|
|
253
254
|
}
|
|
254
255
|
|
|
@@ -377,9 +378,6 @@ function emitEnum(context: PythonSdkContext, type: SdkEnumType): Record<string,
|
|
|
377
378
|
}
|
|
378
379
|
|
|
379
380
|
function enumName(name: string): string {
|
|
380
|
-
if (name.toUpperCase() === name) {
|
|
381
|
-
return name;
|
|
382
|
-
}
|
|
383
381
|
return camelToSnakeCase(name).toUpperCase();
|
|
384
382
|
}
|
|
385
383
|
|
|
@@ -547,6 +545,22 @@ export const KnownTypes = {
|
|
|
547
545
|
any: { type: "any" },
|
|
548
546
|
};
|
|
549
547
|
|
|
548
|
+
/**
|
|
549
|
+
* Detect whether an endpoint template parameter should be treated as an API
|
|
550
|
+
* version parameter even when TCGC does not flag it as such. This is a
|
|
551
|
+
* compatibility shim for unreleased TCGC changes that gate the name-based
|
|
552
|
+
* heuristic on `isMetadata`, which excludes server template parameters.
|
|
553
|
+
*/
|
|
554
|
+
function isEndpointApiVersionFallback(
|
|
555
|
+
param: { name: string; isApiVersionParam: boolean },
|
|
556
|
+
serviceApiVersions: string[],
|
|
557
|
+
): boolean {
|
|
558
|
+
if (param.isApiVersionParam) return false;
|
|
559
|
+
if (serviceApiVersions.length === 0) return false;
|
|
560
|
+
const lower = param.name.toLowerCase();
|
|
561
|
+
return lower === "apiversion" || lower === "api-version";
|
|
562
|
+
}
|
|
563
|
+
|
|
550
564
|
export function emitEndpointType(
|
|
551
565
|
context: PythonSdkContext,
|
|
552
566
|
type: SdkEndpointType,
|
|
@@ -556,13 +570,29 @@ export function emitEndpointType(
|
|
|
556
570
|
for (const param of type.templateArguments) {
|
|
557
571
|
const paramBase = emitParamBase(context, param, undefined, serviceApiVersions);
|
|
558
572
|
paramBase.clientName = context.arm ? "base_url" : paramBase.clientName;
|
|
573
|
+
|
|
574
|
+
let effectiveClientDefaultValue = param.clientDefaultValue;
|
|
575
|
+
// If this endpoint template param looks like an api-version but TCGC
|
|
576
|
+
// did not flag it, apply fallback: mark as api version and derive defaults.
|
|
577
|
+
if (isEndpointApiVersionFallback(param, serviceApiVersions)) {
|
|
578
|
+
paramBase.isApiVersion = true;
|
|
579
|
+
if (!effectiveClientDefaultValue) {
|
|
580
|
+
effectiveClientDefaultValue = serviceApiVersions[serviceApiVersions.length - 1];
|
|
581
|
+
}
|
|
582
|
+
paramBase.type = getSimpleTypeResult(context, {
|
|
583
|
+
type: "constant",
|
|
584
|
+
value: effectiveClientDefaultValue,
|
|
585
|
+
valueType: paramBase.type,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
|
|
559
589
|
params.push({
|
|
560
590
|
...paramBase,
|
|
561
|
-
optional: Boolean(
|
|
591
|
+
optional: Boolean(effectiveClientDefaultValue),
|
|
562
592
|
wireName: param.name,
|
|
563
593
|
location: "endpointPath",
|
|
564
594
|
implementation: getImplementation(context, param),
|
|
565
|
-
clientDefaultValue:
|
|
595
|
+
clientDefaultValue: effectiveClientDefaultValue,
|
|
566
596
|
skipUrlEncoding: param.allowReserved,
|
|
567
597
|
});
|
|
568
598
|
context.__endpointPathParameters!.push(params.at(-1)!);
|