@autorest/python 6.19.0 → 6.21.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.
Files changed (44) hide show
  1. package/generator/pygen/black.py +2 -3
  2. package/generator/pygen/codegen/models/combined_type.py +1 -1
  3. package/generator/pygen/codegen/models/credential_types.py +7 -14
  4. package/generator/pygen/codegen/models/enum_type.py +1 -1
  5. package/generator/pygen/codegen/models/lro_operation.py +0 -1
  6. package/generator/pygen/codegen/models/lro_paging_operation.py +1 -1
  7. package/generator/pygen/codegen/models/model_type.py +5 -7
  8. package/generator/pygen/codegen/models/operation.py +13 -2
  9. package/generator/pygen/codegen/models/paging_operation.py +0 -1
  10. package/generator/pygen/codegen/models/parameter.py +5 -1
  11. package/generator/pygen/codegen/models/parameter_list.py +2 -5
  12. package/generator/pygen/codegen/models/primitive_types.py +11 -4
  13. package/generator/pygen/codegen/models/property.py +5 -1
  14. package/generator/pygen/codegen/serializers/__init__.py +1 -1
  15. package/generator/pygen/codegen/serializers/builder_serializer.py +22 -20
  16. package/generator/pygen/codegen/serializers/general_serializer.py +2 -1
  17. package/generator/pygen/codegen/serializers/model_serializer.py +3 -0
  18. package/generator/pygen/codegen/serializers/sample_serializer.py +1 -3
  19. package/generator/pygen/codegen/serializers/test_serializer.py +6 -0
  20. package/generator/pygen/codegen/templates/model_base.py.jinja2 +319 -67
  21. package/generator/pygen/codegen/templates/model_dpg.py.jinja2 +5 -0
  22. package/generator/pygen/codegen/templates/serialization.py.jinja2 +271 -162
  23. package/generator/pygen/codegen/templates/test.py.jinja2 +2 -2
  24. package/generator/pygen/m2r.py +1 -1
  25. package/generator/pygen/postprocess/__init__.py +2 -2
  26. package/generator/pygen/postprocess/venvtools.py +1 -3
  27. package/generator/pygen/preprocess/__init__.py +1 -1
  28. package/generator/pygen/utils.py +10 -3
  29. package/generator/setup.py +1 -1
  30. package/package.json +8 -5
  31. package/scripts/__pycache__/venvtools.cpython-310.pyc +0 -0
  32. package/scripts/copy-generator.ts +24 -0
  33. package/scripts/eng/format.ts +5 -0
  34. package/scripts/eng/lint.ts +75 -0
  35. package/scripts/eng/mypy.ini +38 -0
  36. package/scripts/eng/pylintrc +58 -0
  37. package/scripts/eng/pyrightconfig.json +6 -0
  38. package/scripts/eng/regenerate.ts +292 -0
  39. package/scripts/{run-tests.ts → eng/run-tests.ts} +27 -18
  40. package/scripts/eng/utils.ts +38 -0
  41. package/scripts/mypy.ini +38 -0
  42. package/scripts/run-python3.ts +25 -0
  43. package/scripts/system-requirements.ts +253 -0
  44. package/scripts/copy-generator.js +0 -19
@@ -4,28 +4,28 @@ import { readFileSync } from "fs";
4
4
  import { join } from "path";
5
5
  import yargs from "yargs";
6
6
  import { hideBin } from "yargs/helpers";
7
+ import { fileURLToPath } from "url";
7
8
 
8
9
  interface Arguments {
10
+ validFolders: string[];
9
11
  folder?: string;
10
12
  command?: string;
13
+ name?: string;
11
14
  }
12
15
 
13
- const validFolders = [
14
- "azure/legacy",
15
- "azure/version-tolerant",
16
- "vanilla/legacy",
17
- "vanilla/version-tolerant",
18
- "dpg/version-tolerant",
19
- ];
20
-
21
16
  const validCommands = ["ci", "lint", "mypy", "pyright", "apiview"];
22
17
 
23
18
  // Parse command-line arguments using yargs
24
19
  const argv = yargs(hideBin(process.argv))
20
+ .option("validFolders", {
21
+ alias: "vf",
22
+ describe: "Specify the valid folders",
23
+ type: "array",
24
+ default: ["azure", "unbranded"],
25
+ })
25
26
  .option("folder", {
26
27
  alias: "f",
27
28
  describe: "Specify the folder to use",
28
- choices: validFolders,
29
29
  type: "string",
30
30
  })
