@autorest/python 6.26.7 → 6.27.1

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.
@@ -51,6 +51,7 @@ class BlackScriptPlugin(Plugin):
51
51
  return True
52
52
 
53
53
  def format_file(self, file: Path) -> None:
54
+ file_content = ""
54
55
  try:
55
56
  file_content = self.read_file(file)
56
57
  file_content = black.format_file_contents(file_content, fast=True, mode=_BLACK_MODE)
@@ -241,6 +241,23 @@ 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(
253
+ operation["exceptions"],
254
+ key=lambda x: (
255
+ 3
256
+ if x["statusCodes"][0] == "default"
257
+ else (1 if isinstance(x["statusCodes"][0], int) else 2)
258
+ ),
259
+ )
260
+
244
261
  @staticmethod
245
262
  def remove_cloud_errors(yaml_data: Dict[str, Any]) -> None:
246
263
  for client in yaml_data["clients"]:
@@ -316,6 +333,8 @@ class CodeGenerator(Plugin):
316
333
  options = self._build_code_model_options()
317
334
  yaml_data = self.get_yaml()
318
335
 
336
+ self.sort_exceptions(yaml_data)
337
+
319
338
  if self.options_retriever.azure_arm:
320
339
  self.remove_cloud_errors(yaml_data)
321
340
 
@@ -3,7 +3,7 @@
3
3
  # Licensed under the MIT License. See License.txt in the project root for
4
4
  # license information.
5
5
  # --------------------------------------------------------------------------
6
- from typing import Any
6
+ from typing import Any, List, Union
7
7
  from .imports import FileImport
8
8
  from .lro_operation import LROOperationBase
9
9
  from .paging_operation import PagingOperationBase
@@ -12,7 +12,7 @@ from .response import LROPagingResponse, Response
12
12
 
13
13
  class LROPagingOperation(LROOperationBase[LROPagingResponse], PagingOperationBase[LROPagingResponse]):
14
14
  @property
15
- def success_status_codes(self):
15
+ def success_status_codes(self) -> List[Union[int, str, List[int]]]:
16
16
  """The list of all successfull status code."""
17
17
  return [200]
18
18
 
@@ -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)
@@ -3,7 +3,6 @@
3
3
  # Licensed under the MIT License. See License.txt in the project root for
4
4
  # license information.
5
5
  # --------------------------------------------------------------------------
6
- from itertools import chain
7
6
  from typing import (
8
7
  Dict,
9
8
  List,
@@ -14,7 +13,6 @@ from typing import (
14
13
  Generic,
15
14
  TypeVar,
16
15
  cast,
17
- Sequence,
18
16
  )
19
17
 
20
18
  from .request_builder_parameter import RequestBuilderParameter
@@ -201,17 +199,13 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
201
199
  exception_schema = default_exceptions[0].type
202
200
  if isinstance(exception_schema, ModelType):
203
201
  return exception_schema.type_annotation(skip_quote=True)
204
- # in this case, it's just an AnyType
205
- return "'object'"
202
+ return None if self.code_model.options["models_mode"] == "dpg" else "'object'"
206
203
 
207
204
  @property
208
205
  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]))
206
+ return [
207
+ e for e in self.exceptions if "default" not in e.status_codes and e.type and isinstance(e.type, ModelType)
208
+ ]
215
209
 
216
210
  def _imports_shared(self, async_mode: bool, **kwargs: Any) -> FileImport: # pylint: disable=unused-argument
217
211
  file_import = FileImport(self.code_model)
@@ -344,19 +338,7 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
344
338
  file_import.add_submodule_import("exceptions", error, ImportType.SDKCORE)
345
339
  if self.code_model.options["azure_arm"]:
346
340
  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
341
  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
342
 
