@typespec/http-client-python 0.16.0 → 0.17.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/emitter.d.ts.map +1 -1
- package/dist/emitter/emitter.js +6 -4
- package/dist/emitter/emitter.js.map +1 -1
- package/emitter/src/emitter.ts +6 -4
- package/emitter/temp/tsconfig.tsbuildinfo +1 -1
- package/eng/scripts/ci/regenerate.ts +4 -5
- package/eng/scripts/setup/__pycache__/package_manager.cpython-39.pyc +0 -0
- package/eng/scripts/setup/__pycache__/venvtools.cpython-39.pyc +0 -0
- package/eng/scripts/setup/install.py +21 -11
- package/eng/scripts/setup/install.ts +29 -4
- package/eng/scripts/setup/run-python3.ts +1 -1
- package/eng/scripts/setup/system-requirements.ts +13 -5
- package/generator/build/lib/pygen/black.py +2 -2
- package/generator/build/lib/pygen/codegen/models/code_model.py +4 -0
- package/generator/build/lib/pygen/codegen/models/list_type.py +3 -7
- package/generator/build/lib/pygen/codegen/models/operation.py +0 -4
- package/generator/build/lib/pygen/codegen/serializers/model_serializer.py +21 -12
- package/generator/build/lib/pygen/codegen/serializers/operation_groups_serializer.py +5 -0
- package/generator/build/lib/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2 +1 -1
- package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
- package/generator/pygen/black.py +2 -2
- package/generator/pygen/codegen/models/code_model.py +4 -0
- package/generator/pygen/codegen/models/list_type.py +3 -7
- package/generator/pygen/codegen/models/operation.py +0 -4
- package/generator/pygen/codegen/serializers/model_serializer.py +21 -12
- package/generator/pygen/codegen/serializers/operation_groups_serializer.py +5 -0
- package/generator/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2 +1 -1
- package/generator/test/azure/mock_api_tests/asynctests/test_azure_client_generator_core_hierrarchy_building_async.py +53 -0
- package/generator/test/azure/mock_api_tests/test_azure_client_generator_core_hierrarchy_building.py +45 -0
- package/generator/test/azure/requirements.txt +1 -0
- package/generator/test/unittests/test_model_base_serialization.py +47 -0
- package/package.json +33 -33
|
@@ -22,11 +22,7 @@ const argv = parseArgs({
|
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
// Add this near the top with other constants
|
|
25
|
-
const SKIP_SPECS = [
|
|
26
|
-
"type/union/discriminated",
|
|
27
|
-
"client-operation-group",
|
|
28
|
-
"azure/client-generator-core/hierarchy-building",
|
|
29
|
-
];
|
|
25
|
+
const SKIP_SPECS = ["type/union/discriminated", "client-operation-group"];
|
|
30
26
|
|
|
31
27
|
// Get the directory of the current file
|
|
32
28
|
const PLUGIN_DIR = argv.values.pluginDir
|
|
@@ -68,6 +64,9 @@ const AZURE_EMITTER_OPTIONS: Record<string, Record<string, string> | Record<stri
|
|
|
68
64
|
"azure/client-generator-core/override": {
|
|
69
65
|
namespace: "specs.azure.clientgenerator.core.override",
|
|
70
66
|
},
|
|
67
|
+
"azure/client-generator-core/hierarchy-building": {
|
|
68
|
+
namespace: "specs.azure.clientgenerator.core.hierarchybuilding",
|
|
69
|
+
},
|
|
71
70
|
"azure/core/basic": {
|
|
72
71
|
namespace: "specs.azure.core.basic",
|
|
73
72
|
},
|
|
Binary file
|
|
Binary file
|
|
@@ -8,28 +8,33 @@
|
|
|
8
8
|
import sys
|
|
9
9
|
|
|
10
10
|
if not sys.version_info >= (3, 9, 0):
|
|
11
|
-
|
|
12
|
-
"Autorest for Python extension requires Python 3.9 at least. We will run your code with Pyodide since your Python version isn't adequate."
|
|
11
|
+
print(
|
|
12
|
+
"Warning: Autorest for Python extension requires Python 3.9 at least. We will run your code with Pyodide since your Python version isn't adequate."
|
|
13
13
|
)
|
|
14
|
+
sys.exit(2) # Exit code 2 for inadequate environment
|
|
14
15
|
|
|
15
16
|
try:
|
|
16
17
|
from package_manager import detect_package_manager, PackageManagerNotFoundError
|
|
17
18
|
|
|
18
19
|
detect_package_manager() # Just check if we have a package manager
|
|
19
20
|
except (ImportError, ModuleNotFoundError, PackageManagerNotFoundError):
|
|
20
|
-
|
|
21
|
-
"Your Python installation doesn't have a suitable package manager (pip or uv) available. We will run your code with Pyodide since your Python environment isn't adequate."
|
|
21
|
+
print(
|
|
22
|
+
"Warning: Your Python installation doesn't have a suitable package manager (pip or uv) available. We will run your code with Pyodide since your Python environment isn't adequate."
|
|
22
23
|
)
|
|
24
|
+
sys.exit(2) # Exit code 2 for inadequate environment
|
|
23
25
|
|
|
24
26
|
try:
|
|
25
27
|
import venv
|
|
26
28
|
except (ImportError, ModuleNotFoundError):
|
|
27
|
-
|
|
28
|
-
"Your Python installation doesn't have venv available. We will run your code with Pyodide since your Python version isn't adequate."
|
|
29
|
+
print(
|
|
30
|
+
"Warning: Your Python installation doesn't have venv available. We will run your code with Pyodide since your Python version isn't adequate."
|
|
29
31
|
)
|
|
32
|
+
sys.exit(2) # Exit code 2 for inadequate environment
|
|
30
33
|
|
|
31
34
|
|
|
32
|
-
# Now we have a package manager (uv or pip) and Py >= 3.
|
|
35
|
+
# Now we have a package manager (uv or pip) and Py >= 3.9, go to work
|
|
36
|
+
# At this point, both Python and package manager are confirmed to be available
|
|
37
|
+
# Any failures from here should fail the npm install, not fallback to Pyodide
|
|
33
38
|
|
|
34
39
|
from pathlib import Path
|
|
35
40
|
|
|
@@ -42,11 +47,16 @@ def main():
|
|
|
42
47
|
# Create virtual environment using package manager abstraction
|
|
43
48
|
from package_manager import create_venv_with_package_manager, install_packages
|
|
44
49
|
|
|
45
|
-
|
|
50
|
+
try:
|
|
51
|
+
venv_context = create_venv_with_package_manager(venv_path)
|
|
46
52
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
# Install required packages - install_packages handles package manager logic
|
|
54
|
+
install_packages(["-U", "black"], venv_context)
|
|
55
|
+
install_packages([f"{_ROOT_DIR}/generator"], venv_context)
|
|
56
|
+
except Exception as e:
|
|
57
|
+
# Since Python and package manager are available, any failure here should fail the npm install
|
|
58
|
+
print(f"Error: Installation failed despite Python and package manager being available: {e}")
|
|
59
|
+
sys.exit(1) # Exit code 1 for installation failure
|
|
50
60
|
|
|
51
61
|
|
|
52
62
|
if __name__ == "__main__":
|
|
@@ -1,11 +1,36 @@
|
|
|
1
|
-
import
|
|
1
|
+
import cp from "child_process";
|
|
2
|
+
import { patchPythonPath } from "./system-requirements.js";
|
|
2
3
|
|
|
3
4
|
async function main() {
|
|
5
|
+
let pythonCommand: string[];
|
|
6
|
+
|
|
4
7
|
try {
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
// First try to resolve Python path
|
|
9
|
+
pythonCommand = await patchPythonPath(["python", "./eng/scripts/setup/install.py"], {
|
|
10
|
+
version: ">=3.9",
|
|
11
|
+
environmentVariable: "AUTOREST_PYTHON_EXE",
|
|
12
|
+
});
|
|
7
13
|
} catch (error) {
|
|
8
|
-
console.log(
|
|
14
|
+
console.log(`Ran into the following error when setting up your local environment: ${error}`); // eslint-disable-line no-console
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// Python found, now try to run the installation script
|
|
20
|
+
cp.execSync(pythonCommand.join(" "), {
|
|
21
|
+
stdio: [0, 1, 2],
|
|
22
|
+
});
|
|
23
|
+
console.log("Found Python on your local environment and created a venv with all requirements."); // eslint-disable-line no-console
|
|
24
|
+
} catch (error: any) {
|
|
25
|
+
// Check the exit code to determine the type of error
|
|
26
|
+
if (error.status === 2) {
|
|
27
|
+
// Exit code 2: Python/pip not adequate - use Pyodide
|
|
28
|
+
console.log("No Python found on your local environment. We will use Pyodide instead."); // eslint-disable-line no-console
|
|
29
|
+
} else {
|
|
30
|
+
// Exit code 1 or other: Python and pip were found but installation failed - fail the npm install
|
|
31
|
+
console.error("Python and package manager found but installation failed."); // eslint-disable-line no-console
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
9
34
|
}
|
|
10
35
|
}
|
|
11
36
|
|
|
@@ -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.9",
|
|
15
15
|
environmentVariable: "AUTOREST_PYTHON_EXE",
|
|
16
16
|
});
|
|
17
17
|
cp.execSync(command.join(" "), {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ChildProcess, spawn, SpawnOptions } from "child_process";
|
|
2
|
-
import { coerce, satisfies } from "semver";
|
|
3
2
|
|
|
4
3
|
/*
|
|
5
4
|
* Copied from @autorest/system-requirements
|
|
@@ -46,12 +45,21 @@ const execute = (
|
|
|
46
45
|
});
|
|
47
46
|
};
|
|
48
47
|
|
|
48
|
+
// Simple version comparison without semver dependency
|
|
49
49
|
const versionIsSatisfied = (version: string, requirement: string): boolean => {
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
50
|
+
// For now, support only >= requirements since that's what we use
|
|
51
|
+
if (requirement.startsWith(">=")) {
|
|
52
|
+
const requiredVersion = requirement.substring(2);
|
|
53
|
+
return parseVersionNumber(version) >= parseVersionNumber(requiredVersion);
|
|
53
54
|
}
|
|
54
|
-
|
|
55
|
+
// Fallback to true for other version requirements
|
|
56
|
+
return true;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const parseVersionNumber = (version: string): number => {
|
|
60
|
+
// Parse version like "3.9.0" to a comparable number
|
|
61
|
+
const parts = version.split(".").map((p) => parseInt(p.replace(/[^0-9]/g, ""), 10) || 0);
|
|
62
|
+
return parts[0] * 10000 + (parts[1] || 0) * 100 + (parts[2] || 0);
|
|
55
63
|
};
|
|
56
64
|
|
|
57
65
|
/**
|
|
@@ -69,9 +69,9 @@ class BlackScriptPlugin(Plugin):
|
|
|
69
69
|
pylint_disables.append("too-many-lines")
|
|
70
70
|
if pylint_disables:
|
|
71
71
|
file_content = (
|
|
72
|
-
|
|
72
|
+
os.linesep.join([lines[0] + ",".join([""] + pylint_disables)] + lines[1:])
|
|
73
73
|
if "pylint: disable=" in lines[0]
|
|
74
|
-
else f"# pylint: disable={','.join(pylint_disables)}
|
|
74
|
+
else f"# pylint: disable={','.join(pylint_disables)}{os.linesep}" + file_content
|
|
75
75
|
)
|
|
76
76
|
self.write_file(file, file_content)
|
|
77
77
|
|
|
@@ -484,3 +484,7 @@ class CodeModel: # pylint: disable=too-many-public-methods, disable=too-many-in
|
|
|
484
484
|
|
|
485
485
|
# No generation-subdir specified, use the namespace path directly
|
|
486
486
|
return Path(*namespace.split("."))
|
|
487
|
+
|
|
488
|
+
@property
|
|
489
|
+
def has_operation_named_list(self) -> bool:
|
|
490
|
+
return any(o.name.lower() == "list" for c in self.clients for og in c.operation_groups for o in og.operations)
|
|
@@ -40,13 +40,9 @@ class ListType(BaseType):
|
|
|
40
40
|
):
|
|
41
41
|
# this means we're version tolerant XML, we just return the XML element
|
|
42
42
|
return self.element_type.type_annotation(**kwargs)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
for og in c.operation_groups
|
|
47
|
-
for o in og.operations
|
|
48
|
-
)
|
|
49
|
-
list_type = "List" if has_operation_named_list and kwargs.get("is_operation_file") else "list"
|
|
43
|
+
|
|
44
|
+
# if there is a function named `list` we have to make sure there's no conflict with the built-in `list`
|
|
45
|
+
list_type = "List" if self.code_model.has_operation_named_list and kwargs.get("is_operation_file") else "list"
|
|
50
46
|
return f"{list_type}[{self.element_type.type_annotation(**kwargs)}]"
|
|
51
47
|
|
|
52
48
|
def description(self, *, is_operation_file: bool) -> str:
|
|
@@ -404,10 +404,6 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
|
|
|
404
404
|
file_import.merge(self.get_request_builder_import(self.request_builder, async_mode, serialize_namespace))
|
|
405
405
|
if self.overloads:
|
|
406
406
|
file_import.add_submodule_import("typing", "overload", ImportType.STDLIB)
|
|
407
|
-
if self.name == "list":
|
|
408
|
-
# if there is a function named `list` we have to make sure there's no conflict with the built-in `list`
|
|
409
|
-
# not doing for dict or set yet, though we might have to later
|
|
410
|
-
file_import.define_mypy_type("List", "list")
|
|
411
407
|
if self.code_model.options["models-mode"] == "dpg":
|
|
412
408
|
relative_path = self.code_model.get_relative_import_path(
|
|
413
409
|
serialize_namespace, module_name="_utils.model_base"
|
|
@@ -158,6 +158,16 @@ class MsrestModelSerializer(_ModelSerializer):
|
|
|
158
158
|
called_by_property=True,
|
|
159
159
|
)
|
|
160
160
|
)
|
|
161
|
+
for prop in model.properties:
|
|
162
|
+
if prop.readonly:
|
|
163
|
+
# it will be defined in the __init__ so we need to import it
|
|
164
|
+
file_import.merge(
|
|
165
|
+
prop.imports(
|
|
166
|
+
serialize_namespace=self.serialize_namespace,
|
|
167
|
+
serialize_namespace_type=NamespaceType.MODEL,
|
|
168
|
+
called_by_property=True,
|
|
169
|
+
)
|
|
170
|
+
)
|
|
161
171
|
|
|
162
172
|
return file_import
|
|
163
173
|
|
|
@@ -224,7 +234,16 @@ class DpgModelSerializer(_ModelSerializer):
|
|
|
224
234
|
"for k, v in _flattened_input.items():",
|
|
225
235
|
" setattr(self, k, v)",
|
|
226
236
|
]
|
|
227
|
-
|
|
237
|
+
discriminator_value_setter = []
|
|
238
|
+
for prop in self.get_properties_to_declare(model):
|
|
239
|
+
if (
|
|
240
|
+
prop.is_discriminator
|
|
241
|
+
and isinstance(prop.type, (ConstantType, EnumValue))
|
|
242
|
+
and prop.type.value is not None
|
|
243
|
+
):
|
|
244
|
+
discriminator_value_setter.append(f"self.{prop.client_name}={prop.get_declaration()} # type: ignore")
|
|
245
|
+
|
|
246
|
+
return [super_call, *discriminator_value_setter]
|
|
228
247
|
|
|
229
248
|
def imports(self) -> FileImport:
|
|
230
249
|
file_import = FileImport(self.code_model)
|
|
@@ -343,17 +362,7 @@ class DpgModelSerializer(_ModelSerializer):
|
|
|
343
362
|
|
|
344
363
|
@staticmethod
|
|
345
364
|
def properties_to_pass_to_super(model: ModelType) -> str:
|
|
346
|
-
properties_to_pass_to_super = ["*args"]
|
|
347
|
-
for parent in model.parents:
|
|
348
|
-
for prop in model.properties:
|
|
349
|
-
if (
|
|
350
|
-
prop.client_name in [prop.client_name for prop in parent.properties if prop.is_base_discriminator]
|
|
351
|
-
and prop.is_discriminator
|
|
352
|
-
and not prop.constant
|
|
353
|
-
and not prop.readonly
|
|
354
|
-
):
|
|
355
|
-
properties_to_pass_to_super.append(f"{prop.client_name}={prop.get_declaration()}")
|
|
356
|
-
properties_to_pass_to_super.append("**kwargs")
|
|
365
|
+
properties_to_pass_to_super = ["*args", "**kwargs"]
|
|
357
366
|
return ", ".join(properties_to_pass_to_super)
|
|
358
367
|
|
|
359
368
|
def global_pylint_disables(self) -> str:
|
|
@@ -66,6 +66,11 @@ class OperationGroupsSerializer(BaseSerializer):
|
|
|
66
66
|
serialize_namespace_type=NamespaceType.OPERATION,
|
|
67
67
|
)
|
|
68
68
|
)
|
|
69
|
+
# put here since one operation file only need one self-defined list type
|
|
70
|
+
if self.code_model.has_operation_named_list:
|
|
71
|
+
# if there is a function named `list` we have to make sure there's no conflict with the built-in `list`
|
|
72
|
+
# not doing for dict or set yet, though we might have to later
|
|
73
|
+
imports.define_mypy_type("List", "list")
|
|
69
74
|
|
|
70
75
|
template = self.env.get_or_select_template("operation_groups_container.py.jinja2")
|
|
71
76
|
|
package/generator/build/lib/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2
CHANGED
|
@@ -43,7 +43,7 @@ dependencies = [
|
|
|
43
43
|
{% else %}
|
|
44
44
|
"isodate>={{ VERSION_MAP['isodate'] }}",
|
|
45
45
|
{% endif %}
|
|
46
|
-
{% if options.get('
|
|
46
|
+
{% if options.get('azure-arm') %}
|
|
47
47
|
"azure-mgmt-core>={{ VERSION_MAP['azure-mgmt-core'] }}",
|
|
48
48
|
{% elif code_model.is_azure_flavor %}
|
|
49
49
|
"azure-core>={{ VERSION_MAP['azure-core'] }}",
|
|
Binary file
|
package/generator/pygen/black.py
CHANGED
|
@@ -69,9 +69,9 @@ class BlackScriptPlugin(Plugin):
|
|
|
69
69
|
pylint_disables.append("too-many-lines")
|
|
70
70
|
if pylint_disables:
|
|
71
71
|
file_content = (
|
|
72
|
-
|
|
72
|
+
os.linesep.join([lines[0] + ",".join([""] + pylint_disables)] + lines[1:])
|
|
73
73
|
if "pylint: disable=" in lines[0]
|
|
74
|
-
else f"# pylint: disable={','.join(pylint_disables)}
|
|
74
|
+
else f"# pylint: disable={','.join(pylint_disables)}{os.linesep}" + file_content
|
|
75
75
|
)
|
|
76
76
|
self.write_file(file, file_content)
|
|
77
77
|
|
|
@@ -484,3 +484,7 @@ class CodeModel: # pylint: disable=too-many-public-methods, disable=too-many-in
|
|
|
484
484
|
|
|
485
485
|
# No generation-subdir specified, use the namespace path directly
|
|
486
486
|
return Path(*namespace.split("."))
|
|
487
|
+
|
|
488
|
+
@property
|
|
489
|
+
def has_operation_named_list(self) -> bool:
|
|
490
|
+
return any(o.name.lower() == "list" for c in self.clients for og in c.operation_groups for o in og.operations)
|
|
@@ -40,13 +40,9 @@ class ListType(BaseType):
|
|
|
40
40
|
):
|
|
41
41
|
# this means we're version tolerant XML, we just return the XML element
|
|
42
42
|
return self.element_type.type_annotation(**kwargs)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
for og in c.operation_groups
|
|
47
|
-
for o in og.operations
|
|
48
|
-
)
|
|
49
|
-
list_type = "List" if has_operation_named_list and kwargs.get("is_operation_file") else "list"
|
|
43
|
+
|
|
44
|
+
# if there is a function named `list` we have to make sure there's no conflict with the built-in `list`
|
|
45
|
+
list_type = "List" if self.code_model.has_operation_named_list and kwargs.get("is_operation_file") else "list"
|
|
50
46
|
return f"{list_type}[{self.element_type.type_annotation(**kwargs)}]"
|
|
51
47
|
|
|
52
48
|
def description(self, *, is_operation_file: bool) -> str:
|
|
@@ -404,10 +404,6 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
|
|
|
404
404
|
file_import.merge(self.get_request_builder_import(self.request_builder, async_mode, serialize_namespace))
|
|
405
405
|
if self.overloads:
|
|
406
406
|
file_import.add_submodule_import("typing", "overload", ImportType.STDLIB)
|
|
407
|
-
if self.name == "list":
|
|
408
|
-
# if there is a function named `list` we have to make sure there's no conflict with the built-in `list`
|
|
409
|
-
# not doing for dict or set yet, though we might have to later
|
|
410
|
-
file_import.define_mypy_type("List", "list")
|
|
411
407
|
if self.code_model.options["models-mode"] == "dpg":
|
|
412
408
|
relative_path = self.code_model.get_relative_import_path(
|
|
413
409
|
serialize_namespace, module_name="_utils.model_base"
|
|
@@ -158,6 +158,16 @@ class MsrestModelSerializer(_ModelSerializer):
|
|
|
158
158
|
called_by_property=True,
|
|
159
159
|
)
|
|
160
160
|
)
|
|
161
|
+
for prop in model.properties:
|
|
162
|
+
if prop.readonly:
|
|
163
|
+
# it will be defined in the __init__ so we need to import it
|
|
164
|
+
file_import.merge(
|
|
165
|
+
prop.imports(
|
|
166
|
+
serialize_namespace=self.serialize_namespace,
|
|
167
|
+
serialize_namespace_type=NamespaceType.MODEL,
|
|
168
|
+
called_by_property=True,
|
|
169
|
+
)
|
|
170
|
+
)
|
|
161
171
|
|
|
162
172
|
return file_import
|
|
163
173
|
|
|
@@ -224,7 +234,16 @@ class DpgModelSerializer(_ModelSerializer):
|
|
|
224
234
|
"for k, v in _flattened_input.items():",
|
|
225
235
|
" setattr(self, k, v)",
|
|
226
236
|
]
|
|
227
|
-
|
|
237
|
+
discriminator_value_setter = []
|
|
238
|
+
for prop in self.get_properties_to_declare(model):
|
|
239
|
+
if (
|
|
240
|
+
prop.is_discriminator
|
|
241
|
+
and isinstance(prop.type, (ConstantType, EnumValue))
|
|
242
|
+
and prop.type.value is not None
|
|
243
|
+
):
|
|
244
|
+
discriminator_value_setter.append(f"self.{prop.client_name}={prop.get_declaration()} # type: ignore")
|
|
245
|
+
|
|
246
|
+
return [super_call, *discriminator_value_setter]
|
|
228
247
|
|
|
229
248
|
def imports(self) -> FileImport:
|
|
230
249
|
file_import = FileImport(self.code_model)
|
|
@@ -343,17 +362,7 @@ class DpgModelSerializer(_ModelSerializer):
|
|
|
343
362
|
|
|
344
363
|
@staticmethod
|
|
345
364
|
def properties_to_pass_to_super(model: ModelType) -> str:
|
|
346
|
-
properties_to_pass_to_super = ["*args"]
|
|
347
|
-
for parent in model.parents:
|
|
348
|
-
for prop in model.properties:
|
|
349
|
-
if (
|
|
350
|
-
prop.client_name in [prop.client_name for prop in parent.properties if prop.is_base_discriminator]
|
|
351
|
-
and prop.is_discriminator
|
|
352
|
-
and not prop.constant
|
|
353
|
-
and not prop.readonly
|
|
354
|
-
):
|
|
355
|
-
properties_to_pass_to_super.append(f"{prop.client_name}={prop.get_declaration()}")
|
|
356
|
-
properties_to_pass_to_super.append("**kwargs")
|
|
365
|
+
properties_to_pass_to_super = ["*args", "**kwargs"]
|
|
357
366
|
return ", ".join(properties_to_pass_to_super)
|
|
358
367
|
|
|
359
368
|
def global_pylint_disables(self) -> str:
|
|
@@ -66,6 +66,11 @@ class OperationGroupsSerializer(BaseSerializer):
|
|
|
66
66
|
serialize_namespace_type=NamespaceType.OPERATION,
|
|
67
67
|
)
|
|
68
68
|
)
|
|
69
|
+
# put here since one operation file only need one self-defined list type
|
|
70
|
+
if self.code_model.has_operation_named_list:
|
|
71
|
+
# if there is a function named `list` we have to make sure there's no conflict with the built-in `list`
|
|
72
|
+
# not doing for dict or set yet, though we might have to later
|
|
73
|
+
imports.define_mypy_type("List", "list")
|
|
69
74
|
|
|
70
75
|
template = self.env.get_or_select_template("operation_groups_container.py.jinja2")
|
|
71
76
|
|
|
@@ -43,7 +43,7 @@ dependencies = [
|
|
|
43
43
|
{% else %}
|
|
44
44
|
"isodate>={{ VERSION_MAP['isodate'] }}",
|
|
45
45
|
{% endif %}
|
|
46
|
-
{% if options.get('
|
|
46
|
+
{% if options.get('azure-arm') %}
|
|
47
47
|
"azure-mgmt-core>={{ VERSION_MAP['azure-mgmt-core'] }}",
|
|
48
48
|
{% elif code_model.is_azure_flavor %}
|
|
49
49
|
"azure-core>={{ VERSION_MAP['azure-core'] }}",
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# -------------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
# Licensed under the MIT License. See License.txt in the project root for
|
|
4
|
+
# license information.
|
|
5
|
+
# --------------------------------------------------------------------------
|
|
6
|
+
import pytest
|
|
7
|
+
from specs.azure.clientgenerator.core.hierarchybuilding.aio import HierarchyBuildingClient
|
|
8
|
+
from specs.azure.clientgenerator.core.hierarchybuilding.models import (
|
|
9
|
+
Pet,
|
|
10
|
+
Dog,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
async def client():
|
|
16
|
+
async with HierarchyBuildingClient() as client:
|
|
17
|
+
yield client
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ========== test for spector ==========
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.mark.asyncio
|
|
24
|
+
async def test_update_pet_as_pet(client: HierarchyBuildingClient):
|
|
25
|
+
resp = Pet(name="Buddy", trained=True)
|
|
26
|
+
assert await client.pet_operations.update_pet_as_pet(Pet(name="Buddy", trained=True)) == resp
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.mark.asyncio
|
|
30
|
+
async def test_update_dog_as_pet(client: HierarchyBuildingClient):
|
|
31
|
+
resp = Dog(name="Rex", trained=True, breed="German Shepherd")
|
|
32
|
+
assert await client.pet_operations.update_dog_as_pet(Dog(name="Rex", trained=True, breed="German Shepherd")) == resp
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pytest.mark.asyncio
|
|
36
|
+
async def test_update_pet_as_animal(client: HierarchyBuildingClient):
|
|
37
|
+
resp = Pet(name="Buddy", trained=True)
|
|
38
|
+
assert await client.animal_operations.update_pet_as_animal(Pet(name="Buddy", trained=True)) == resp
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.mark.asyncio
|
|
42
|
+
async def test_update_dog_as_animal(client: HierarchyBuildingClient):
|
|
43
|
+
resp = Dog(name="Rex", trained=True, breed="German Shepherd")
|
|
44
|
+
assert (
|
|
45
|
+
await client.animal_operations.update_dog_as_animal(Dog(name="Rex", trained=True, breed="German Shepherd"))
|
|
46
|
+
== resp
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.mark.asyncio
|
|
51
|
+
async def test_update_dog_as_dog(client: HierarchyBuildingClient):
|
|
52
|
+
resp = Dog(name="Rex", trained=True, breed="German Shepherd")
|
|
53
|
+
assert await client.dog_operations.update_dog_as_dog(Dog(name="Rex", trained=True, breed="German Shepherd")) == resp
|
package/generator/test/azure/mock_api_tests/test_azure_client_generator_core_hierrarchy_building.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# -------------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
# Licensed under the MIT License. See License.txt in the project root for
|
|
4
|
+
# license information.
|
|
5
|
+
# --------------------------------------------------------------------------
|
|
6
|
+
import pytest
|
|
7
|
+
from specs.azure.clientgenerator.core.hierarchybuilding import HierarchyBuildingClient
|
|
8
|
+
from specs.azure.clientgenerator.core.hierarchybuilding.models import (
|
|
9
|
+
Pet,
|
|
10
|
+
Dog,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def client():
|
|
16
|
+
with HierarchyBuildingClient() as client:
|
|
17
|
+
yield client
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ========== test for spector ==========
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_update_pet_as_pet(client: HierarchyBuildingClient):
|
|
24
|
+
resp = Pet(name="Buddy", trained=True)
|
|
25
|
+
assert client.pet_operations.update_pet_as_pet(Pet(name="Buddy", trained=True)) == resp
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_update_dog_as_pet(client: HierarchyBuildingClient):
|
|
29
|
+
resp = Dog(name="Rex", trained=True, breed="German Shepherd")
|
|
30
|
+
assert client.pet_operations.update_dog_as_pet(Dog(name="Rex", trained=True, breed="German Shepherd")) == resp
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_update_pet_as_animal(client: HierarchyBuildingClient):
|
|
34
|
+
resp = Pet(name="Buddy", trained=True)
|
|
35
|
+
assert client.animal_operations.update_pet_as_animal(Pet(name="Buddy", trained=True)) == resp
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_update_dog_as_animal(client: HierarchyBuildingClient):
|
|
39
|
+
resp = Dog(name="Rex", trained=True, breed="German Shepherd")
|
|
40
|
+
assert client.animal_operations.update_dog_as_animal(Dog(name="Rex", trained=True, breed="German Shepherd")) == resp
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_update_dog_as_dog(client: HierarchyBuildingClient):
|
|
44
|
+
resp = Dog(name="Rex", trained=True, breed="German Shepherd")
|
|
45
|
+
assert client.dog_operations.update_dog_as_dog(Dog(name="Rex", trained=True, breed="German Shepherd")) == resp
|
|
@@ -10,6 +10,7 @@ azure-mgmt-core==1.6.0
|
|
|
10
10
|
-e ./generated/azure-client-generator-core-client-initialization
|
|
11
11
|
-e ./generated/azure-client-generator-core-deserialize-empty-string-as-null
|
|
12
12
|
-e ./generated/azure-client-generator-core-flatten-property
|
|
13
|
+
-e ./generated/azure-client-generator-core-hierarchy-building
|
|
13
14
|
-e ./generated/azure-client-generator-core-usage
|
|
14
15
|
-e ./generated/azure-client-generator-core-override
|
|
15
16
|
-e ./generated/azure-client-generator-core-client-location
|
|
@@ -4061,3 +4061,50 @@ def test_additional_properties_serialization():
|
|
|
4061
4061
|
model["durationProp"] = datetime.timedelta(days=1)
|
|
4062
4062
|
|
|
4063
4063
|
assert json.loads(json.dumps(model, cls=SdkJSONEncoder)) == value
|
|
4064
|
+
|
|
4065
|
+
|
|
4066
|
+
class Animal(Model):
|
|
4067
|
+
__mapping__: dict[str, Model] = {}
|
|
4068
|
+
kind: str = rest_discriminator(name="kind")
|
|
4069
|
+
name: str = rest_field()
|
|
4070
|
+
|
|
4071
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
4072
|
+
super().__init__(*args, **kwargs)
|
|
4073
|
+
|
|
4074
|
+
|
|
4075
|
+
class AnotherPet(Animal, discriminator="pet"):
|
|
4076
|
+
__mapping__: dict[str, Model] = {}
|
|
4077
|
+
kind: Literal["pet"] = rest_discriminator(name="kind")
|
|
4078
|
+
trained: bool = rest_field()
|
|
4079
|
+
|
|
4080
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
4081
|
+
super().__init__(*args, **kwargs)
|
|
4082
|
+
self.kind = "pet"
|
|
4083
|
+
|
|
4084
|
+
|
|
4085
|
+
class AnotherDog(AnotherPet, discriminator="dog"):
|
|
4086
|
+
kind: Literal["dog"] = rest_discriminator(name="kind")
|
|
4087
|
+
breed: str = rest_field()
|
|
4088
|
+
|
|
4089
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
4090
|
+
super().__init__(*args, **kwargs)
|
|
4091
|
+
self.kind = "dog"
|
|
4092
|
+
|
|
4093
|
+
|
|
4094
|
+
def test_multi_layer_discriminator():
|
|
4095
|
+
pet = {"kind": "pet", "name": "Buddy", "trained": True}
|
|
4096
|
+
|
|
4097
|
+
dog = {"kind": "dog", "name": "Rex", "trained": True, "breed": "German Shepherd"}
|
|
4098
|
+
|
|
4099
|
+
model_pet = AnotherPet(pet)
|
|
4100
|
+
assert model_pet == pet
|
|
4101
|
+
|
|
4102
|
+
model_dog = AnotherDog(dog)
|
|
4103
|
+
assert model_dog == dog
|
|
4104
|
+
|
|
4105
|
+
assert _deserialize(Animal, pet) == model_pet
|
|
4106
|
+
assert _deserialize(Animal, dog) == model_dog
|
|
4107
|
+
assert _deserialize(AnotherPet, dog) == model_dog
|
|
4108
|
+
|
|
4109
|
+
assert AnotherPet(name="Buddy", trained=True) == model_pet
|
|
4110
|
+
assert AnotherDog(name="Rex", trained=True, breed="German Shepherd") == model_dog
|