31
31
  .option("command", {
@@ -33,30 +33,39 @@ const argv = yargs(hideBin(process.argv))
33
33
  describe: "Specify the command to run",
34
34
  choices: validCommands,
35
35
  type: "string",
36
+ })
37
+ .option("name", {
38
+ alias: "n",
39
+ describe: "Specify the name of the test",
40
+ type: "string",
36
41
  }).argv as Arguments;
37
42
 
38
- const foldersToProcess = argv.folder ? [argv.folder] : validFolders;
43
+ const foldersToProcess = argv.folder ? [argv.folder] : argv.validFolders;
39
44
 
40
45
  const commandToRun = argv.command || "all";
41
46
 
42
- function getCommand(command: string, folder: string) {
47
+ function getCommand(command: string, folder: string, name?: string): string {
43
48
  if (!validCommands.includes(command)) throw new Error(`Unknown command '${command}'.`);
44
- return `FOLDER=${folder} tox -c ./test/${folder}/tox.ini -e ${command}`;
49
+ const retval = `FOLDER=${folder} tox -c ./test/${folder}/tox.ini -e ${command}`;
50
+ if (name) {
51
+ return `${retval} -- -f ${name}`;
52
+ }
53
+ return retval;
45
54
  }
46
55
 
47
56
  function sectionExistsInToxIni(command: string, folder: string): boolean {
48
- const toxIniPath = join(__dirname, `../test/${folder}/tox.ini`);
57
+ const toxIniPath = join(fileURLToPath(import.meta.url), `../../../test/${folder}/tox.ini`);
49
58
  const toxIniContent = readFileSync(toxIniPath, "utf-8");
50
59
  const sectionHeader = `[testenv:${command}]`;
51
60
  return toxIniContent.includes(sectionHeader);
52
61
  }
53
62
 
54
- function myExecSync(command: string, folder: string): void {
63
+ function myExecSync(command: string, folder: string, name?: string): void {
55
64
  if (!sectionExistsInToxIni(command, folder)) {
56
65
  console.log(`No section for ${command} in tox.ini for folder ${folder}. Skipping...`);
57
66
  return;
58
67
  }
59
- execSync(getCommand(command, folder), { stdio: "inherit" });
68
+ execSync(getCommand(command, folder, name), { stdio: "inherit" });
60
69
  }
61
70
 
62
71
  foldersToProcess.forEach((folder) => {
@@ -64,11 +73,11 @@ foldersToProcess.forEach((folder) => {
64
73
  if (commandToRun === "all") {
65
74
  for (const key of validCommands) {
66
75
  console.log(`Running ${key} for folder ${folder}...`);
67
- myExecSync(key, folder);
76
+ myExecSync(key, folder, argv.name);
68
77
  }
69
- } else if (getCommand(commandToRun, folder)) {
78
+ } else if (getCommand(commandToRun, folder, argv.name)) {
70
79
  console.log(`Running ${commandToRun} for folder ${folder}...`);
71
- myExecSync(commandToRun, folder);
80
+ myExecSync(commandToRun, folder, argv.name);
72
81
  } else {
73
82
  console.error(`Error: Unknown command '${commandToRun}'.`);
74
83
  process.exit(1);
@@ -0,0 +1,38 @@
1
+ /* eslint-disable no-console */
2
+ import { exec } from "child_process";
3
+ import process from "process";
4
+ import { existsSync } from "fs";
5
+ import { dirname, join } from "path";
6
+ import { fileURLToPath } from "url";
7
+ import chalk from "chalk";
8
+
9
+ // execute the command
10
+ export function executeCommand(command: string, prettyName: string) {
11
+ exec(command, (error, stdout, stderr) => {
12
+ if (error) {
13
+ console.error(chalk.red(`Error executing ${command}(stdout): ${stdout}`));
14
+ console.error(chalk.red(`Error executing ${command}{stderr}: ${stderr}`));
15
+ process.exit(1);
16
+ }
17
+ if (stderr) {
18
+ // Process stderr output
19
+ console.log(chalk.yellow(`${command}:\n${stderr}`));
20
+ return;
21
+ }
22
+ console.log(chalk.green(`${prettyName} passed`));
23
+ });
24
+ }
25
+
26
+ // Function to run a command and log the output
27
+ export function runCommand(command: string, prettyName: string) {
28
+ let pythonPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "venv/");
29
+ if (existsSync(join(pythonPath, "bin"))) {
30
+ pythonPath = join(pythonPath, "bin", "python");
31
+ } else if (existsSync(join(pythonPath, "Scripts"))) {
32
+ pythonPath = join(pythonPath, "Scripts", "python");
33
+ } else {
34
+ throw new Error(pythonPath);
35
+ }
36
+ command = `${pythonPath} -m ${command}`;
37
+ executeCommand(command, prettyName);
38
+ }
@@ -0,0 +1,38 @@
1
+ # global configurations
2
+ [mypy]
3
+ python_version = 3.8
4
+
5
+
6
+ # module level configuratiohns
7
+ [mypy-jsonrpc.*]
8
+ ignore_missing_imports = True
9
+
10
+ [mypy-ptvsd.*]
11
+ ignore_missing_imports = True
12
+
13
+ [mypy-debugpy.*]
14
+ ignore_missing_imports = True
15
+
16
+ [mypy-m2r2.*]
17
+ ignore_missing_imports = True
18
+
19
+ [mypy-autorest.common.utils.*]
20
+ ignore_missing_imports = True
21
+
22
+ [mypy-autorest.common.python_mappings.*]
23
+ ignore_missing_imports = True
24
+
25
+ [mypy-pygen.codegen.models.*]
26
+ ignore_missing_imports = True
27
+
28
+ [mypy-setuptools]
29
+ ignore_missing_imports = True
30
+
31
+ [mypy-*._patch]
32
+ ignore_missing_imports = True
33
+
34
+ [mypy-pygen.*]
35
+ ignore_missing_imports = True
36
+
37
+ [mypy-yaml.*]
38
+ ignore_missing_imports = True
@@ -0,0 +1,25 @@
1
+ // This script wraps logic in @azure-tools/extension to resolve
2
+ // the path to Python 3 so that a Python script file can be run
3
+ // from an npm script in package.json. It uses the same Python 3
4
+ // path resolution algorithm as AutoRest so that the behavior
5
+ // is fully consistent (and also supports AUTOREST_PYTHON_EXE).
6
+ //
7
+ // Invoke it like so: "tsx run-python3.ts script.py"
8
+
9
+ import cp from "child_process";
10
+ import { patchPythonPath } from "./system-requirements.js";
11
+
12
+ async function runPython3(...args: string[]) {
13
+ const command = await patchPythonPath(["python", ...args], {
14
+ version: ">=3.8",
15
+ environmentVariable: "AUTOREST_PYTHON_EXE",
16
+ });
17
+ cp.execSync(command.join(" "), {
18
+ stdio: [0, 1, 2],
19
+ });
20
+ }
21
+
22
+ runPython3(...process.argv.slice(2)).catch((err) => {
23
+ console.error(err.toString()); // eslint-disable-line no-console
24
+ process.exit(1);
25
+ });
@@ -0,0 +1,253 @@
1
+ import { SpawnOptions, ChildProcess, spawn } from "child_process";
2
+ import { coerce, satisfies } from "semver";
3
+
4
+ /*
5
+ * Copied from @autorest/system-requirements
6
+ */
7
+
8
+ const execute = (command: string, cmdlineargs: Array<string>, options: MoreOptions = {}): Promise<ExecResult> => {
9
+ return new Promise((resolve, reject) => {
10
+ const cp = spawn(command, cmdlineargs, { ...options, stdio: "pipe", shell: true });
11
+ if (options.onCreate) {
12
+ options.onCreate(cp);
13
+ }
14
+
15
+ options.onStdOutData ? cp.stdout.on("data", options.onStdOutData) : cp;
16
+ options.onStdErrData ? cp.stderr.on("data", options.onStdErrData) : cp;
17
+
18
+ let err = "";
19
+ let out = "";
20
+ let all = "";
21
+ cp.stderr.on("data", (chunk) => {
22
+ err += chunk;
23
+ all += chunk;
24
+ });
25
+ cp.stdout.on("data", (chunk) => {
26
+ out += chunk;
27
+ all += chunk;
28
+ });
29
+
30
+ cp.on("error", (err) => {
31
+ reject(err);
32
+ });
33
+ cp.on("close", (code, signal) =>
34
+ resolve({
35
+ stdout: out,
36
+ stderr: err,
37
+ log: all,
38
+ error: code ? new Error("Process Failed.") : null,
39
+ code,
40
+ }),
41
+ );
42
+ });
43
+ };
44
+
45
+ const versionIsSatisfied = (version: string, requirement: string): boolean => {
46
+ const cleanedVersion = coerce(version);
47
+ if (!cleanedVersion) {
48
+ throw new Error(`Invalid version ${version}.`);
49
+ }
50
+ return satisfies(cleanedVersion, requirement, true);
51
+ };
52
+
53
+ /**
54
+ * Validate the provided system requirement resolution is satisfying the version requirement if applicable.
55
+ * @param resolution Command resolution.
56
+ * @param actualVersion Version for that resolution.
57
+ * @param requirement Requirement.
58
+ * @returns the resolution if it is valid or an @see SystemRequirementError if not.
59
+ */
60
+ const validateVersionRequirement = (
61
+ resolution: SystemRequirementResolution,
62
+ actualVersion: string,
63
+ requirement: SystemRequirement,
64
+ ): SystemRequirementResolution | SystemRequirementError => {
65
+ if (!requirement.version) {
66
+ return resolution; // No version requirement.
67
+ }
68
+
69
+ try {
70
+ if (versionIsSatisfied(actualVersion, requirement.version)) {
71
+ return resolution;
72
+ }
73
+ return {
74
+ ...resolution,
75
+ error: true,
76
+ message: `'${resolution.command}' version is '${actualVersion}' but doesn't satisfy requirement '${requirement.version}'. Please update.`,
77
+ actualVersion: actualVersion,
78
+ neededVersion: requirement.version,
79
+ };
80
+ } catch {
81
+ return {
82
+ ...resolution,
83
+ error: true,
84
+ message: `Couldn't parse the version ${actualVersion}. This is not a valid semver version.`,
85
+ actualVersion: actualVersion,
86
+ neededVersion: requirement.version,
87
+ };
88
+ }
89
+ };
90
+
91
+ const tryPython = async (
92
+ requirement: SystemRequirement,
93
+ command: string,
94
+ additionalArgs: string[] = [],
95
+ ): Promise<SystemRequirementResolution | SystemRequirementError> => {
96
+ const resolution: SystemRequirementResolution = {
97
+ name: PythonRequirement,
98
+ command,
99
+ additionalArgs: additionalArgs.length > 0 ? additionalArgs : undefined,
100
+ };
101
+
102
+ try {
103
+ const result = await execute(command, [...additionalArgs, "-c", `"${PRINT_PYTHON_VERSION_SCRIPT}"`]);
104
+ return validateVersionRequirement(resolution, result.stdout.trim(), requirement);
105
+ } catch (e) {
106
+ return {
107
+ error: true,
108
+ ...resolution,
109
+ message: `'${command}' command line is not found in the path. Make sure to have it installed.`,
110
+ };
111
+ }
112
+ };
113
+
114
+ /**
115
+ * Returns the path to the executable as asked in the requirement.
116
+ * @param requirement System requirement definition.
117
+ * @returns If the requirement provide an environment variable for the path returns the value of that environment variable. undefined otherwise.
118
+ */
119
+ const getExecutablePath = (requirement: SystemRequirement): string | undefined =>
120
+ requirement.environmentVariable && process.env[requirement.environmentVariable];
121
+
122
+ const createPythonErrorMessage = (
123
+ requirement: SystemRequirement,
124
+ errors: SystemRequirementError[],
125
+ ): SystemRequirementError => {
126
+ const versionReq = requirement.version ?? "*";
127
+ const lines = [
128
+ `Couldn't find a valid python interpreter satisfying the requirement (version: ${versionReq}). Tried:`,
129
+ ...errors.map((x) => ` - ${x.command} (${x.message})`),
130
+ ];
131
+
132
+ return {
133
+ error: true,
134
+ name: "python",
135
+ command: "python",
136
+ message: lines.join("\n"),
137
+ };
138
+ };
139
+
140
+ const resolvePythonRequirement = async (
141
+ requirement: SystemRequirement,
142
+ ): Promise<SystemRequirementResolution | SystemRequirementError> => {
143
+ // Hardcoding AUTOREST_PYTHON_EXE is for backward compatibility
144
+ const path = getExecutablePath(requirement) ?? process.env["AUTOREST_PYTHON_EXE"];
145
+ if (path) {
146
+ return await tryPython(requirement, path);
147
+ }
148
+
149
+ const errors: SystemRequirementError[] = [];
150
+ // On windows try `py` executable with `-3` flag.
151
+ if (process.platform === "win32") {
152
+ const pyResult = await tryPython(requirement, "py", ["-3"]);
153
+ if ("error" in pyResult) {
154
+ errors.push(pyResult);
155
+ } else {
156
+ return pyResult;
157
+ }
158
+ }
159
+
160
+ const python3Result = await tryPython(requirement, "python3");
161
+ if ("error" in python3Result) {
162
+ errors.push(python3Result);
163
+ } else {
164
+ return python3Result;
165
+ }
166
+
167
+ const pythonResult = await tryPython(requirement, "python");
168
+ if ("error" in pythonResult) {
169
+ errors.push(pythonResult);
170
+ } else {
171
+ return pythonResult;
172
+ }
173
+
174
+ return createPythonErrorMessage(requirement, errors);
175
+ };
176
+
177
+ /**
178
+ * @param command list of the command and arguments. First item in array must be a python exe @see KnownPythonExe. (e.g. ["python", "mypythonfile.py"]
179
+ * @param requirement
180
+ */
181
+ export const patchPythonPath = async (
182
+ command: PythonCommandLine,
183
+ requirement: SystemRequirement,
184
+ ): Promise<string[]> => {
185
+ const [_, ...args] = command;
186
+ const resolution = await resolvePythonRequirement(requirement);
187
+ if ("error" in resolution) {
188
+ throw new Error(`Failed to find compatible python version. ${resolution.message}`);
189
+ }
190
+ return [resolution.command, ...(resolution.additionalArgs ?? []), ...args];
191
+ };
192
+
193
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
194
+ // TYPES
195
+ const PythonRequirement = "python";
196
+ const PRINT_PYTHON_VERSION_SCRIPT = "import sys; print('.'.join(map(str, sys.version_info[:3])))";
197
+
198
+ type KnownPythonExe = "python.exe" | "python3.exe" | "python" | "python3";
199
+ type PythonCommandLine = [KnownPythonExe, ...string[]];
200
+
201
+ interface MoreOptions extends SpawnOptions {
202
+ onCreate?(cp: ChildProcess): void;
203
+ onStdOutData?(chunk: any): void;
204
+ onStdErrData?(chunk: any): void;
205
+ }
206
+
207
+ interface SystemRequirement {
208
+ version?: string;
209
+ /**
210
+ * Name of an environment variable where the user could provide the path to the exe.
211
+ * @example "AUTOREST_PYTHON_PATH"
212
+ */
213
+ environmentVariable?: string;
214
+ }
215
+
216
+ interface SystemRequirementResolution {
217
+ /**
218
+ * Name of the requirement.
219
+ * @example python, java, etc.
220
+ */
221
+ name: string;
222
+
223
+ /**
224
+ * Name of the command
225
+ * @example python3, /home/myuser/python39/python, java, etc.
226
+ */
227
+ command: string;
228
+
229
+ /**
230
+ * List of additional arguments to pass to this command.
231
+ * @example '-3' for 'py' to specify to use python 3
232
+ */
233
+ additionalArgs?: string[];
234
+ }
235
+
236
+ interface ExecResult {
237
+ stdout: string;
238
+ stderr: string;
239
+
240
+ /**
241
+ * Union of stdout and stderr.
242
+ */
243
+ log: string;
244
+ error: Error | null;
245
+ code: number | null;
246
+ }
247
+
248
+ interface SystemRequirementError extends SystemRequirementResolution {
249
+ error: true;
250
+ message: string;
251
+ neededVersion?: string;
252
+ actualVersion?: string;
253
+ }
@@ -1,19 +0,0 @@
1
- const fs = require("fs-extra");
2
- const path = require("path");
3
-
4
- const force = process.argv[2] === "--force" ? true : false;
5
-
6
- const typespecModulePath = path.join(__dirname, "..", "node_modules", "@azure-tools", "typespec-python");
7
-
8
- // Define the source and destination directories
9
- const sourceDir = path.join(typespecModulePath, "generator");
10
- const destDir = path.join(__dirname, "..", "generator");
11
-
12
- // Delete the destination directory if it exists
13
- if (fs.existsSync(destDir)) {
14
- if (force) fs.removeSync(destDir);
15
- else process.exit(0);
16
- }
17
-
18
- // Copy the source directory to the destination directory
19
- fs.copySync(sourceDir, destDir);