@autorest/python 6.26.6 → 6.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/generator/pygen/codegen/__init__.py +12 -0
- package/generator/pygen/codegen/models/model_type.py +1 -1
- package/generator/pygen/codegen/models/operation.py +4 -21
- package/generator/pygen/codegen/models/primitive_types.py +1 -1
- package/generator/pygen/codegen/models/response.py +1 -1
- package/generator/pygen/codegen/serializers/builder_serializer.py +81 -61
- package/generator/pygen/codegen/templates/model_base.py.jinja2 +17 -0
- package/generator/pygen/codegen/templates/serialization.py.jinja2 +3 -3
- package/package.json +2 -2
- package/scripts/__pycache__/venvtools.cpython-310.pyc +0 -0
- package/scripts/eng/pylintrc +1 -1
|
@@ -241,6 +241,16 @@ class CodeGenerator(Plugin):
|
|
|
241
241
|
if not self.options_retriever.is_azure_flavor and self.options_retriever.tracing:
|
|
242
242
|
raise ValueError("Can only have tracing turned on for Azure SDKs.")
|
|
243
243
|
|
|
244
|
+
@staticmethod
|
|
245
|
+
def sort_exceptions(yaml_data: Dict[str, Any]) -> None:
|
|
246
|
+
for client in yaml_data["clients"]:
|
|
247
|
+
for group in client["operationGroups"]:
|
|
248
|
+
for operation in group["operations"]:
|
|
249
|
+
if not operation.get("exceptions"):
|
|
250
|
+
continue
|
|
251
|
+
# sort exceptions by status code, first single status code, then range, then default
|
|
252
|
+
operation["exceptions"] = sorted(operation["exceptions"], key=lambda x: 3 if x["statusCodes"][0] == "default" else (1 if isinstance(x["statusCodes"][0], int) else 2))
|
|
253
|
+
|
|
244
254
|
@staticmethod
|
|
245
255
|
def remove_cloud_errors(yaml_data: Dict[str, Any]) -> None:
|
|
246
256
|
for client in yaml_data["clients"]:
|
|
@@ -315,6 +325,8 @@ class CodeGenerator(Plugin):
|
|
|
315
325
|
self._validate_code_model_options()
|
|
316
326
|
options = self._build_code_model_options()
|
|
317
327
|
yaml_data = self.get_yaml()
|
|
328
|
+
|
|
329
|
+
self.sort_exceptions(yaml_data)
|
|
318
330
|
|
|
319
331
|
if self.options_retriever.azure_arm:
|
|
320
332
|
self.remove_cloud_errors(yaml_data)
|
|
@@ -348,7 +348,7 @@ class DPGModelType(GeneratedModelType):
|
|
|
348
348
|
|
|
349
349
|
@property
|
|
350
350
|
def instance_check_template(self) -> str:
|
|
351
|
-
return "isinstance({},
|
|
351
|
+
return "isinstance({}, " + f"_models.{self.name})"
|
|
352
352
|
|
|
353
353
|
def imports(self, **kwargs: Any) -> FileImport:
|
|
354
354
|
file_import = super().imports(**kwargs)
|
|
@@ -9,6 +9,7 @@ from typing import (
|
|
|
9
9
|
List,
|
|
10
10
|
Any,
|
|
11
11
|
Optional,
|
|
12
|
+
Tuple,
|
|
12
13
|
Union,
|
|
13
14
|
TYPE_CHECKING,
|
|
14
15
|
Generic,
|
|
@@ -201,17 +202,11 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
|
|
|
201
202
|
exception_schema = default_exceptions[0].type
|
|
202
203
|
if isinstance(exception_schema, ModelType):
|
|
203
204
|
return exception_schema.type_annotation(skip_quote=True)
|
|
204
|
-
|
|
205
|
-
return "'object'"
|
|
205
|
+
return None if self.code_model.options["models_mode"] == "dpg" else "'object'"
|
|
206
206
|
|
|
207
207
|
@property
|
|
208
208
|
def non_default_errors(self) -> List[Response]:
|
|
209
|
-
return [e for e in self.exceptions if "default" not in e.status_codes]
|
|
210
|
-
|
|
211
|
-
@property
|
|
212
|
-
def non_default_error_status_codes(self) -> List[Union[str, int]]:
|
|
213
|
-
"""Actually returns all of the status codes from exceptions (besides default)"""
|
|
214
|
-
return list(chain.from_iterable([error.status_codes for error in self.non_default_errors]))
|
|
209
|
+
return [e for e in self.exceptions if "default" not in e.status_codes and e.type and isinstance(e.type, ModelType)]
|
|
215
210
|
|
|
216
211
|
def _imports_shared(self, async_mode: bool, **kwargs: Any) -> FileImport: # pylint: disable=unused-argument
|
|
217
212
|
file_import = FileImport(self.code_model)
|
|
@@ -344,19 +339,7 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
|
|
|
344
339
|
file_import.add_submodule_import("exceptions", error, ImportType.SDKCORE)
|
|
345
340
|
if self.code_model.options["azure_arm"]:
|
|
346
341
|
file_import.add_submodule_import("azure.mgmt.core.exceptions", "ARMErrorFormat", ImportType.SDKCORE)
|
|
347
|
-
if self.non_default_errors:
|
|
348
|
-
file_import.add_submodule_import(
|
|
349
|
-
"typing",
|
|
350
|
-
"Type",
|
|
351
|
-
ImportType.STDLIB,
|
|
352
|
-
)
|
|
353
342
|
file_import.add_mutable_mapping_import()
|
|
354
|
-
if self.non_default_error_status_codes:
|
|
355
|
-
file_import.add_submodule_import(
|
|
356
|
-
"typing",
|
|
357
|
-
"cast",
|
|
358
|
-
ImportType.STDLIB,
|
|
359
|
-
)
|
|
360
343
|
|
|
361
344
|
if self.has_kwargs_to_pop_with_default(
|
|
362
345
|
self.parameters.kwargs_to_pop, ParameterLocation.HEADER # type: ignore
|
|
@@ -436,7 +419,7 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
|
|
|
436
419
|
elif any(r.type for r in self.responses):
|
|
437
420
|
file_import.add_submodule_import(f"{relative_path}_model_base", "_deserialize", ImportType.LOCAL)
|
|
438
421
|
if self.default_error_deserialization or self.non_default_errors:
|
|
439
|
-
file_import.add_submodule_import(f"{relative_path}_model_base", "
|
|
422
|
+
file_import.add_submodule_import(f"{relative_path}_model_base", "_failsafe_deserialize", ImportType.LOCAL)
|
|
440
423
|
return file_import
|
|
441
424
|
|
|
442
425
|
def get_response_from_status(self, status_code: Optional[Union[str, int]]) -> ResponseType:
|
|
@@ -54,7 +54,7 @@ class Response(BaseModel):
|
|
|
54
54
|
type: Optional[BaseType] = None,
|
|
55
55
|
) -> None:
|
|
56
56
|
super().__init__(yaml_data=yaml_data, code_model=code_model)
|
|
57
|
-
self.status_codes: List[Union[int, str]] = yaml_data["statusCodes"]
|
|
57
|
+
self.status_codes: List[Union[int, str, List[int]]] = yaml_data["statusCodes"]
|
|
58
58
|
self.headers = headers or []
|
|
59
59
|
self.type = type
|
|
60
60
|
self.nullable = yaml_data.get("nullable")
|
|
@@ -61,14 +61,6 @@ def _all_same(data: List[List[str]]) -> bool:
|
|
|
61
61
|
return len(data) > 1 and all(sorted(data[0]) == sorted(data[i]) for i in range(1, len(data)))
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
def _need_type_ignore(builder: OperationType) -> bool:
|
|
65
|
-
for e in builder.non_default_errors:
|
|
66
|
-
for status_code in e.status_codes:
|
|
67
|
-
if status_code in (401, 404, 409, 304):
|
|
68
|
-
return True
|
|
69
|
-
return False
|
|
70
|
-
|
|
71
|
-
|
|
72
64
|
def _xml_config(send_xml: bool, content_types: List[str]) -> str:
|
|
73
65
|
if not (send_xml and "xml" in str(content_types)):
|
|
74
66
|
return ""
|
|
@@ -999,20 +991,80 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
|
|
|
999
991
|
elif isinstance(builder.stream_value, str): # _stream is not sure, so we need to judge it
|
|
1000
992
|
retval.append(" if _stream:")
|
|
1001
993
|
retval.extend([f" {l}" for l in response_read])
|
|
1002
|
-
type_ignore = " # type: ignore" if _need_type_ignore(builder) else ""
|
|
1003
994
|
retval.append(
|
|
1004
|
-
f" map_error(status_code=response.status_code, response=response, error_map=error_map)
|
|
995
|
+
f" map_error(status_code=response.status_code, response=response, error_map=error_map)"
|
|
1005
996
|
)
|
|
1006
997
|
error_model = ""
|
|
998
|
+
if builder.non_default_errors and self.code_model.options["models_mode"]:
|
|
999
|
+
error_model = ", model=error"
|
|
1000
|
+
condition = "if"
|
|
1001
|
+
retval.append(" error = None")
|
|
1002
|
+
for e in builder.non_default_errors:
|
|
1003
|
+
# single status code
|
|
1004
|
+
if isinstance(e.status_codes[0], int):
|
|
1005
|
+
for status_code in e.status_codes:
|
|
1006
|
+
retval.append(f" {condition} response.status_code == {status_code}:")
|
|
1007
|
+
if self.code_model.options["models_mode"] == "dpg":
|
|
1008
|
+
retval.append(f" error = _failsafe_deserialize({e.type.type_annotation(is_operation_file=True, skip_quote=True)}, response.json())")
|
|
1009
|
+
else:
|
|
1010
|
+
retval.append(
|
|
1011
|
+
f" error = self._deserialize.failsafe_deserialize({e.type.type_annotation(is_operation_file=True, skip_quote=True)}, "
|
|
1012
|
+
"pipeline_response)"
|
|
1013
|
+
)
|
|
1014
|
+
# add build-in error type
|
|
1015
|
+
# TODO: we should decide whether need to this wrapper for customized error type
|
|
1016
|
+
if status_code == 401:
|
|
1017
|
+
retval.append(
|
|
1018
|
+
" raise ClientAuthenticationError(response=response{}{})".format(
|
|
1019
|
+
error_model,
|
|
1020
|
+
(", error_format=ARMErrorFormat" if self.code_model.options["azure_arm"] else ""),
|
|
1021
|
+
)
|
|
1022
|
+
)
|
|
1023
|
+
elif status_code == 404:
|
|
1024
|
+
retval.append(
|
|
1025
|
+
" raise ResourceNotFoundError(response=response{}{})".format(
|
|
1026
|
+
error_model,
|
|
1027
|
+
(", error_format=ARMErrorFormat" if self.code_model.options["azure_arm"] else ""),
|
|
1028
|
+
)
|
|
1029
|
+
)
|
|
1030
|
+
elif status_code == 409:
|
|
1031
|
+
retval.append(
|
|
1032
|
+
" raise ResourceExistsError(response=response{}{})".format(
|
|
1033
|
+
error_model,
|
|
1034
|
+
(", error_format=ARMErrorFormat" if self.code_model.options["azure_arm"] else ""),
|
|
1035
|
+
)
|
|
1036
|
+
)
|
|
1037
|
+
elif status_code == 304:
|
|
1038
|
+
retval.append(
|
|
1039
|
+
" raise ResourceNotModifiedError(response=response{}{})".format(
|
|
1040
|
+
error_model,
|
|
1041
|
+
(", error_format=ARMErrorFormat" if self.code_model.options["azure_arm"] else ""),
|
|
1042
|
+
)
|
|
1043
|
+
)
|
|
1044
|
+
# ranged status code only exist in typespec and will not have multiple status codes
|
|
1045
|
+
else:
|
|
1046
|
+
retval.append(f" {condition} {e.status_codes[0][0]} <= response.status_code <= {e.status_codes[0][1]}:")
|
|
1047
|
+
if self.code_model.options["models_mode"] == "dpg":
|
|
1048
|
+
retval.append(f" error = _failsafe_deserialize({e.type.type_annotation(is_operation_file=True, skip_quote=True)}, response.json())")
|
|
1049
|
+
else:
|
|
1050
|
+
retval.append(
|
|
1051
|
+
f" error = self._deserialize.failsafe_deserialize({e.type.type_annotation(is_operation_file=True, skip_quote=True)}, "
|
|
1052
|
+
"pipeline_response)"
|
|
1053
|
+
)
|
|
1054
|
+
condition = "elif"
|
|
1055
|
+
# default error handling
|
|
1007
1056
|
if builder.default_error_deserialization and self.code_model.options["models_mode"]:
|
|
1057
|
+
error_model = ", model=error"
|
|
1058
|
+
indent = " " if builder.non_default_errors else " "
|
|
1059
|
+
if builder.non_default_errors:
|
|
1060
|
+
retval.append(" else:")
|
|
1008
1061
|
if self.code_model.options["models_mode"] == "dpg":
|
|
1009
|
-
retval.append(f"
|
|
1062
|
+
retval.append(f"{indent}error = _failsafe_deserialize({builder.default_error_deserialization}, response.json())")
|
|
1010
1063
|
else:
|
|
1011
1064
|
retval.append(
|
|
1012
|
-
f"
|
|
1065
|
+
f"{indent}error = self._deserialize.failsafe_deserialize({builder.default_error_deserialization}, "
|
|
1013
1066
|
"pipeline_response)"
|
|
1014
1067
|
)
|
|
1015
|
-
error_model = ", model=error"
|
|
1016
1068
|
retval.append(
|
|
1017
1069
|
" raise HttpResponseError(response=response{}{})".format(
|
|
1018
1070
|
error_model,
|
|
@@ -1085,60 +1137,28 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
|
|
|
1085
1137
|
retval.append("return 200 <= response.status_code <= 299")
|
|
1086
1138
|
return retval
|
|
1087
1139
|
|
|
1140
|
+
def _need_specific_error_map(self, code: int, builder: OperationType) -> bool:
|
|
1141
|
+
for non_default_error in builder.non_default_errors:
|
|
1142
|
+
# single status code
|
|
1143
|
+
if code in non_default_error.status_codes:
|
|
1144
|
+
return False
|
|
1145
|
+
# ranged status code
|
|
1146
|
+
if isinstance(non_default_error.status_codes[0], list) and non_default_error.status_codes[0][0] <= code <= non_default_error.status_codes[0][1]:
|
|
1147
|
+
return False
|
|
1148
|
+
return True
|
|
1149
|
+
|
|
1088
1150
|
def error_map(self, builder: OperationType) -> List[str]:
|
|
1089
1151
|
retval = ["error_map: MutableMapping = {"]
|
|
1090
|
-
if builder.non_default_errors:
|
|
1091
|
-
|
|
1152
|
+
if builder.non_default_errors and self.code_model.options["models_mode"]:
|
|
1153
|
+
# TODO: we should decide whether to add the build-in error map when there is a customized default error type
|
|
1154
|
+
if self._need_specific_error_map(401, builder):
|
|
1092
1155
|
retval.append(" 401: ClientAuthenticationError,")
|
|
1093
|
-
if
|
|
1156
|
+
if self._need_specific_error_map(404, builder):
|
|
1094
1157
|
retval.append(" 404: ResourceNotFoundError,")
|
|
1095
|
-
if
|
|
1158
|
+
if self._need_specific_error_map(409, builder):
|
|
1096
1159
|
retval.append(" 409: ResourceExistsError,")
|
|
1097
|
-
if
|
|
1160
|
+
if self._need_specific_error_map(304, builder):
|
|
1098
1161
|
retval.append(" 304: ResourceNotModifiedError,")
|
|
1099
|
-
for e in builder.non_default_errors:
|
|
1100
|
-
error_model_str = ""
|
|
1101
|
-
if isinstance(e.type, ModelType):
|
|
1102
|
-
if self.code_model.options["models_mode"] == "msrest":
|
|
1103
|
-
error_model_str = (
|
|
1104
|
-
f", model=self._deserialize(" f"_models.{e.type.serialization_type}, response)"
|
|
1105
|
-
)
|
|
1106
|
-
elif self.code_model.options["models_mode"] == "dpg":
|
|
1107
|
-
error_model_str = f", model=_deserialize(_models.{e.type.name}, response.json())"
|
|
1108
|
-
error_format_str = ", error_format=ARMErrorFormat" if self.code_model.options["azure_arm"] else ""
|
|
1109
|
-
for status_code in e.status_codes:
|
|
1110
|
-
if status_code == 401:
|
|
1111
|
-
retval.append(
|
|
1112
|
-
" 401: cast(Type[HttpResponseError], "
|
|
1113
|
-
"lambda response: ClientAuthenticationError(response=response"
|
|
1114
|
-
f"{error_model_str}{error_format_str})),"
|
|
1115
|
-
)
|
|
1116
|
-
elif status_code == 404:
|
|
1117
|
-
retval.append(
|
|
1118
|
-
" 404: cast(Type[HttpResponseError], "
|
|
1119
|
-
"lambda response: ResourceNotFoundError(response=response"
|
|
1120
|
-
f"{error_model_str}{error_format_str})),"
|
|
1121
|
-
)
|
|
1122
|
-
elif status_code == 409:
|
|
1123
|
-
retval.append(
|
|
1124
|
-
" 409: cast(Type[HttpResponseError], "
|
|
1125
|
-
"lambda response: ResourceExistsError(response=response"
|
|
1126
|
-
f"{error_model_str}{error_format_str})),"
|
|
1127
|
-
)
|
|
1128
|
-
elif status_code == 304:
|
|
1129
|
-
retval.append(
|
|
1130
|
-
" 304: cast(Type[HttpResponseError], "
|
|
1131
|
-
"lambda response: ResourceNotModifiedError(response=response"
|
|
1132
|
-
f"{error_model_str}{error_format_str})),"
|
|
1133
|
-
)
|
|
1134
|
-
elif not error_model_str and not error_format_str:
|
|
1135
|
-
retval.append(f" {status_code}: HttpResponseError,")
|
|
1136
|
-
else:
|
|
1137
|
-
retval.append(
|
|
1138
|
-
f" {status_code}: cast(Type[HttpResponseError], "
|
|
1139
|
-
"lambda response: HttpResponseError(response=response"
|
|
1140
|
-
f"{error_model_str}{error_format_str})),"
|
|
1141
|
-
)
|
|
1142
1162
|
else:
|
|
1143
1163
|
retval.append(
|
|
1144
1164
|
" 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, "
|
|
@@ -892,6 +892,23 @@ def _deserialize(
|
|
|
892
892
|
return _deserialize_with_callable(deserializer, value)
|
|
893
893
|
|
|
894
894
|
|
|
895
|
+
def _failsafe_deserialize(
|
|
896
|
+
deserializer: typing.Any,
|
|
897
|
+
value: typing.Any,
|
|
898
|
+
module: typing.Optional[str] = None,
|
|
899
|
+
rf: typing.Optional["_RestField"] = None,
|
|
900
|
+
format: typing.Optional[str] = None,
|
|
901
|
+
) -> typing.Any:
|
|
902
|
+
try:
|
|
903
|
+
return _deserialize(deserializer, value, module, rf, format)
|
|
904
|
+
except DeserializationError:
|
|
905
|
+
_LOGGER.warning(
|
|
906
|
+
"Ran into a deserialization error. Ignoring since this is failsafe deserialization",
|
|
907
|
+
exc_info=True
|
|
908
|
+
)
|
|
909
|
+
return None
|
|
910
|
+
|
|
911
|
+
|
|
895
912
|
class _RestField:
|
|
896
913
|
def __init__(
|
|
897
914
|
self,
|
|
@@ -309,7 +309,7 @@ def _create_xml_node(tag, prefix=None, ns=None):
|
|
|
309
309
|
return ET.Element(tag)
|
|
310
310
|
|
|
311
311
|
|
|
312
|
-
class Model
|
|
312
|
+
class Model:
|
|
313
313
|
"""Mixin for all client request body/response body models to support
|
|
314
314
|
serialization and deserialization.
|
|
315
315
|
"""
|
|
@@ -562,7 +562,7 @@ def _decode_attribute_map_key(key):
|
|
|
562
562
|
return key.replace("\\.", ".")
|
|
563
563
|
|
|
564
564
|
|
|
565
|
-
class Serializer
|
|
565
|
+
class Serializer: # pylint: disable=too-many-public-methods
|
|
566
566
|
"""Request object model serializer."""
|
|
567
567
|
|
|
568
568
|
basic_types = {str: "str", int: "int", bool: "bool", float: "float"}
|
|
@@ -1440,7 +1440,7 @@ def xml_key_extractor(attr, attr_desc, data): # pylint: disable=unused-argument
|
|
|
1440
1440
|
return children[0]
|
|
1441
1441
|
|
|
1442
1442
|
|
|
1443
|
-
class Deserializer
|
|
1443
|
+
class Deserializer:
|
|
1444
1444
|
"""Response object model deserializer.
|
|
1445
1445
|
|
|
1446
1446
|
:param dict classes: Class type dictionary for deserializing complex types.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autorest/python",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.27.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.
|
|
22
|
+
"@typespec/http-client-python": "~0.4.2",
|
|
23
23
|
"@autorest/system-requirements": "~1.0.2",
|
|
24
24
|
"fs-extra": "~11.2.0",
|
|
25
25
|
"tsx": "~4.19.1"
|
|
Binary file
|
package/scripts/eng/pylintrc
CHANGED
|
@@ -17,7 +17,7 @@ enable=useless-suppression
|
|
|
17
17
|
# too-many-arguments: Due to the nature of the CLI many commands have large arguments set which reflect in large arguments set in corresponding methods.
|
|
18
18
|
# too-many-lines: Due to code generation many files end up with too many lines.
|
|
19
19
|
# Let's black deal with bad-continuation
|
|
20
|
-
disable=
|
|
20
|
+
disable=missing-docstring,locally-disabled,fixme,cyclic-import,too-many-arguments,invalid-name,duplicate-code,too-few-public-methods,consider-using-f-string,super-with-arguments,redefined-builtin,import-outside-toplevel,client-suffix-needed,unnecessary-dunder-call,unnecessary-ellipsis,disallowed-name,consider-using-max-builtin,unknown-option-value
|
|
21
21
|
|
|
22
22
|
[FORMAT]
|
|
23
23
|
max-line-length=120
|