@autorest/python 6.26.7 → 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.
@@ -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({}, _model_base.Model)"
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
- # in this case, it's just an AnyType
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", "_deserialize", ImportType.LOCAL)
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){type_ignore}"
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" error = _deserialize({builder.default_error_deserialization}, response.json())")
1062
+ retval.append(f"{indent}error = _failsafe_deserialize({builder.default_error_deserialization}, response.json())")
1010
1063
  else:
1011
1064
  retval.append(
1012
- f" error = self._deserialize.failsafe_deserialize({builder.default_error_deserialization}, "
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
- if not 401 in builder.non_default_error_status_codes:
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 not 404 in builder.non_default_error_status_codes:
1156
+ if self._need_specific_error_map(404, builder):
1094
1157
  retval.append(" 404: ResourceNotFoundError,")
1095
- if not 409 in builder.non_default_error_status_codes:
1158
+ if self._need_specific_error_map(409, builder):
1096
1159
  retval.append(" 409: ResourceExistsError,")
1097
- if not 304 in builder.non_default_error_status_codes:
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autorest/python",
3
- "version": "6.26.7",
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.3.12",
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"