@typespec/http-client-python 0.16.0 → 0.16.1-dev.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 (32) hide show
  1. package/dist/emitter/emitter.d.ts.map +1 -1
  2. package/dist/emitter/emitter.js +5 -4
  3. package/dist/emitter/emitter.js.map +1 -1
  4. package/emitter/src/emitter.ts +5 -4
  5. package/emitter/temp/tsconfig.tsbuildinfo +1 -1
  6. package/eng/scripts/ci/regenerate.ts +4 -5
  7. package/eng/scripts/setup/__pycache__/package_manager.cpython-39.pyc +0 -0
  8. package/eng/scripts/setup/__pycache__/venvtools.cpython-39.pyc +0 -0
  9. package/eng/scripts/setup/install.py +21 -11
  10. package/eng/scripts/setup/install.ts +29 -4
  11. package/eng/scripts/setup/run-python3.ts +1 -1
  12. package/eng/scripts/setup/system-requirements.ts +13 -5
  13. package/generator/build/lib/pygen/black.py +2 -2
  14. package/generator/build/lib/pygen/codegen/models/code_model.py +4 -0
  15. package/generator/build/lib/pygen/codegen/models/list_type.py +3 -7
  16. package/generator/build/lib/pygen/codegen/models/operation.py +0 -4
  17. package/generator/build/lib/pygen/codegen/serializers/model_serializer.py +21 -12
  18. package/generator/build/lib/pygen/codegen/serializers/operation_groups_serializer.py +5 -0
  19. package/generator/build/lib/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2 +1 -1
  20. package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
  21. package/generator/pygen/black.py +2 -2
  22. package/generator/pygen/codegen/models/code_model.py +4 -0
  23. package/generator/pygen/codegen/models/list_type.py +3 -7
  24. package/generator/pygen/codegen/models/operation.py +0 -4
  25. package/generator/pygen/codegen/serializers/model_serializer.py +21 -12
  26. package/generator/pygen/codegen/serializers/operation_groups_serializer.py +5 -0
  27. package/generator/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2 +1 -1
  28. package/generator/test/azure/mock_api_tests/asynctests/test_azure_client_generator_core_hierrarchy_building_async.py +53 -0
  29. package/generator/test/azure/mock_api_tests/test_azure_client_generator_core_hierrarchy_building.py +45 -0
  30. package/generator/test/azure/requirements.txt +1 -0
  31. package/generator/test/unittests/test_model_base_serialization.py +47 -0
  32. package/package.json +1 -1
@@ -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
  },
@@ -8,28 +8,33 @@
8
8
  import sys
9
9
 
10
10
  if not sys.version_info >= (3, 9, 0):
11
- raise Warning(
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
- raise Warning(
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
- raise Warning(
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.8, go to work
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
- venv_context = create_venv_with_package_manager(venv_path)
50
+ try:
51
+ venv_context = create_venv_with_package_manager(venv_path)
46
52
 
47
- # Install required packages - install_packages handles package manager logic
48
- install_packages(["-U", "black"], venv_context)
49
- install_packages(["-e", f"{_ROOT_DIR}/generator"], venv_context)
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 { runPython3 } from "./run-python3.js";
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
- await runPython3("./eng/scripts/setup/install.py");
6
- console.log("Found Python on your local environment and created a venv with all requirements."); // eslint-disable-line no-console
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("No Python found on your local environment. We will use Pyodide instead."); // eslint-disable-line no-console
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.8",
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
- const cleanedVersion = coerce(version);
51
- if (!cleanedVersion) {
52
- throw new Error(`Invalid version ${version}.`);
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
- return satisfies(cleanedVersion, requirement, true);
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
- "\n".join([lines[0] + ",".join([""] + pylint_disables)] + lines[1:])
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)}\n" + file_content
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
- has_operation_named_list = any(
44
- o.name.lower() == "list"
45
- for c in self.code_model.clients
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
- return [super_call]
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('azure_arm') %}
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'] }}",
@@ -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
- "\n".join([lines[0] + ",".join([""] + pylint_disables)] + lines[1:])
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)}\n" + file_content
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
- has_operation_named_list = any(
44
- o.name.lower() == "list"
45
- for c in self.code_model.clients
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
- return [super_call]
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('azure_arm') %}
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
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typespec/http-client-python",
3
- "version": "0.16.0",
3
+ "version": "0.16.1-dev.0",
4
4
  "author": "Microsoft Corporation",
5
5
  "description": "TypeSpec emitter for Python SDKs",
6
6
  "homepage": "https://typespec.io",