361
343
  if self.has_kwargs_to_pop_with_default(
362
344
  self.parameters.kwargs_to_pop, ParameterLocation.HEADER # type: ignore
@@ -436,7 +418,9 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
436
418
  elif any(r.type for r in self.responses):
437
419
  file_import.add_submodule_import(f"{relative_path}_model_base", "_deserialize", ImportType.LOCAL)
438
420
  if self.default_error_deserialization or self.non_default_errors:
439
- file_import.add_submodule_import(f"{relative_path}_model_base", "_deserialize", ImportType.LOCAL)
421
+ file_import.add_submodule_import(
422
+ f"{relative_path}_model_base", "_failsafe_deserialize", ImportType.LOCAL
423
+ )
440
424
  return file_import
441
425
 
442
426
  def get_response_from_status(self, status_code: Optional[Union[str, int]]) -> ResponseType:
@@ -446,7 +430,7 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
446
430
  raise ValueError(f"Incorrect status code {status_code}, operation {self.name}") from exc
447
431
 
448
432
  @property
449
- def success_status_codes(self) -> Sequence[Union[str, int]]:
433
+ def success_status_codes(self) -> List[Union[int, str, List[int]]]:
450
434
  """The list of all successfull status code."""
451
435
  return sorted([code for response in self.responses for code in response.status_codes])
452
436
 
@@ -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,82 @@ 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
- retval.append(
1004
- f" map_error(status_code=response.status_code, response=response, error_map=error_map){type_ignore}"
1005
- )
994
+ retval.append(" map_error(status_code=response.status_code, response=response, error_map=error_map)")
1006
995
  error_model = ""
996
+ if builder.non_default_errors and self.code_model.options["models_mode"]:
997
+ error_model = ", model=error"
998
+ condition = "if"
999
+ retval.append(" error = None")
1000
+ for e in builder.non_default_errors:
1001
+ # single status code
1002
+ if isinstance(e.status_codes[0], int):
1003
+ for status_code in e.status_codes:
1004
+ retval.append(f" {condition} response.status_code == {status_code}:")
1005
+ if self.code_model.options["models_mode"] == "dpg":
1006
+ retval.append(f" error = _failsafe_deserialize({e.type.type_annotation(is_operation_file=True, skip_quote=True)}, response.json())") # type: ignore # pylint: disable=line-too-long
1007
+ else:
1008
+ retval.append(
1009
+ f" error = self._deserialize.failsafe_deserialize({e.type.type_annotation(is_operation_file=True, skip_quote=True)}, " # type: ignore # pylint: disable=line-too-long
1010
+ "pipeline_response)"
1011
+ )
1012
+ # add build-in error type
1013
+ # TODO: we should decide whether need to this wrapper for customized error type
1014
+ if status_code == 401:
1015
+ retval.append(
1016
+ " raise ClientAuthenticationError(response=response{}{})".format(
1017
+ error_model,
1018
+ (", error_format=ARMErrorFormat" if self.code_model.options["azure_arm"] else ""),
1019
+ )
1020
+ )
1021
+ elif status_code == 404:
1022
+ retval.append(
1023
+ " raise ResourceNotFoundError(response=response{}{})".format(
1024
+ error_model,
1025
+ (", error_format=ARMErrorFormat" if self.code_model.options["azure_arm"] else ""),
1026
+ )
1027
+ )
1028
+ elif status_code == 409:
1029
+ retval.append(
1030
+ " raise ResourceExistsError(response=response{}{})".format(
1031
+ error_model,
1032
+ (", error_format=ARMErrorFormat" if self.code_model.options["azure_arm"] else ""),
1033
+ )
1034
+ )
1035
+ elif status_code == 304:
1036
+ retval.append(
1037
+ " raise ResourceNotModifiedError(response=response{}{})".format(
1038
+ error_model,
1039
+ (", error_format=ARMErrorFormat" if self.code_model.options["azure_arm"] else ""),
1040
+ )
1041
+ )
1042
+ # ranged status code only exist in typespec and will not have multiple status codes
1043
+ else:
1044
+ retval.append(
1045
+ f" {condition} {e.status_codes[0][0]} <= response.status_code <= {e.status_codes[0][1]}:"
1046
+ )
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())") # type: ignore # pylint: disable=line-too-long
1049
+ else:
1050
+ retval.append(
1051
+ f" error = self._deserialize.failsafe_deserialize({e.type.type_annotation(is_operation_file=True, skip_quote=True)}, " # type: ignore # pylint: disable=line-too-long
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(
1063
+ f"{indent}error = _failsafe_deserialize({builder.default_error_deserialization}, response.json())"
1064
+ )
1010
1065
  else:
1011
1066
  retval.append(
1012
- f" error = self._deserialize.failsafe_deserialize({builder.default_error_deserialization}, "
1067
+ f"{indent}error = self._deserialize.failsafe_deserialize({builder.default_error_deserialization}, "
1013
1068
  "pipeline_response)"
1014
1069
  )
1015
- error_model = ", model=error"
1016
1070
  retval.append(
1017
1071
  " raise HttpResponseError(response=response{}{})".format(
1018
1072
  error_model,
@@ -1034,7 +1088,7 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
1034
1088
  if len(builder.responses) > 1:
1035
1089
  status_codes, res_headers, res_deserialization = [], [], []
1036
1090
  for status_code in builder.success_status_codes:
1037
- response = builder.get_response_from_status(status_code)
1091
+ response = builder.get_response_from_status(status_code) # type: ignore
1038
1092
  if response.headers or response.type:
1039
1093
  status_codes.append(status_code)
1040
1094
  res_headers.append(self.response_headers(response))
@@ -1085,60 +1139,31 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
1085
1139
  retval.append("return 200 <= response.status_code <= 299")
1086
1140
  return retval
1087
1141
 
1142
+ def _need_specific_error_map(self, code: int, builder: OperationType) -> bool:
1143
+ for non_default_error in builder.non_default_errors:
1144
+ # single status code
1145
+ if code in non_default_error.status_codes:
1146
+ return False
1147
+ # ranged status code
1148
+ if (
1149
+ isinstance(non_default_error.status_codes[0], list)
1150
+ and non_default_error.status_codes[0][0] <= code <= non_default_error.status_codes[0][1]
1151
+ ):
1152
+ return False
1153
+ return True
1154
+
1088
1155
  def error_map(self, builder: OperationType) -> List[str]:
1089
1156
  retval = ["error_map: MutableMapping = {"]
1090
- if builder.non_default_errors:
1091
- if not 401 in builder.non_default_error_status_codes:
1157
+ if builder.non_default_errors and self.code_model.options["models_mode"]:
1158
+ # TODO: we should decide whether to add the build-in error map when there is a customized default error type
1159
+ if self._need_specific_error_map(401, builder):
1092
1160
  retval.append(" 401: ClientAuthenticationError,")
1093
- if not 404 in builder.non_default_error_status_codes:
1161
+ if self._need_specific_error_map(404, builder):
1094
1162
  retval.append(" 404: ResourceNotFoundError,")
1095
- if not 409 in builder.non_default_error_status_codes:
1163
+ if self._need_specific_error_map(409, builder):
1096
1164
  retval.append(" 409: ResourceExistsError,")
1097
- if not 304 in builder.non_default_error_status_codes:
1165
+ if self._need_specific_error_map(304, builder):
1098
1166
  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
1167
  else:
1143
1168
  retval.append(
1144
1169
  " 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.1",
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.3",
23
23
  "@autorest/system-requirements": "~1.0.2",
24
24
  "fs-extra": "~11.2.0",
25
25
  "tsx": "~4.19.1"