@autorest/python 6.1.5 → 6.1.7

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 (29) hide show
  1. package/autorest/__init__.py +10 -3
  2. package/autorest/black/__init__.py +2 -8
  3. package/autorest/codegen/__init__.py +4 -2
  4. package/autorest/codegen/models/__init__.py +1 -1
  5. package/autorest/codegen/models/base_builder.py +4 -1
  6. package/autorest/codegen/models/client.py +5 -3
  7. package/autorest/codegen/models/combined_type.py +6 -4
  8. package/autorest/codegen/models/enum_type.py +6 -4
  9. package/autorest/codegen/models/model_type.py +30 -9
  10. package/autorest/codegen/models/operation.py +32 -0
  11. package/autorest/codegen/models/operation_group.py +11 -7
  12. package/autorest/codegen/models/parameter.py +44 -0
  13. package/autorest/codegen/models/property.py +8 -15
  14. package/autorest/codegen/models/request_builder_parameter.py +4 -2
  15. package/autorest/codegen/serializers/__init__.py +18 -2
  16. package/autorest/codegen/serializers/builder_serializer.py +64 -22
  17. package/autorest/codegen/serializers/client_serializer.py +3 -4
  18. package/autorest/codegen/serializers/general_serializer.py +8 -0
  19. package/autorest/codegen/serializers/model_serializer.py +159 -61
  20. package/autorest/codegen/templates/model_base.py.jinja2 +636 -0
  21. package/autorest/codegen/templates/model_container.py.jinja2 +6 -2
  22. package/autorest/codegen/templates/model_dpg.py.jinja2 +62 -0
  23. package/autorest/codegen/templates/model_init.py.jinja2 +0 -5
  24. package/autorest/codegen/templates/{model.py.jinja2 → model_msrest.py.jinja2} +1 -1
  25. package/autorest/codegen/templates/operation_group.py.jinja2 +1 -1
  26. package/autorest/codegen/templates/operation_groups_container.py.jinja2 +1 -1
  27. package/autorest/codegen/templates/validation.py.jinja2 +38 -0
  28. package/package.json +9 -10
  29. package/ChangeLog.md +0 -1284
