@autorest/python 6.39.0 → 6.41.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 (27) hide show
  1. package/generator/build/lib/pygen/black.py +2 -2
  2. package/generator/build/lib/pygen/codegen/models/code_model.py +4 -0
  3. package/generator/build/lib/pygen/codegen/models/list_type.py +3 -7
  4. package/generator/build/lib/pygen/codegen/models/operation.py +0 -4
  5. package/generator/build/lib/pygen/codegen/models/paging_operation.py +4 -0
  6. package/generator/build/lib/pygen/codegen/models/parameter.py +1 -1
  7. package/generator/build/lib/pygen/codegen/serializers/builder_serializer.py +7 -0
  8. package/generator/build/lib/pygen/codegen/serializers/model_serializer.py +21 -12
  9. package/generator/build/lib/pygen/codegen/serializers/operation_groups_serializer.py +5 -0
  10. package/generator/build/lib/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2 +1 -1
  11. package/generator/build/lib/pygen/codegen/templates/serialization.py.jinja2 +1 -1
  12. package/generator/build/lib/pygen/preprocess/__init__.py +13 -3
  13. package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
  14. package/generator/pygen/black.py +2 -2
  15. package/generator/pygen/codegen/models/code_model.py +4 -0
  16. package/generator/pygen/codegen/models/list_type.py +3 -7
  17. package/generator/pygen/codegen/models/operation.py +0 -4
  18. package/generator/pygen/codegen/models/paging_operation.py +4 -0
  19. package/generator/pygen/codegen/models/parameter.py +1 -1
  20. package/generator/pygen/codegen/serializers/builder_serializer.py +7 -0
  21. package/generator/pygen/codegen/serializers/model_serializer.py +21 -12
  22. package/generator/pygen/codegen/serializers/operation_groups_serializer.py +5 -0
  23. package/generator/pygen/codegen/templates/packaging_templates/pyproject.toml.jinja2 +1 -1
  24. package/generator/pygen/codegen/templates/serialization.py.jinja2 +1 -1
  25. package/generator/pygen/preprocess/__init__.py +13 -3
  26. package/package.json +2 -2
  27. package/scripts/__pycache__/venvtools.cpython-310.pyc +0 -0
@@ -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"
@@ -96,6 +96,10 @@ class PagingOperationBase(OperationBase[PagingResponseType]):
96
96
  return self._get_attr_name(wire_name)
97
97
  return wire_name
98
98
 
99
+ @property
100
+ def next_link_is_nested(self) -> bool:
101
+ return self.yaml_data.get("nextLinkIsNested", False)
102
+
99
103
  @property
100
104
  def item_name(self) -> str:
101
105
  wire_name = self.yaml_data["itemName"]
@@ -63,7 +63,7 @@ class _ParameterBase(BaseModel, abc.ABC): # pylint: disable=too-many-instance-a
63
63
  self.wire_name: str = yaml_data.get("wireName", "")
64
64
  self.client_name: str = self.yaml_data["clientName"]
65
65
  self.optional: bool = self.yaml_data["optional"]
66
- self.implementation: str = yaml_data.get("implementation", None)
66
+ self.implementation: Optional[str] = yaml_data.get("implementation", None)
67
67
  self.location: ParameterLocation = self.yaml_data["location"]
68
68
  self.client_default_value = self.yaml_data.get("clientDefaultValue", None)
69
69
  self.in_docstring = self.yaml_data.get("inDocstring", True)
@@ -1383,6 +1383,13 @@ class _PagingOperationSerializer(_OperationSerializer[PagingOperationType]):
1383
1383
  cont_token_property = "None"
1384
1384
  elif self.code_model.options["models-mode"] == "msrest":
1385
1385
  cont_token_property = f"deserialized.{next_link_name} or None"
1386
+ elif builder.next_link_is_nested:
1387
+ next_link_name_array = next_link_name.split(".")
1388
+ access = (
1389
+ "".join([f'.get("{i}", {{}})' for i in next_link_name_array[:-1]])
1390
+ + f'.get("{next_link_name_array[-1]}")'
1391
+ )
1392
+ cont_token_property = f"deserialized{access} or None"
1386
1393
  else:
