@typespec/http-client-python 0.23.0 → 0.24.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 (77) hide show
  1. package/dist/emitter/code-model.d.ts.map +1 -1
  2. package/dist/emitter/code-model.js +35 -25
  3. package/dist/emitter/code-model.js.map +1 -1
  4. package/dist/emitter/http.d.ts +4 -4
  5. package/dist/emitter/http.d.ts.map +1 -1
  6. package/dist/emitter/http.js +41 -35
  7. package/dist/emitter/http.js.map +1 -1
  8. package/dist/emitter/types.d.ts +1 -1
  9. package/dist/emitter/types.d.ts.map +1 -1
  10. package/dist/emitter/types.js +2 -2
  11. package/dist/emitter/types.js.map +1 -1
  12. package/dist/emitter/utils.d.ts +2 -2
  13. package/dist/emitter/utils.d.ts.map +1 -1
  14. package/dist/emitter/utils.js +7 -6
  15. package/dist/emitter/utils.js.map +1 -1
  16. package/emitter/src/code-model.ts +61 -18
  17. package/emitter/src/http.ts +107 -22
  18. package/emitter/src/types.ts +2 -1
  19. package/emitter/src/utils.ts +14 -12
  20. package/emitter/temp/tsconfig.tsbuildinfo +1 -1
  21. package/eng/scripts/ci/dev_requirements.txt +3 -3
  22. package/eng/scripts/ci/pylintrc +1 -1
  23. package/eng/scripts/ci/regenerate.ts +8 -1
  24. package/eng/scripts/setup/__pycache__/package_manager.cpython-311.pyc +0 -0
  25. package/eng/scripts/setup/__pycache__/venvtools.cpython-311.pyc +0 -0
  26. package/generator/build/lib/pygen/codegen/models/code_model.py +4 -0
  27. package/generator/build/lib/pygen/codegen/models/enum_type.py +8 -1
  28. package/generator/build/lib/pygen/codegen/models/list_type.py +6 -2
  29. package/generator/build/lib/pygen/codegen/models/model_type.py +2 -2
  30. package/generator/build/lib/pygen/codegen/models/operation.py +10 -1
  31. package/generator/build/lib/pygen/codegen/models/operation_group.py +3 -1
  32. package/generator/build/lib/pygen/codegen/models/request_builder.py +20 -3
  33. package/generator/build/lib/pygen/codegen/models/response.py +2 -2
  34. package/generator/build/lib/pygen/codegen/models/utils.py +7 -0
  35. package/generator/build/lib/pygen/codegen/serializers/builder_serializer.py +26 -12
  36. package/generator/build/lib/pygen/codegen/serializers/client_serializer.py +1 -2
  37. package/generator/build/lib/pygen/codegen/serializers/general_serializer.py +1 -1
  38. package/generator/build/lib/pygen/codegen/serializers/model_serializer.py +3 -0
  39. package/generator/build/lib/pygen/codegen/templates/enum.py.jinja2 +3 -1
  40. package/generator/build/lib/pygen/codegen/templates/model_base.py.jinja2 +65 -9
  41. package/generator/build/lib/pygen/codegen/templates/serialization.py.jinja2 +14 -3
  42. package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
  43. package/generator/pygen/codegen/models/code_model.py +4 -0
  44. package/generator/pygen/codegen/models/enum_type.py +8 -1
  45. package/generator/pygen/codegen/models/list_type.py +6 -2
  46. package/generator/pygen/codegen/models/model_type.py +2 -2
  47. package/generator/pygen/codegen/models/operation.py +10 -1
  48. package/generator/pygen/codegen/models/operation_group.py +3 -1
  49. package/generator/pygen/codegen/models/request_builder.py +20 -3
  50. package/generator/pygen/codegen/models/response.py +2 -2
  51. package/generator/pygen/codegen/models/utils.py +7 -0
  52. package/generator/pygen/codegen/serializers/builder_serializer.py +26 -12
  53. package/generator/pygen/codegen/serializers/client_serializer.py +1 -2
  54. package/generator/pygen/codegen/serializers/general_serializer.py +1 -1
  55. package/generator/pygen/codegen/serializers/model_serializer.py +3 -0
  56. package/generator/pygen/codegen/templates/enum.py.jinja2 +3 -1
  57. package/generator/pygen/codegen/templates/model_base.py.jinja2 +65 -9
  58. package/generator/pygen/codegen/templates/serialization.py.jinja2 +14 -3
  59. package/generator/test/azure/mock_api_tests/asynctests/test_azure_client_generator_core_client_default_value_async.py +46 -0
  60. package/generator/test/azure/mock_api_tests/asynctests/test_azure_client_generator_core_client_location_async.py +48 -21
  61. package/generator/test/azure/mock_api_tests/asynctests/test_azure_resource_manager_multi_service_async.py +110 -0
  62. package/generator/test/azure/mock_api_tests/asynctests/test_service_multi_service_async.py +31 -0
  63. package/generator/test/azure/mock_api_tests/asynctests/test_special_words_async.py +18 -0
  64. package/generator/test/azure/mock_api_tests/test_azure_arm_operationtemplates.py +16 -0
  65. package/generator/test/azure/mock_api_tests/test_azure_client_generator_core_client_default_value.py +42 -0
  66. package/generator/test/azure/mock_api_tests/test_azure_client_generator_core_client_location.py +44 -19
  67. package/generator/test/azure/mock_api_tests/test_azure_resource_manager_multi_service.py +104 -0
  68. package/generator/test/azure/mock_api_tests/test_service_multi_service.py +29 -0
  69. package/generator/test/azure/mock_api_tests/test_special_words.py +17 -0
  70. package/generator/test/azure/requirements.txt +9 -1
  71. package/generator/test/generic_mock_api_tests/asynctests/test_parameters_query_async.py +18 -0
  72. package/generator/test/generic_mock_api_tests/test_parameters_query.py +17 -0
  73. package/generator/test/generic_mock_api_tests/test_typetest_scalar.py +0 -5
  74. package/generator/test/generic_mock_api_tests/test_typetest_union_discriminated.py +290 -0
  75. package/generator/test/unbranded/requirements.txt +2 -0
  76. package/generator/test/unittests/test_model_base_serialization.py +323 -5
  77. package/package.json +5 -5