@@ -173,6 +173,22 @@ def _get_json_response_template_to_status_codes(
173
173
  return retval
174
174
 
175
175
 
176
+ def _api_version_validation(builder: OperationType) -> str:
177
+ retval: List[str] = []
178
+ if builder.added_on:
179
+ retval.append(f' method_added_on="{builder.added_on}",')
180
+ params_added_on = defaultdict(list)
181
+ for parameter in builder.parameters:
182
+ if parameter.added_on:
183
+ params_added_on[parameter.added_on].append(parameter.client_name)
184
+ if params_added_on:
185
+ retval.append(f" params_added_on={dict(params_added_on)},")
186
+ if retval:
187
+ retval_str = "\n".join(retval)
188
+ return f"@api_version_validation(\n{retval_str}\n)"
189
+ return ""
190
+
191
+
176
192
  class _BuilderBaseSerializer(Generic[BuilderType]): # pylint: disable=abstract-method
177
193
  def __init__(self, code_model: CodeModel, async_mode: bool) -> None:
178
194
  self.code_model = code_model
@@ -590,8 +606,10 @@ class _OperationSerializer(
590
606
 
591
607
  def decorators(self, builder: OperationType) -> List[str]:
592
608
  """Decorators for the method"""
593
- super_decorators = super().decorators(builder)
594
- return super_decorators
609
+ retval = super().decorators(builder)
610
+ if _api_version_validation(builder):
611
+ retval.append(_api_version_validation(builder))
612
+ return retval
595
613
 
596
614
  def param_description(
597
615
  self, builder: OperationType
@@ -656,7 +674,7 @@ class _OperationSerializer(
656
674
  ser_ctxt_name = "serialization_ctxt"
657
675
  if xml_serialization_ctxt and self.code_model.options["models_mode"]:
658
676
  retval.append(f'{ser_ctxt_name} = {{"xml": {{{xml_serialization_ctxt}}}}}')
659
- if self.code_model.options["models_mode"]:
677
+ if self.code_model.options["models_mode"] == "msrest":
660
678
  is_xml_cmd = ", is_xml=True" if send_xml else ""
661
679
  serialization_ctxt_cmd = (
662
680
  f", {ser_ctxt_name}={ser_ctxt_name}" if xml_serialization_ctxt else ""
@@ -665,6 +683,8 @@ class _OperationSerializer(
665
683
  f"_{body_kwarg_name} = self._serialize.body({body_param.client_name}, "
666
684
  f"'{body_param.type.serialization_type}'{is_xml_cmd}{serialization_ctxt_cmd})"
667
685
  )
686
+ elif self.code_model.options["models_mode"] == "dpg":
687
+ create_body_call = f"_{body_kwarg_name} = json.dumps({body_param.client_name}, cls=AzureJSONEncoder)"
668
688
  else:
669
689
  create_body_call = f"_{body_kwarg_name} = {body_param.client_name}"
670
690
  if body_param.optional:
@@ -729,10 +749,12 @@ class _OperationSerializer(
729
749
  0
730
750
  ].parameters.body_parameter.default_content_type
731
751
  retval.append(f'content_type = content_type or "{default_content_type}"')
732
- for overload in builder.overloads:
733
- retval.append(
734
- f"_{overload.request_builder.parameters.body_parameter.client_name} = None"
735
- )
752
+ client_names = [
753
+ overload.request_builder.parameters.body_parameter.client_name
754
+ for overload in builder.overloads
755
+ ]
756
+ for v in sorted(set(client_names), key=client_names.index):
757
+ retval.append(f"_{v} = None")
736
758
  try:
737
759
  # if there is a binary overload, we do a binary check first.
738
760
  binary_overload = cast(
@@ -941,10 +963,16 @@ class _OperationSerializer(
941
963
  )
942
964
  )
943
965
  elif response.type:
944
- if self.code_model.options["models_mode"]:
966
+ if self.code_model.options["models_mode"] == "msrest":
945
967
  retval.append(
946
968
  f"deserialized = self._deserialize('{response.serialization_type}', pipeline_response)"
947
969
  )
970
+ elif self.code_model.options["models_mode"] == "dpg" and isinstance(
971
+ response.type, ModelType
972
+ ):
973
+ retval.append(
974
+ f"deserialized = _deserialize({response.serialization_type}, response.json())"
975
+ )
948
976
  else:
949
977
  deserialized_value = (
950
978
  "ET.fromstring(response.text())"
@@ -969,10 +997,15 @@ class _OperationSerializer(
969
997
  builder.default_error_deserialization
970
998
  and self.code_model.options["models_mode"]
971
999
  ):
972
- retval.append(
973
- f" error = self._deserialize.failsafe_deserialize({builder.default_error_deserialization}, "
974
- "pipeline_response)"
975
- )
1000
+ if self.code_model.options["models_mode"] == "dpg":
1001
+ retval.append(
1002
+ f" error = _deserialize({builder.default_error_deserialization}, response.json())"
1003
+ )
1004
+ else:
1005
+ retval.append(
1006
+ f" error = self._deserialize.failsafe_deserialize({builder.default_error_deserialization}, "
1007
+ "pipeline_response)"
1008
+ )
976
1009
  error_model = ", model=error"
977
1010
  retval.append(
978
1011
  " raise HttpResponseError(response=response{}{})".format(
@@ -1047,11 +1080,14 @@ class _OperationSerializer(
1047
1080
  retval.append(" 304: ResourceNotModifiedError,")
1048
1081
  for excep in builder.non_default_errors:
1049
1082
  error_model_str = ""
1050
- if (
1051
- isinstance(excep.type, ModelType)
1052
- and self.code_model.options["models_mode"]
1053
- ):
1054
- error_model_str = f", model=self._deserialize(_models.{excep.type.serialization_type}, response)"
1083
+ if isinstance(excep.type, ModelType):
1084
+ if self.code_model.options["models_mode"] == "msrest":
1085
+ error_model_str = (
1086
+ f", model=self._deserialize("
1087
+ f"_models.{excep.type.serialization_type}, response)"
1088
+ )
1089
+ elif self.code_model.options["models_mode"] == "dpg":
1090
+ error_model_str = f", model=_deserialize(_models.{excep.type.name}, response.json())"
1055
1091
  error_format_str = (
1056
1092
  ", error_format=ARMErrorFormat"
1057
1093
  if self.code_model.options["azure_arm"]
@@ -1138,6 +1174,8 @@ class _PagingOperationSerializer(
1138
1174
  return ["@overload"]
1139
1175
  if self.code_model.options["tracing"] and builder.want_tracing:
1140
1176
  retval.append("@distributed_trace")
1177
+ if _api_version_validation(builder):
1178
+ retval.append(_api_version_validation(builder))
1141
1179
  return retval
1142
1180
 
1143
1181
  def call_next_link_request_builder(self, builder: PagingOperationType) -> List[str]:
@@ -1218,11 +1256,15 @@ class _PagingOperationSerializer(
1218
1256
  f"{'async ' if self.async_mode else ''}def extract_data(pipeline_response):"
1219
1257
  ]
1220
1258
  response = builder.responses[0]
1221
- deserialized = (
1222
- f'self._deserialize("{response.serialization_type}", pipeline_response)'
1223
- if self.code_model.options["models_mode"]
1224
- else "pipeline_response.http_response.json()"
1225
- )
1259
+ deserialized = "pipeline_response.http_response.json()"
1260
+ if self.code_model.options["models_mode"] == "msrest":
1261
+ deserialized = (
1262
+ f'self._deserialize("{response.serialization_type}", pipeline_response)'
1263
+ )
1264
+ elif self.code_model.options["models_mode"] == "dpg":
1265
+ deserialized = (
1266
+ f"_deserialize({response.serialization_type}, pipeline_response)"
1267
+ )
1226
1268
  retval.append(f" deserialized = {deserialized}")
1227
1269
  item_name = builder.item_name
1228
1270
  list_of_elem = (
@@ -125,11 +125,10 @@ class ClientSerializer:
125
125
  )
126
126
  else:
127
127
  client_models_value = "{} # type: Dict[str, Any]"
128
- if self.code_model.options["models_mode"]:
128
+ is_msrest_model = self.code_model.options["models_mode"] == "msrest"
129
+ if is_msrest_model:
129
130
  retval.append(f"client_models = {client_models_value}")
130
- client_models_str = (
131
- "client_models" if self.code_model.options["models_mode"] else ""
132
- )
131
+ client_models_str = "client_models" if is_msrest_model else ""
133
132
  retval.append(f"self._serialize = Serializer({client_models_str})")
134
133
  retval.append(f"self._deserialize = Deserializer({client_models_str})")
135
134
  if not self.code_model.options["client_side_validation"]:
@@ -120,3 +120,11 @@ class GeneralSerializer:
120
120
  def serialize_serialization_file(self) -> str:
121
121
  template = self.env.get_template("serialization.py.jinja2")
122
122
  return template.render(code_model=self.code_model)
123
+
124
+ def serialize_model_base_file(self) -> str:
125
+ template = self.env.get_template("model_base.py.jinja2")
126
+ return template.render(code_model=self.code_model)
127
+
128
+ def serialize_validation_file(self) -> str:
129
+ template = self.env.get_template("validation.py.jinja2")
130
+ return template.render(code_model=self.code_model)
@@ -4,9 +4,10 @@
4
4
  # license information.
5
5
  # --------------------------------------------------------------------------
6
6
  from typing import cast, List
7
+ from abc import ABC, abstractmethod
7
8
  from jinja2 import Environment
8
9
  from ..models import ModelType, CodeModel, Property
9
- from ..models.imports import FileImport, TypingSection, MsrestImportType
10
+ from ..models.imports import FileImport, TypingSection, MsrestImportType, ImportType
10
11
  from .import_serializer import FileImportSerializer
11
12
 
12
13
 
@@ -26,11 +27,15 @@ def _documentation_string(
26
27
  return retval
27
28
 
28
29
 
29
- class ModelSerializer:
30
+ class _ModelSerializer(ABC):
30
31
  def __init__(self, code_model: CodeModel, env: Environment) -> None:
31
32
  self.code_model = code_model
32
33
  self.env = env
33
34
 
35
+ @abstractmethod
36
+ def imports(self) -> FileImport:
37
+ ...
38
+
34
39
  def serialize(self) -> str:
35
40
  # Generate the models
36
41
  template = self.env.get_template("model_container.py.jinja2")
@@ -41,46 +46,13 @@ class ModelSerializer:
41
46
  serializer=self,
42
47
  )
43
48
 
44
- def imports(self) -> FileImport:
45
- file_import = FileImport()
46
- file_import.add_msrest_import(
47
- self.code_model, "..", MsrestImportType.Module, TypingSection.REGULAR
48
- )
49
- for model in self.code_model.model_types:
50
- file_import.merge(model.imports(is_operation_file=False))
51
- init_line_parameters = [
52
- p for p in model.properties if not p.readonly and not p.is_discriminator
53
- ]
54
- for param in init_line_parameters:
55
- file_import.merge(param.imports())
56
- return file_import
49
+ @abstractmethod
50
+ def declare_model(self, model: ModelType) -> str:
51
+ ...
57
52
 
58
53
  @staticmethod
59
- def get_properties_to_initialize(model: ModelType) -> List[Property]:
60
- if model.parents:
61
- properties_to_initialize = list(
62
- {
63
- p.client_name: p
64
- for bm in model.parents
65
- for p in model.properties
66
- if p not in cast(ModelType, bm).properties
67
- or p.is_discriminator
68
- or p.constant
69
- }.values()
70
- )
71
- else:
72
- properties_to_initialize = model.properties
73
- return properties_to_initialize
74
-
75
- def declare_model(self, model: ModelType) -> str:
76
- basename = (
77
- "msrest.serialization.Model"
78
- if self.code_model.options["client_side_validation"]
79
- else "_serialization.Model"
80
- )
81
- if model.parents:
82
- basename = ", ".join([cast(ModelType, m).name for m in model.parents])
83
- return f"class {model.name}({basename}):{model.pylint_disable}"
54
+ def escape_dot(s: str):
55
+ return s.replace(".", "\\\\.")
84
56
 
85
57
  @staticmethod
86
58
  def input_documentation_string(prop: Property) -> List[str]:
@@ -94,27 +66,16 @@ class ModelSerializer:
94
66
  def super_call(self, model: ModelType):
95
67
  return f"super().__init__({self.properties_to_pass_to_super(model)})"
96
68
 
97
- def initialize_properties(self, model: ModelType) -> List[str]:
98
- init_args = []
99
- for prop in self.get_properties_to_initialize(model):
100
- if prop.is_discriminator:
101
- discriminator_value = (
102
- f"'{model.discriminator_value}'"
103
- if model.discriminator_value
104
- else None
105
- )
106
- if not discriminator_value:
107
- typing = "Optional[str]"
108
- else:
109
- typing = "str"
110
- init_args.append(
111
- f"self.{prop.client_name} = {discriminator_value} # type: {typing}"
112
- )
113
- elif prop.readonly:
114
- init_args.append(f"self.{prop.client_name} = None")
115
- elif not prop.constant:
116
- init_args.append(f"self.{prop.client_name} = {prop.client_name}")
117
- return init_args
69
+ @staticmethod
70
+ def initialize_discriminator_property(model: ModelType, prop: Property) -> str:
71
+ discriminator_value = (
72
+ f"'{model.discriminator_value}'" if model.discriminator_value else None
73
+ )
74
+ if not discriminator_value:
75
+ typing = "Optional[str]"
76
+ else:
77
+ typing = "str"
78
+ return f"self.{prop.client_name} = {discriminator_value} # type: {typing}"
118
79
 
119
80
  @staticmethod
120
81
  def initialize_standard_property(prop: Property):
@@ -163,3 +124,140 @@ class ModelSerializer:
163
124
  )
164
125
  properties_to_pass_to_super.append("**kwargs")
165
126
  return ", ".join(properties_to_pass_to_super)
127
+
128
+
129
+ class MsrestModelSerializer(_ModelSerializer):
130
+ def imports(self) -> FileImport:
131
+ file_import = FileImport()
132
+ file_import.add_msrest_import(
133
+ self.code_model, "..", MsrestImportType.Module, TypingSection.REGULAR
134
+ )
135
+ for model in self.code_model.model_types:
136
+ file_import.merge(model.imports(is_operation_file=False))
137
+ init_line_parameters = [
138
+ p for p in model.properties if not p.readonly and not p.is_discriminator
139
+ ]
140
+ for param in init_line_parameters:
141
+ file_import.merge(param.imports())
142
+ return file_import
143
+
144
+ def declare_model(self, model: ModelType) -> str:
145
+ basename = (
146
+ "msrest.serialization.Model"
147
+ if self.code_model.options["client_side_validation"]
148
+ else "_serialization.Model"
149
+ )
150
+ if model.parents:
151
+ basename = ", ".join([cast(ModelType, m).name for m in model.parents])
152
+ return f"class {model.name}({basename}):{model.pylint_disable}"
153
+
154
+ @staticmethod
155
+ def get_properties_to_initialize(model: ModelType) -> List[Property]:
156
+ if model.parents:
157
+ properties_to_initialize = list(
158
+ {
159
+ p.client_name: p
160
+ for bm in model.parents
161
+ for p in model.properties
162
+ if p not in cast(ModelType, bm).properties
163
+ or p.is_discriminator
164
+ or p.constant
165
+ }.values()
166
+ )
167
+ else:
168
+ properties_to_initialize = model.properties
169
+ return properties_to_initialize
170
+
171
+ def initialize_properties(self, model: ModelType) -> List[str]:
172
+ init_args = []
173
+ for prop in self.get_properties_to_initialize(model):
174
+ if prop.is_discriminator:
175
+ init_args.append(self.initialize_discriminator_property(model, prop))
176
+ elif prop.readonly:
177
+ init_args.append(f"self.{prop.client_name} = None")
178
+ elif not prop.constant:
179
+ init_args.append(f"self.{prop.client_name} = {prop.client_name}")
180
+ return init_args
181
+
182
+ @staticmethod
183
+ def declare_property(prop: Property) -> str:
184
+ if prop.flattened_names:
185
+ attribute_key = ".".join(
186
+ _ModelSerializer.escape_dot(n) for n in prop.flattened_names
187
+ )
188
+ else:
189
+ attribute_key = _ModelSerializer.escape_dot(prop.rest_api_name)
190
+ if prop.type.xml_serialization_ctxt:
191
+ xml_metadata = f", 'xml': {{{prop.type.xml_serialization_ctxt}}}"
192
+ else:
193
+ xml_metadata = ""
194
+ return f'"{prop.client_name}": {{"key": "{attribute_key}", "type": "{prop.serialization_type}"{xml_metadata}}},'
195
+
196
+
197
+ class DpgModelSerializer(_ModelSerializer):
198
+ def imports(self) -> FileImport:
199
+ file_import = FileImport()
200
+ file_import.add_submodule_import(
201
+ "..",
202
+ "_model_base",
203
+ ImportType.LOCAL,
204
+ TypingSection.REGULAR,
205
+ )
206
+ file_import.add_submodule_import("typing", "overload", ImportType.STDLIB)
207
+ file_import.add_submodule_import("typing", "Mapping", ImportType.STDLIB)
208
+ file_import.add_submodule_import("typing", "Any", ImportType.STDLIB)
209
+
210
+ for model in self.code_model.model_types:
211
+ file_import.merge(model.imports(is_operation_file=False))
212
+ for param in model.properties:
213
+ file_import.merge(param.imports())
214
+ return file_import
215
+
216
+ def declare_model(self, model: ModelType) -> str:
217
+ basename = "_model_base.Model"
218
+ if model.parents:
219
+ basename = ", ".join([cast(ModelType, m).name for m in model.parents])
220
+ if model.discriminator_value:
221
+ basename += f", discriminator='{model.discriminator_value}'"
222
+ return f"class {model.name}({basename}):{model.pylint_disable}"
223
+
224
+ @staticmethod
225
+ def get_properties_to_initialize(model: ModelType) -> List[Property]:
226
+ if model.parents:
227
+ properties_to_declare = [
228
+ p
229
+ for bm in model.parents
230
+ for p in model.properties
231
+ if p not in cast(ModelType, bm).properties
232
+ ]
233
+
234
+ else:
235
+ properties_to_declare = model.properties
236
+ if any(p for p in properties_to_declare if p.client_name == "_"):
237
+ raise ValueError("We do not generate anonymous properties")
238
+ return [
239
+ p
240
+ for p in properties_to_declare
241
+ if (not p.is_discriminator or p.is_polymorphic)
242
+ ]
243
+
244
+ @staticmethod
245
+ def declare_property(prop: Property) -> List[str]:
246
+ attribute_key = _ModelSerializer.escape_dot(prop.rest_api_name)
247
+ args = []
248
+ if prop.client_name != attribute_key:
249
+ args.append(f'name="{attribute_key}"')
250
+ if prop.readonly:
251
+ args.append("readonly=True")
252
+ if prop.client_default_value is not None:
253
+ args.append(f"default={prop.client_default_value_declaration}")
254
+
255
+ field = "rest_discriminator" if prop.is_discriminator else "rest_field"
256
+ ret = [
257
+ f"{prop.client_name}: {prop.type_annotation()} ="
258
+ f' {field}({", ".join(args)})'
259
+ ]
260
+ comment = prop.description(is_operation_file=False).replace('"', '\\"')
261
+ if comment:
262
+ ret.append(f'"""{comment}"""')
263
+ return ret