1387
1394
  cont_token_property = f'deserialized.get("{next_link_name}") or None'
1388
1395
  list_type = "AsyncList" if self.async_mode else "iter"
@@ -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'] }}",
@@ -783,7 +783,7 @@ class Serializer: # pylint: disable=too-many-public-methods
783
783
 
784
784
  # If dependencies is empty, try with current data class
785
785
  # It has to be a subclass of Enum anyway
786
- enum_type = self.dependencies.get(data_type, data.__class__)
786
+ enum_type = self.dependencies.get(data_type, cast(type, data.__class__))
787
787
  if issubclass(enum_type, Enum):
788
788
  return Serializer.serialize_enum(data, enum_obj=enum_type)
789
789
 
@@ -213,7 +213,10 @@ class PreProcessPlugin(YamlUpdatePlugin):
213
213
  and not any(t for t in ["flattened", "groupedBy"] if body_parameter.get(t))
214
214
  ):
215
215
  origin_type = body_parameter["type"]["type"]
216
- is_dpg_model = body_parameter["type"].get("base") == "dpg"
216
+ model_type = (
217
+ body_parameter["type"] if origin_type == "model" else body_parameter["type"].get("elementType", {})
218
+ )
219
+ is_dpg_model = model_type.get("base") == "dpg"
217
220
  body_parameter["type"] = {
218
221
  "type": "combined",
219
222
  "types": [body_parameter["type"]],
@@ -222,8 +225,15 @@ class PreProcessPlugin(YamlUpdatePlugin):
222
225
  if not (self.is_tsp and has_multi_part_content_type(body_parameter)):
223
226
  body_parameter["type"]["types"].append(KNOWN_TYPES["binary"])
224
227
 
225
- if origin_type == "model" and is_dpg_model and self.options["models-mode"] == "dpg":
226
- body_parameter["type"]["types"].insert(1, KNOWN_TYPES["any-object"])
228
+ if self.options["models-mode"] == "dpg" and is_dpg_model:
229
+ if origin_type == "model":
230
+ body_parameter["type"]["types"].insert(1, KNOWN_TYPES["any-object"])
231
+ else:
232
+ # dict or list
233
+ # copy the original dict / list type
234
+ any_obj_list_or_dict = copy.deepcopy(body_parameter["type"]["types"][0])
235
+ any_obj_list_or_dict["elementType"] = KNOWN_TYPES["any-object"]
236
+ body_parameter["type"]["types"].insert(1, any_obj_list_or_dict)
227
237
  code_model["types"].append(body_parameter["type"])
228
238
 
229
239
  def pad_reserved_words(self, name: str, pad_type: PadType):
@@ -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"
@@ -96,6 +96,10 @@ class PagingOperationBase(OperationBase[PagingResponseType]):
96
96
  return self._get_attr_name(wire_name)
97
97
  return wire_name
98
98
 
99
+ @property
100
+ def next_link_is_nested(self) -> bool:
101
+ return self.yaml_data.get("nextLinkIsNested", False)
102
+
99
103
  @property
100
104
  def item_name(self) -> str:
101
105
  wire_name = self.yaml_data["itemName"]
@@ -63,7 +63,7 @@ class _ParameterBase(BaseModel, abc.ABC): # pylint: disable=too-many-instance-a
63
63
  self.wire_name: str = yaml_data.get("wireName", "")
64
64
  self.client_name: str = self.yaml_data["clientName"]
65
65
  self.optional: bool = self.yaml_data["optional"]
66
- self.implementation: str = yaml_data.get("implementation", None)
66
+ self.implementation: Optional[str] = yaml_data.get("implementation", None)
67
67
  self.location: ParameterLocation = self.yaml_data["location"]
68
68
  self.client_default_value = self.yaml_data.get("clientDefaultValue", None)
69
69
  self.in_docstring = self.yaml_data.get("inDocstring", True)
@@ -1383,6 +1383,13 @@ class _PagingOperationSerializer(_OperationSerializer[PagingOperationType]):
1383
1383
  cont_token_property = "None"
1384
1384
  elif self.code_model.options["models-mode"] == "msrest":
1385
1385
  cont_token_property = f"deserialized.{next_link_name} or None"
1386
+ elif builder.next_link_is_nested:
1387
+ next_link_name_array = next_link_name.split(".")
1388
+ access = (
1389
+ "".join([f'.get("{i}", {{}})' for i in next_link_name_array[:-1]])
1390
+ + f'.get("{next_link_name_array[-1]}")'
1391
+ )
1392
+ cont_token_property = f"deserialized{access} or None"
1386
1393
  else:
1387
1394
  cont_token_property = f'deserialized.get("{next_link_name}") or None'
1388
1395
  list_type = "AsyncList" if self.async_mode else "iter"
@@ -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'] }}",
@@ -783,7 +783,7 @@ class Serializer: # pylint: disable=too-many-public-methods
783
783
 
784
784
  # If dependencies is empty, try with current data class
785
785
  # It has to be a subclass of Enum anyway
786
- enum_type = self.dependencies.get(data_type, data.__class__)
786
+ enum_type = self.dependencies.get(data_type, cast(type, data.__class__))
787
787
  if issubclass(enum_type, Enum):
788
788
  return Serializer.serialize_enum(data, enum_obj=enum_type)
789
789
 
@@ -213,7 +213,10 @@ class PreProcessPlugin(YamlUpdatePlugin):
213
213
  and not any(t for t in ["flattened", "groupedBy"] if body_parameter.get(t))
214
214
  ):