@@ -93,7 +93,9 @@ class OperationGroup(BaseModel):
93
93
  @property
94
94
  def need_validation(self) -> bool:
95
95
  """Whether any of its operations need validation"""
96
- return any(o for o in self.operations if o.need_validation)
96
+ return any(o for o in self.operations if o.need_validation) or any(
97
+ og for og in self.operation_groups if og.need_validation
98
+ )
97
99
 
98
100
  def imports(self, async_mode: bool, **kwargs: Any) -> FileImport:
99
101
  file_import = FileImport(self.code_model)
@@ -16,11 +16,12 @@ from typing import (
16
16
  from abc import abstractmethod
17
17
 
18
18
  from .base_builder import BaseBuilder
19
- from .utils import add_to_pylint_disable
19
+ from .utils import add_to_pylint_disable, LOCALS_LENGTH_LIMIT, REQUEST_BUILDER_BODY_VARIABLES_LENGTH
20
20
  from .parameter_list import (
21
21
  RequestBuilderParameterList,
22
22
  OverloadedRequestBuilderParameterList,
23
23
  )
24
+ from .parameter import ParameterLocation
24
25
  from .imports import FileImport, ImportType, TypingSection, MsrestImportType
25
26
  from ...utils import NAME_LENGTH_LIMIT
26
27
 
@@ -67,9 +68,25 @@ class RequestBuilderBase(BaseBuilder[ParameterListType, Sequence["RequestBuilder
67
68
  return self.yaml_data.get("discriminator") in ("lro", "lropaging")
68
69
 
69
70
  def pylint_disable(self, async_mode: bool) -> str:
71
+ retval = ""
70
72
  if len(self.name) > NAME_LENGTH_LIMIT:
71
- return add_to_pylint_disable("", "name-too-long")
72
- return ""
73
+ retval = add_to_pylint_disable(retval, "name-too-long")
74
+ method_params = self.parameters.method
75
+ if len(method_params) > LOCALS_LENGTH_LIMIT - REQUEST_BUILDER_BODY_VARIABLES_LENGTH:
76
+ retval = add_to_pylint_disable(retval, "too-many-locals")
77
+ if (
78
+ len(
79
+ [
80
+ p
81
+ for p in method_params
82
+ if p.optional and p.location in [ParameterLocation.HEADER, ParameterLocation.QUERY]
83
+ ]
84
+ )
85
+ > LOCALS_LENGTH_LIMIT
86
+ ):
87
+ retval = add_to_pylint_disable(retval, "too-many-statements")
88
+ retval = add_to_pylint_disable(retval, "too-many-branches")
89
+ return retval
73
90
 
74
91
  def response_type_annotation(self, **kwargs) -> str:
75
92
  return "HttpRequest"
@@ -233,7 +233,7 @@ class LROResponse(Response):
233
233
 
234
234
  def get_no_polling_method(self, async_mode: bool) -> str:
235
235
  """Get the default no polling method"""
236
- return self.get_no_polling_method_path(async_mode).split(".")[-1]
236
+ return self.get_no_polling_method_path(async_mode).rsplit(".", maxsplit=1)[-1]
237
237
 
238
238
  @staticmethod
239
239
  def get_base_polling_method_path(async_mode: bool) -> str:
@@ -242,7 +242,7 @@ class LROResponse(Response):
242
242
 
243
243
  def get_base_polling_method(self, async_mode: bool) -> str:
244
244
  """Get the base polling method."""
245
- return self.get_base_polling_method_path(async_mode).split(".")[-1]
245
+ return self.get_base_polling_method_path(async_mode).rsplit(".", maxsplit=1)[-1]
246
246
 
247
247
  def type_annotation(self, **kwargs: Any) -> str:
248
248
  return f"{self.get_poller(kwargs.get('async_mode', False))}[{super().type_annotation(**kwargs)}]"
@@ -30,3 +30,10 @@ class NamespaceType(str, Enum):
30
30
  OPERATION = "operation"
31
31
  CLIENT = "client"
32
32
  TYPES_FILE = "types_file"
33
+
34
+
35
+ LOCALS_LENGTH_LIMIT = 25
36
+
37
+ REQUEST_BUILDER_BODY_VARIABLES_LENGTH = 6 # how many body variables are present in a request builder
38
+
39
+ OPERATION_BODY_VARIABLES_LENGTH = 14 # how many body variables are present in an operation
@@ -403,7 +403,12 @@ class RequestBuilderSerializer(_BuilderBaseSerializer[RequestBuilderType]):
403
403
  builder: RequestBuilderType,
404
404
  ) -> list[str]:
405
405
  def _get_value(param):
406
- declaration = param.get_declaration() if param.constant else None
406
+ if param.constant:
407
+ declaration = param.get_declaration()
408
+ elif param.client_default_value_declaration is not None:
409
+ declaration = param.client_default_value_declaration
410
+ else:
411
+ declaration = None
407
412
  if param.location in [ParameterLocation.HEADER, ParameterLocation.QUERY]:
408
413
  kwarg_dict = "headers" if param.location == ParameterLocation.HEADER else "params"
409
414
  return f"_{kwarg_dict}.pop('{param.wire_name}', {declaration})"
@@ -776,8 +781,14 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
776
781
  client_names = [
777
782
  overload.request_builder.parameters.body_parameter.client_name for overload in builder.overloads
778
783
  ]
779
- for v in sorted(set(client_names), key=client_names.index):
780
- retval.append(f"_{v} = None")
784
+ all_dpg_model_overloads = False
785
+ if self.code_model.options["models-mode"] == "dpg" and builder.overloads:
786
+ all_dpg_model_overloads = all(
787
+ isinstance(o.parameters.body_parameter.type, DPGModelType) for o in builder.overloads
788
+ )
789
+ if not all_dpg_model_overloads:
790
+ for v in sorted(set(client_names), key=client_names.index):
791
+ retval.append(f"_{v} = None")
781
792
  try:
782
793
  # if there is a binary overload, we do a binary check first.
783
794
  binary_overload = cast(
@@ -803,17 +814,20 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
803
814
  f'"{other_overload.parameters.body_parameter.default_content_type}"{check_body_suffix}'
804
815
  )
805
816
  except StopIteration:
806
- for idx, overload in enumerate(builder.overloads):
807
- if_statement = "if" if idx == 0 else "elif"
808
- body_param = overload.parameters.body_parameter
809
- retval.append(
810
- f"{if_statement} {body_param.type.instance_check_template.format(body_param.client_name)}:"
811
- )
812
- if body_param.default_content_type and not same_content_type:
817
+ if all_dpg_model_overloads:
818
+ retval.extend(f"{l}" for l in self._create_body_parameter(cast(OperationType, builder.overloads[0])))
819
+ else:
820
+ for idx, overload in enumerate(builder.overloads):
821
+ if_statement = "if" if idx == 0 else "elif"
822
+ body_param = overload.parameters.body_parameter
813
823
  retval.append(
814
- f' content_type = content_type or "{body_param.default_content_type}"{check_body_suffix}'
824
+ f"{if_statement} {body_param.type.instance_check_template.format(body_param.client_name)}:"
815
825
  )
816
- retval.extend(f" {l}" for l in self._create_body_parameter(cast(OperationType, overload)))
826
+ if body_param.default_content_type and not same_content_type:
827
+ retval.append(
828
+ f' content_type = content_type or "{body_param.default_content_type}"{check_body_suffix}'
829
+ )
830
+ retval.extend(f" {l}" for l in self._create_body_parameter(cast(OperationType, overload)))
817
831
  return retval
818
832
 
819
833
  def _create_request_builder_call(
@@ -182,11 +182,10 @@ class ClientSerializer:
182
182
  retval.append("self._serialize.client_side_validation = False")
183
183
  operation_groups = [og for og in self.client.operation_groups if not og.is_mixin]
184
184
  for og in operation_groups:
185
- api_version = ""
186
185
  retval.extend(
187
186
  [
188
187
  f"self.{og.property_name} = {og.class_name}(",
189
- f" self._client, self._config, self._serialize, self._deserialize{api_version}",
188
+ " self._client, self._config, self._serialize, self._deserialize",
190
189
  ")",
191
190
  ]
192
191
  )
@@ -23,7 +23,7 @@ VERSION_MAP = {
23
23
  "msrest": "0.7.1",
24
24
  "isodate": "0.6.1",
25
25
  "azure-mgmt-core": "1.6.0",
26
- "azure-core": "1.36.0",
26
+ "azure-core": "1.37.0",
27
27
  "typing-extensions": "4.6.0",
28
28
  "corehttp": "1.0.0b6",
29
29
  }
@@ -285,6 +285,9 @@ class DpgModelSerializer(_ModelSerializer):
285
285
  file_import.add_submodule_import("typing", "overload", ImportType.STDLIB)
286
286
  file_import.add_submodule_import("typing", "Mapping", ImportType.STDLIB)
287
287
  file_import.add_submodule_import("typing", "Any", ImportType.STDLIB)
288
+ # if there is a property named `list` we have to make sure there's no conflict with the built-in `list`
289
+ if self.code_model.has_property_named_list:
290
+ file_import.define_mypy_type("List", "list")
288
291
  return file_import
289
292
 
290
293
  def declare_model(self, model: ModelType) -> str:
@@ -1,5 +1,7 @@
1
1
 
2
- class {{ enum.name }}({{ enum.value_type.type_annotation(is_operation_file=False) }}, Enum, metaclass=CaseInsensitiveEnumMeta):
2
+ class {{ enum.name }}({{enum.pylint_disable()}}
3
+ {{ enum.value_type.type_annotation(is_operation_file=False) }}, Enum, metaclass=CaseInsensitiveEnumMeta
4
+ ):
3
5
  {% if enum.yaml_data.get("description") %}
4
6
  """{{ op_tools.wrap_string(enum.yaml_data["description"], "\n ") }}
5
7
  """
@@ -36,6 +36,7 @@ __all__ = ["SdkJSONEncoder", "Model", "rest_field", "rest_discriminator"]
36
36
 
37
37
  TZ_UTC = timezone.utc
38
38
  _T = typing.TypeVar("_T")
39
+ _NONE_TYPE = type(None)
39
40
 
40
41
  {% if code_model.has_external_type %}
41
42
  TYPE_HANDLER_REGISTRY = TypeHandlerRegistry()
@@ -223,7 +224,7 @@ def _deserialize_datetime(attr: typing.Union[str, datetime]) -> datetime:
223
224
  test_utc = date_obj.utctimetuple()
224
225
  if test_utc.tm_year > 9999 or test_utc.tm_year < 1:
225
226
  raise OverflowError("Hit max or min date")
226
- return date_obj
227
+ return date_obj # type: ignore[no-any-return]
227
228
 
228
229
 
229
230
  def _deserialize_datetime_rfc7231(attr: typing.Union[str, datetime]) -> datetime:
@@ -277,7 +278,7 @@ def _deserialize_time(attr: typing.Union[str, time]) -> time:
277
278
  """
278
279
  if isinstance(attr, time):
279
280
  return attr
280
- return isodate.parse_time(attr)
281
+ return isodate.parse_time(attr) # type: ignore[no-any-return]
281
282
 
282
283
 
283
284
  def _deserialize_bytes(attr):
@@ -382,9 +383,39 @@ class _MyMutableMapping(MutableMapping[str, typing.Any]):
382
383
  return key in self._data
383
384
 
384
385
  def __getitem__(self, key: str) -> typing.Any:
386
+ # If this key has been deserialized (for mutable types), we need to handle serialization
387
+ if hasattr(self, "_attr_to_rest_field"):
388
+ cache_attr = f"_deserialized_{key}"
389
+ if hasattr(self, cache_attr):
390
+ rf = _get_rest_field(getattr(self, "_attr_to_rest_field"), key)
391
+ if rf:
392
+ value = self._data.get(key)
393
+ if isinstance(value, (dict, list, set)):
394
+ # For mutable types, serialize and return
395
+ # But also update _data with serialized form and clear flag
396
+ # so mutations via this returned value affect _data
397
+ serialized = _serialize(value, rf._format)
398
+ # If serialized form is same type (no transformation needed),
399
+ # return _data directly so mutations work
400
+ if isinstance(serialized, type(value)) and serialized == value:
401
+ return self._data.get(key)
402
+ # Otherwise return serialized copy and clear flag
403
+ try:
404
+ object.__delattr__(self, cache_attr)
405
+ except AttributeError:
406
+ pass
407
+ # Store serialized form back
408
+ self._data[key] = serialized
409
+ return serialized
385
410
  return self._data.__getitem__(key)
386
411
 
387
412
  def __setitem__(self, key: str, value: typing.Any) -> None:
413
+ # Clear any cached deserialized value when setting through dictionary access
414
+ cache_attr = f"_deserialized_{key}"
415
+ try:
416
+ object.__delattr__(self, cache_attr)
417
+ except AttributeError:
418
+ pass
388
419
  self._data.__setitem__(key, value)
389
420
 
390
421
  def __delitem__(self, key: str) -> None:
@@ -886,16 +917,16 @@ def _get_deserialize_callable_from_annotation( # pylint: disable=too-many-retur
886
917
 
887
918
  # is it optional?
888
919
  try:
889
- if any(a for a in annotation.__args__ if a == type(None)): # pyright: ignore
920
+ if any(a is _NONE_TYPE for a in annotation.__args__): # pyright: ignore
890
921
  if len(annotation.__args__) <= 2: # pyright: ignore
891
922
  if_obj_deserializer = _get_deserialize_callable_from_annotation(
892
- next(a for a in annotation.__args__ if a != type(None)), module, rf # pyright: ignore
923
+ next(a for a in annotation.__args__ if a is not _NONE_TYPE), module, rf # pyright: ignore
893
924
  )
894
925
 
895
926
  return functools.partial(_deserialize_with_optional, if_obj_deserializer)
896
927
  # the type is Optional[Union[...]], we need to remove the None type from the Union
897
928
  annotation_copy = copy.copy(annotation)
898
- annotation_copy.__args__ = [a for a in annotation_copy.__args__ if a != type(None)] # pyright: ignore
929
+ annotation_copy.__args__ = [a for a in annotation_copy.__args__ if a is not _NONE_TYPE] # pyright: ignore
899
930
  return _get_deserialize_callable_from_annotation(annotation_copy, module, rf)
900
931
  except AttributeError:
901
932
  pass
@@ -1093,14 +1124,37 @@ class _RestField:
1093
1124
  def __get__(self, obj: Model, type=None): # pylint: disable=redefined-builtin
1094
1125
  # by this point, type and rest_name will have a value bc we default
1095
1126
  # them in __new__ of the Model class
1096
- item = obj.get(self._rest_name)
1127
+ # Use _data.get() directly to avoid triggering __getitem__ which clears the cache
1128
+ item = obj._data.get(self._rest_name)
1097
1129
  if item is None:
1098
1130
  return item
1099
1131
  if self._is_model:
1100
1132
  return item
1101
- return _deserialize(self._type, _serialize(item, self._format), rf=self)
1133
+
1134
+ # For mutable types, we want mutations to directly affect _data
1135
+ # Check if we've already deserialized this value
1136
+ cache_attr = f"_deserialized_{self._rest_name}"
1137
+ if hasattr(obj, cache_attr):
1138
+ # Return the value from _data directly (it's been deserialized in place)
1139
+ return obj._data.get(self._rest_name)
1140
+
1141
+ deserialized = _deserialize(self._type, _serialize(item, self._format), rf=self)
1142
+
1143
+ # For mutable types, store the deserialized value back in _data
1144
+ # so mutations directly affect _data
1145
+ if isinstance(deserialized, (dict, list, set)):
1146
+ obj._data[self._rest_name] = deserialized
1147
+ object.__setattr__(obj, cache_attr, True) # Mark as deserialized
1148
+ return deserialized
1149
+
1150
+ return deserialized
1102
1151
 
1103
1152
  def __set__(self, obj: Model, value) -> None:
1153
+ # Clear the cached deserialized object when setting a new value
1154
+ cache_attr = f"_deserialized_{self._rest_name}"
1155
+ if hasattr(obj, cache_attr):
1156
+ object.__delattr__(obj, cache_attr)
1157
+
1104
1158
  if value is None:
1105
1159
  # we want to wipe out entries if users set attr to None
1106
1160
  try:
@@ -1274,7 +1328,7 @@ def _get_wrapped_element(
1274
1328
  _get_element(v, exclude_readonly, meta, wrapped_element)
1275
1329
  else:
1276
1330
  wrapped_element.text = _get_primitive_type_value(v)
1277
- return wrapped_element
1331
+ return wrapped_element # type: ignore[no-any-return]
1278
1332
 
1279
1333
 
1280
1334
  def _get_primitive_type_value(v) -> str:
@@ -1287,7 +1341,9 @@ def _get_primitive_type_value(v) -> str:
1287
1341
  return str(v)
1288
1342
 
1289
1343
 
1290
- def _create_xml_element(tag, prefix=None, ns=None):
1344
+ def _create_xml_element(
1345
+ tag: typing.Any, prefix: typing.Optional[str] = None, ns: typing.Optional[str] = None
1346
+ ) -> ET.Element:
1291
1347
  if prefix and ns:
1292
1348
  ET.register_namespace(prefix, ns)
1293
1349
  if ns:
@@ -817,13 +817,20 @@ class Serializer: # pylint: disable=too-many-public-methods
817
817
  :param str data_type: Type of object in the iterable.
818
818
  :rtype: str, int, float, bool
819
819
  :return: serialized object
820
+ :raises TypeError: raise if data_type is not one of str, int, float, bool.
820
821
  """
821
822
  custom_serializer = cls._get_custom_serializers(data_type, **kwargs)
822
823
  if custom_serializer:
823
824
  return custom_serializer(data)
824
825
  if data_type == "str":
825
826
  return cls.serialize_unicode(data)
826
- return eval(data_type)(data) # nosec # pylint: disable=eval-used
827
+ if data_type == "int":
828
+ return int(data)
829
+ if data_type == "float":
830
+ return float(data)
831
+ if data_type == "bool":
832
+ return bool(data)
833
+ raise TypeError("Unknown basic data type: {}".format(data_type))
827
834
 
828
835
  @classmethod
829
836
  def serialize_unicode(cls, data):
@@ -1753,7 +1760,7 @@ class Deserializer:
1753
1760
  :param str data_type: deserialization data type.
1754
1761
  :return: Deserialized basic type.
1755
1762
  :rtype: str, int, float or bool
1756
- :raises TypeError: if string format is not valid.
1763
+ :raises TypeError: if string format is not valid or data_type is not one of str, int, float, bool.
1757
1764
  """
1758
1765
  # If we're here, data is supposed to be a basic type.
1759
1766
  # If it's still an XML node, take the text
@@ -1779,7 +1786,11 @@ class Deserializer:
1779
1786
 
1780
1787
  if data_type == "str":
1781
1788
  return self.deserialize_unicode(attr)
1782
- return eval(data_type)(attr) # nosec # pylint: disable=eval-used
1789
+ if data_type == "int":
1790
+ return int(attr)
1791
+ if data_type == "float":
1792
+ return float(attr)
1793
+ raise TypeError("Unknown basic data type: {}".format(data_type))
1783
1794
 
1784
1795
  @staticmethod
1785
1796
  def deserialize_unicode(data):
@@ -0,0 +1,46 @@
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.clientdefaultvalue.aio import ClientDefaultValueClient
8
+ from specs.azure.clientgenerator.core.clientdefaultvalue.models import ModelWithDefaultValues
9
+
10
+
11
+ @pytest.fixture
12
+ async def client():
13
+ async with ClientDefaultValueClient() as client:
14
+ yield client
15
+
16
+
17
+ @pytest.mark.asyncio
18
+ async def test_put_model_property(client: ClientDefaultValueClient):
19
+ """Test case 1: @clientDefaultValue for model property."""
20
+ body = ModelWithDefaultValues(name="test")
21
+ result = await client.put_model_property(body=body)
22
+ assert result.name == "test"
23
+ assert result.timeout == 30
24
+ assert result.tier == "standard"
25
+ assert result.retry is True
26
+
27
+
28
+ @pytest.mark.asyncio
29
+ async def test_get_operation_parameter(client: ClientDefaultValueClient):
30
+ """Test case 2: @clientDefaultValue for operation parameter."""
31
+ # Test with only required parameter (name), defaults should be applied
32
+ await client.get_operation_parameter(name="test")
33
+
34
+
35
+ @pytest.mark.asyncio
36
+ async def test_get_path_parameter(client: ClientDefaultValueClient):
37
+ """Test case 3: @clientDefaultValue for first path segment."""
38
+ # Test with only required segment2, segment1 should use default
39
+ await client.get_path_parameter(segment2="segment2")
40
+
41
+
42
+ @pytest.mark.asyncio
43
+ async def test_get_header_parameter(client: ClientDefaultValueClient):
44
+ """Test case 4: @clientDefaultValue for header parameters."""
45
+ # Test with default header values
46
+ await client.get_header_parameter()
@@ -4,52 +4,79 @@
4
4
  # license information.
5
5
  # --------------------------------------------------------------------------
6
6
  import pytest
7
- from specs.azure.clientgenerator.core.clientlocation.aio import ClientLocationClient
7
+ from specs.azure.clientgenerator.core.clientlocation.parameter.aio import MoveMethodParameterToClient
8
+ from specs.azure.clientgenerator.core.clientlocation.subclient.aio import MoveToExistingSubClient
9
+ from specs.azure.clientgenerator.core.clientlocation.newsubclient.aio import MoveToNewSubClient
10
+ from specs.azure.clientgenerator.core.clientlocation.rootclient.aio import MoveToRootClient
8
11
 
9
12
 
10
13
  @pytest.fixture
11
- async def client():
12
- async with ClientLocationClient(storage_account="testaccount") as client:
14
+ async def move_method_parameter_to_client():
15
+ async with MoveMethodParameterToClient(storage_account="testaccount") as client:
16
+ yield client
17
+
18
+
19
+ @pytest.fixture
20
+ async def move_to_existing_sub_client():
21
+ async with MoveToExistingSubClient() as client:
22
+ yield client
23
+
24
+
25
+ @pytest.fixture
26
+ async def move_to_new_sub_client():
27
+ async with MoveToNewSubClient() as client:
28
+ yield client
29
+
30
+
31
+ @pytest.fixture
32
+ async def move_to_root_client():
33
+ async with MoveToRootClient() as client:
13
34
  yield client
14
35
 
15
36
 
16
37
  @pytest.mark.asyncio
17
- async def test_get_health_status(client: ClientLocationClient):
18
- await client.get_health_status()
38
+ async def test_move_method_parameter_to_client_blob_operations_get_blob(
39
+ move_method_parameter_to_client: MoveMethodParameterToClient,
40
+ ):
41
+ await move_method_parameter_to_client.blob_operations.get_blob(container="testcontainer", blob="testblob.txt")
19
42
 
20
43
 
21
44
  @pytest.mark.asyncio
22
- async def test_archive_operations_archive_product(client: ClientLocationClient):
23
- await client.archive_operations.archive_product()
45
+ async def test_move_to_existing_sub_client_user_operations_get_user(
46
+ move_to_existing_sub_client: MoveToExistingSubClient,
47
+ ):
48
+ await move_to_existing_sub_client.user_operations.get_user()
24
49
 
25
50
 
26
51
  @pytest.mark.asyncio
27
- async def test_move_to_existing_sub_client_admin_operations_get_admin_info(client: ClientLocationClient):
28
- await client.move_to_existing_sub_client.admin_operations.get_admin_info()
52
+ async def test_move_to_existing_sub_client_admin_operations_delete_user(
53
+ move_to_existing_sub_client: MoveToExistingSubClient,
54
+ ):
55
+ await move_to_existing_sub_client.admin_operations.delete_user()
29
56
 
30
57
 
31
58
  @pytest.mark.asyncio
32
- async def test_move_to_existing_sub_client_admin_operations_delete_user(client: ClientLocationClient):
33
- await client.move_to_existing_sub_client.admin_operations.delete_user()
59
+ async def test_move_to_existing_sub_client_admin_operations_get_admin_info(
60
+ move_to_existing_sub_client: MoveToExistingSubClient,
61
+ ):
62
+ await move_to_existing_sub_client.admin_operations.get_admin_info()
34
63
 
35
64
 
36
65
  @pytest.mark.asyncio
37
- async def test_move_to_existing_sub_client_user_operations_get_user(client: ClientLocationClient):
38
- await client.move_to_existing_sub_client.user_operations.get_user()
66
+ async def test_move_to_new_sub_client_product_operations_list_products(move_to_new_sub_client: MoveToNewSubClient):
67
+ await move_to_new_sub_client.product_operations.list_products()
39
68
 
40
69
 
41
70
  @pytest.mark.asyncio
42
- async def test_move_to_new_sub_client_product_operations_list_products(client: ClientLocationClient):
43
- await client.move_to_new_sub_client.product_operations.list_products()
71
+ async def test_move_to_new_sub_client_archive_operations_archive_product(move_to_new_sub_client: MoveToNewSubClient):
72
+ await move_to_new_sub_client.archive_operations.archive_product()
44
73
 
45
74
 
46
75
  @pytest.mark.asyncio
47
- async def test_move_to_root_client_resource_operations_get_resource(client: ClientLocationClient):
48
- await client.move_to_root_client.resource_operations.get_resource()
76
+ async def test_move_to_root_client_resource_operations_get_resource(move_to_root_client: MoveToRootClient):
77
+ await move_to_root_client.resource_operations.get_resource()
49
78
 
50
79
 
51
80
  @pytest.mark.asyncio
52
- async def test_move_method_parameter_to_client_blob_operations_get_blob(client: ClientLocationClient):
53
- await client.move_method_parameter_to_client.blob_operations.get_blob(
54
- container="testcontainer", blob="testblob.txt"
55
- )
81
+ async def test_move_to_root_client_get_health_status(move_to_root_client: MoveToRootClient):
82
+ await move_to_root_client.get_health_status()
@@ -0,0 +1,110 @@
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 azure.core.exceptions import HttpResponseError
8
+ from azure.resourcemanager.multiservice.combined.aio import CombinedClient
9
+ from azure.resourcemanager.multiservice.combined.models import VirtualMachine, Disk
10
+
11
+
12
+ @pytest.fixture
13
+ async def client(credential, authentication_policy):
14
+ """Create a Combined async client for testing."""
15
+ return CombinedClient(
16
+ credential=credential,
17
+ subscription_id="00000000-0000-0000-0000-000000000000",
18
+ base_url="http://localhost:3000",
19
+ authentication_policy=authentication_policy,
20
+ polling_interval=0.1, # Speed up tests by reducing polling interval
21
+ )
22
+
23
+
24
+ @pytest.mark.asyncio
25
+ async def test_virtual_machines_get(client):
26
+ resource_group_name = "test-rg"
27
+ vm_name = "vm1"
28
+
29
+ with pytest.raises(HttpResponseError):
30
+ await client.virtual_machines.get(
31
+ resource_group_name=resource_group_name,
32
+ vm_name=vm_name,
33
+ api_version="av1", # invalid api version shall raise error
34
+ )
35
+
36
+ result = await client.virtual_machines.get(resource_group_name=resource_group_name, vm_name=vm_name)
37
+
38
+ assert result is not None
39
+ assert isinstance(result, VirtualMachine)
40
+ assert result.name == vm_name
41
+
42
+
43
+ @pytest.mark.asyncio
44
+ async def test_virtual_machines_create_or_update(client):
45
+ resource_group_name = "test-rg"
46
+ vm_name = "vm1"
47
+
48
+ vm_resource = VirtualMachine(location="eastus", properties={})
49
+
50
+ with pytest.raises(HttpResponseError):
51
+ poller = await client.virtual_machines.begin_create_or_update(
52
+ resource_group_name=resource_group_name,
53
+ vm_name=vm_name,
54
+ resource=vm_resource,
55
+ api_version="av1", # invalid api version shall raise error
56
+ )
57
+ await poller.result()
58
+
59
+ poller = await client.virtual_machines.begin_create_or_update(
60
+ resource_group_name=resource_group_name, vm_name=vm_name, resource=vm_resource
61
+ )
62
+
63
+ result = await poller.result()
64
+ assert result is not None
65
+ assert isinstance(result, VirtualMachine)
66
+ assert result.location == "eastus"
67
+
68
+
69
+ @pytest.mark.asyncio
70
+ async def test_disks_get(client):
71
+ resource_group_name = "test-rg"
72
+ disk_name = "disk1"
73
+ with pytest.raises(HttpResponseError):
74
+ await client.disks.get(
75
+ resource_group_name=resource_group_name,
76
+ disk_name=disk_name,
77
+ api_version="av1", # invalid api version shall raise error
78
+ )
79
+
80
+ result = await client.disks.get(resource_group_name=resource_group_name, disk_name=disk_name)
81
+
82
+ assert result is not None
83
+ assert isinstance(result, Disk)
84
+ assert result.name == disk_name
85
+
86
+
87
+ @pytest.mark.asyncio
88
+ async def test_disks_create_or_update(client):
89
+ resource_group_name = "test-rg"
90
+ disk_name = "disk1"
91
+
92
+ disk_resource = Disk(location="eastus", properties={})
93
+
94
+ with pytest.raises(HttpResponseError):
95
+ poller = await client.disks.begin_create_or_update(
96
+ resource_group_name=resource_group_name,
97
+ disk_name=disk_name,
98
+ resource=disk_resource,
99
+ api_version="av1", # invalid api version shall raise error
100
+ )
101
+ await poller.result()
102
+
103
+ poller = await client.disks.begin_create_or_update(
104
+ resource_group_name=resource_group_name, disk_name=disk_name, resource=disk_resource
105
+ )
106
+
107
+ result = await poller.result()
108
+ assert result is not None
109
+ assert isinstance(result, Disk)
110
+ assert result.location == "eastus"