215
215
  origin_type = body_parameter["type"]["type"]
216
- is_dpg_model = body_parameter["type"].get("base") == "dpg"
216
+ model_type = (
217
+ body_parameter["type"] if origin_type == "model" else body_parameter["type"].get("elementType", {})
218
+ )
219
+ is_dpg_model = model_type.get("base") == "dpg"
217
220
  body_parameter["type"] = {
218
221
  "type": "combined",
219
222
  "types": [body_parameter["type"]],
@@ -222,8 +225,15 @@ class PreProcessPlugin(YamlUpdatePlugin):
222
225
  if not (self.is_tsp and has_multi_part_content_type(body_parameter)):
223
226
  body_parameter["type"]["types"].append(KNOWN_TYPES["binary"])
224
227
 
225
- if origin_type == "model" and is_dpg_model and self.options["models-mode"] == "dpg":
226
- body_parameter["type"]["types"].insert(1, KNOWN_TYPES["any-object"])
228
+ if self.options["models-mode"] == "dpg" and is_dpg_model:
229
+ if origin_type == "model":
230
+ body_parameter["type"]["types"].insert(1, KNOWN_TYPES["any-object"])
231
+ else:
232
+ # dict or list
233
+ # copy the original dict / list type
234
+ any_obj_list_or_dict = copy.deepcopy(body_parameter["type"]["types"][0])
235
+ any_obj_list_or_dict["elementType"] = KNOWN_TYPES["any-object"]
236
+ body_parameter["type"]["types"].insert(1, any_obj_list_or_dict)
227
237
  code_model["types"].append(body_parameter["type"])
228
238
 
229
239
  def pad_reserved_words(self, name: str, pad_type: PadType):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autorest/python",
3
- "version": "6.39.0",
3
+ "version": "6.41.0",
4
4
  "description": "The Python extension for generators in AutoRest.",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "homepage": "https://github.com/Azure/autorest.python/blob/main/README.md",
21
21
  "dependencies": {
22
- "@typespec/http-client-python": "~0.16.0",
22
+ "@typespec/http-client-python": "~0.18.0",
23
23
  "@autorest/system-requirements": "~1.0.2",
24
24
  "fs-extra": "~11.2.0",
25
25
  "tsx": "~4.19.1"