@autorest/python 6.47.0 → 6.48.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.
@@ -448,7 +448,19 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
448
448
  elif self.need_deserialize:
449
449
  file_import.add_submodule_import(relative_path, "_deserialize", ImportType.LOCAL)
450
450
  if self.default_error_deserialization(serialize_namespace) or self.non_default_errors:
451
- file_import.add_submodule_import(relative_path, "_failsafe_deserialize", ImportType.LOCAL)
451
+ xml_non_default_errors = any(
452
+ xml_serializable(str(e.default_content_type)) for e in self.non_default_errors
453
+ )
454
+ try:
455
+ default_error = next(e for e in self.exceptions if "default" in e.status_codes and e.type)
456
+ except StopIteration:
457
+ default_error = None
458
+ if xml_non_default_errors or (
459
+ default_error and xml_serializable(str(default_error.default_content_type))
460
+ ):
461
+ file_import.add_submodule_import(relative_path, "_failsafe_deserialize_xml", ImportType.LOCAL)
462
+ else:
463
+ file_import.add_submodule_import(relative_path, "_failsafe_deserialize", ImportType.LOCAL)
452
464
  return file_import
453
465
 
454
466
  def get_response_from_status(self, status_code: Optional[Union[str, int]]) -> ResponseType:
@@ -73,6 +73,13 @@ class PagingOperationBase(OperationBase[PagingResponseType]):
73
73
  def next_variable_name(self) -> str:
74
74
  return "_continuation_token" if self.has_continuation_token else "next_link"
75
75
 
76
+ @property
77
+ def is_xml_paging(self) -> bool:
78
+ try:
79
+ return bool(self.responses[0].item_type.xml_metadata)
80
+ except KeyError:
81
+ return False
82
+
76
83
  def _get_attr_name(self, wire_name: str) -> str:
77
84
  response_type = self.responses[0].type
78
85
  if not response_type:
@@ -176,6 +183,9 @@ class PagingOperationBase(OperationBase[PagingResponseType]):
176
183
  file_import.merge(self.item_type.imports(**kwargs))
177
184
  if self.default_error_deserialization(serialize_namespace) or self.need_deserialize:
178
185
  file_import.add_submodule_import(relative_path, "_deserialize", ImportType.LOCAL)
186
+ if self.is_xml_paging:
187
+ file_import.add_submodule_import("xml.etree", "ElementTree", ImportType.STDLIB, alias="ET")
188
+ file_import.add_submodule_import(relative_path, "_convert_element", ImportType.LOCAL)
179
189
  return file_import
180
190
 
181
191
 
@@ -1045,7 +1045,9 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
1045
1045
  retval.extend([f" {l}" for l in response_read])
1046
1046
  retval.append(" map_error(status_code=response.status_code, response=response, error_map=error_map)")
1047
1047
  error_model = ""
1048
- if builder.non_default_errors and self.code_model.options["models-mode"]:
1048
+ if ( # pylint: disable=too-many-nested-blocks
1049
+ builder.non_default_errors and self.code_model.options["models-mode"]
1050
+ ):
1049
1051
  error_model = ", model=error"
1050
1052
  condition = "if"
1051
1053
  retval.append(" error = None")
@@ -1062,9 +1064,11 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
1062
1064
  is_operation_file=True, skip_quote=True, serialize_namespace=self.serialize_namespace
1063
1065
  )
1064
1066
  if self.code_model.options["models-mode"] == "dpg":
1065
- retval.append(
1066
- f" error = _failsafe_deserialize({type_annotation},{pylint_disable}\n response)"
1067
- )
1067
+ if xml_serializable(str(e.default_content_type)):
1068
+ fn = "_failsafe_deserialize_xml"
1069
+ else:
1070
+ fn = "_failsafe_deserialize"
1071
+ retval.append(f" error = {fn}({type_annotation},{pylint_disable}\n response)")
1068
1072
  else:
1069
1073
  retval.extend(
1070
1074
  [
@@ -1130,9 +1134,14 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
1130
1134
  if builder.non_default_errors:
1131
1135
  retval.append(" else:")
1132
1136
  if self.code_model.options["models-mode"] == "dpg":
1137
+ default_exception = next(e for e in builder.exceptions if "default" in e.status_codes and e.type)
1138
+ if xml_serializable(str(default_exception.default_content_type)):
1139
+ fn = "_failsafe_deserialize_xml"
1140
+ else:
1141
+ fn = "_failsafe_deserialize"
1133
1142
  retval.extend(
1134
1143
  [
1135
- f"{indent}error = _failsafe_deserialize(",
1144
+ f"{indent}error = {fn}(",
1136
1145
  f"{indent} {default_error_deserialization}",
1137
1146
  f"{indent} response,",
1138
1147
  f"{indent})",
@@ -1372,10 +1381,15 @@ class _PagingOperationSerializer(_OperationSerializer[PagingOperationType]):
1372
1381
  def _function_def(self) -> str:
1373
1382
  return "def"
1374
1383
 
1375
- def _extract_data_callback(self, builder: PagingOperationType) -> list[str]: # pylint: disable=too-many-statements
1384
+ def _extract_data_callback( # pylint: disable=too-many-statements,too-many-branches
1385
+ self, builder: PagingOperationType
1386
+ ) -> list[str]:
1376
1387
  retval = [f"{'async ' if self.async_mode else ''}def extract_data(pipeline_response):"]
1377
1388
  response = builder.responses[0]
1378
- deserialized = "pipeline_response.http_response.json()"
1389
+ if builder.is_xml_paging:
1390
+ deserialized = "ET.fromstring(pipeline_response.http_response.text())"
1391
+ else:
1392
+ deserialized = "pipeline_response.http_response.json()"
1379
1393
  if self.code_model.options["models-mode"] == "msrest":
1380
1394
  suffix = ".http_response" if hasattr(builder, "initial_operation") else ""
1381
1395
  deserialize_type = response.serialization_type(serialize_namespace=self.serialize_namespace)
@@ -1395,6 +1409,10 @@ class _PagingOperationSerializer(_OperationSerializer[PagingOperationType]):
1395
1409
  item_name = builder.item_name
1396
1410
  if self.code_model.options["models-mode"] == "msrest":
1397
1411
  access = f".{item_name}"
1412
+ elif builder.is_xml_paging:
1413
+ # For XML, use .find() to navigate the element tree
1414
+ item_name_array = item_name.split(".")
1415
+ access = "".join([f'.find("{i}")' for i in item_name_array])
1398
1416
  else:
1399
1417
  item_name_array = item_name.split(".")
1400
1418
  access = (
@@ -1412,11 +1430,17 @@ class _PagingOperationSerializer(_OperationSerializer[PagingOperationType]):
1412
1430
  retval.append(" if cls:")
1413
1431
  retval.append(" list_of_elem = cls(list_of_elem) # type: ignore")
1414
1432
 
1433
+ cont_token_expr: Optional[str] = None # For XML, we need to extract find() result first
1415
1434
  if builder.has_continuation_token:
1416
1435
  location = builder.continuation_token.get("output", {}).get("location")
1417
1436
  wire_name = builder.continuation_token.get("output", {}).get("wireName") or ""
1418
1437
  if location == "header":
1419
1438
  cont_token_property = f'pipeline_response.http_response.headers.get("{wire_name}") or None'
1439
+ elif builder.is_xml_paging:
1440
+ wire_name_array = wire_name.split(".")
1441
+ wire_name_call = "".join([f'.find("{i}")' for i in wire_name_array])
1442
+ cont_token_expr = f"deserialized{wire_name_call}"
1443
+ cont_token_property = "_cont_token_elem.text if _cont_token_elem is not None else None"
1420
1444
  else:
1421
1445
  wire_name_array = wire_name.split(".")
1422
1446
  wire_name_call = (
@@ -1429,6 +1453,11 @@ class _PagingOperationSerializer(_OperationSerializer[PagingOperationType]):
1429
1453
  cont_token_property = "None"
1430
1454
  elif self.code_model.options["models-mode"] == "msrest":
1431
1455
  cont_token_property = f"deserialized.{next_link_name} or None"
1456
+ elif builder.is_xml_paging:
1457
+ next_link_name_array = next_link_name.split(".")
1458
+ access = "".join([f'.find("{i}")' for i in next_link_name_array])
1459
+ cont_token_expr = f"deserialized{access}"
1460
+ cont_token_property = "_cont_token_elem.text if _cont_token_elem is not None else None"
1432
1461
  elif builder.next_link_is_nested:
1433
1462
  next_link_name_array = next_link_name.split(".")
1434
1463
  access = (
@@ -1439,6 +1468,8 @@ class _PagingOperationSerializer(_OperationSerializer[PagingOperationType]):
1439
1468
  else:
1440
1469
  cont_token_property = f'deserialized.get("{next_link_name}") or None'
1441
1470
  list_type = "AsyncList" if self.async_mode else "iter"
1471
+ if cont_token_expr:
1472
+ retval.append(f" _cont_token_elem = {cont_token_expr}")
1442
1473
  retval.append(f" return {cont_token_property}, {list_type}(list_of_elem)")
1443
1474
  return retval
1444
1475
 
@@ -75,9 +75,11 @@ class GeneralSerializer(BaseSerializer):
75
75
  # If parsing the pyproject.toml fails, we assume the it does not exist or is incorrectly formatted.
76
76
  return result
77
77
 
78
- # Keep "azure-sdk-build" and "packaging" configuration
79
- if "tool" in loaded_pyproject_toml and "azure-sdk-build" in loaded_pyproject_toml["tool"]:
80
- result["KEEP_FIELDS"]["tool.azure-sdk-build"] = loaded_pyproject_toml["tool"]["azure-sdk-build"]
78
+ # Keep "azure-sdk-*" and "packaging" configuration
79
+ if "tool" in loaded_pyproject_toml:
80
+ for key in loaded_pyproject_toml["tool"]:
81
+ if key.startswith("azure-sdk"):
82
+ result["KEEP_FIELDS"][f"tool.{key}"] = loaded_pyproject_toml["tool"][key]
81
83
  if "packaging" in loaded_pyproject_toml:
82
84
  result["KEEP_FIELDS"]["packaging"] = loaded_pyproject_toml["packaging"]
83
85
 
@@ -3,24 +3,69 @@
3
3
  {%- set is_sphinx_doc = doc_string.strip().startswith(':ivar') or doc_string.strip().startswith(':vartype') or doc_string.strip().startswith(':param') or doc_string.strip().startswith(':type') -%}
4
4
  {# Custom handling for bullet points - normalization is now done in preprocessing #}
5
5
  {% set enable_custom_handling = "\n* " in doc_string or doc_string.startswith("* ") %}
6
+ {# Normalize multi-line descriptions by collapsing internal new_lines to spaces, but preserve special blocks #}
7
+ {%- if not enable_custom_handling -%}
8
+ {%- if '.. code-block::' in doc_string -%}
9
+ {# Split at code block and only normalize the prose before it #}
10
+ {%- set parts = doc_string.split('.. code-block::') -%}
11
+ {%- set prose = parts[0].rstrip() -%}
12
+ {%- set code_block = '.. code-block::' + parts[1:] | join('.. code-block::') -%}
13
+ {%- if '\n\n' not in prose -%}
14
+ {%- set normalized_lines = [] -%}
15
+ {%- for line in prose.split('\n') -%}
16
+ {%- set stripped = line.strip() -%}
17
+ {%- if stripped -%}
18
+ {%- set _ = normalized_lines.append(stripped) -%}
19
+ {%- endif -%}
20
+ {%- endfor -%}
21
+ {%- set doc_string = normalized_lines | join(' ') + '\n\n' + code_block -%}
22
+ {%- endif -%}
23
+ {%- elif '\n\n' not in doc_string -%}
24
+ {%- set normalized_lines = [] -%}
25
+ {%- for line in doc_string.split('\n') -%}
26
+ {%- set stripped = line.strip() -%}
27
+ {%- if stripped -%}
28
+ {%- set _ = normalized_lines.append(stripped) -%}
29
+ {%- endif -%}
30
+ {%- endfor -%}
31
+ {%- set doc_string = normalized_lines | join(' ') -%}
32
+ {%- endif -%}
33
+ {%- endif -%}
6
34
  {%- if enable_custom_handling -%}
7
- {%- set lines = doc_string.split('\n') -%}
35
+ {# First, normalize prose before bullet points by splitting at first bullet #}
36
+ {%- set normalized_doc = doc_string -%}
37
+ {%- if '\n* ' in doc_string -%}
38
+ {%- set parts = doc_string.split('\n* ', 1) -%}
39
+ {%- set prose_part = parts[0] -%}
40
+ {%- set bullet_part = '* ' + parts[1] -%}
41
+ {# Normalize the prose part #}
42
+ {%- set prose_lines = [] -%}
43
+ {%- for line in prose_part.split('\n') -%}
44
+ {%- set stripped = line.strip() -%}
45
+ {%- if stripped -%}
46
+ {%- set _ = prose_lines.append(stripped) -%}
47
+ {%- endif -%}
48
+ {%- endfor -%}
49
+ {%- set normalized_doc = prose_lines | join(' ') + '\n\n' + bullet_part -%}
50
+ {%- endif -%}
51
+ {%- set lines = normalized_doc.split('\n') -%}
8
52
  {%- set base_indent = wrap_string.lstrip('\n') -%}
9
53
  {%- set result_lines = [] -%}
10
54
  {%- for line in lines -%}
11
- {%- if line.startswith('* ') -%}
55
+ {%- if line.strip().startswith('* ') -%}
12
56
  {# Handle bullet points with proper continuation alignment #}
13
- {%- set bullet_content = line[2:] -%}
57
+ {%- set bullet_content = line.strip()[2:] -%}
14
58
  {%- set bullet_line = base_indent + ' * ' + bullet_content -%}
15
59
  {%- set continuation_spaces = base_indent + ' ' -%}
16
60
  {%- set wrapped = bullet_line | wordwrap(width=95, break_long_words=False, break_on_hyphens=False, wrapstring='\n' + continuation_spaces) -%}
17
61
  {%- set _ = result_lines.append(wrapped) -%}
18
62
  {%- elif line.strip() -%}
19
- {%- set line_indent = '' if line.strip().startswith(':') or loop.index == 1 else (base_indent + ' ') -%}
20
- {%- set wrapped = (line_indent + line) | wordwrap(width=95, break_long_words=False, break_on_hyphens=False, wrapstring=wrap_string) -%}
21
- {%- for line in wrapped.split('\n') -%}
22
- {%- set prefix = "" if loop.index == 1 else " " -%}
23
- {%- set _ = result_lines.append(prefix + line) -%}
63
+ {%- set stripped_line = line.strip() -%}
64
+ {%- set line_indent = '' if stripped_line.startswith(':') or loop.index == 1 else (base_indent + ' ') -%}
65
+ {%- set wrapped = (line_indent + stripped_line) | wordwrap(width=95, break_long_words=False, break_on_hyphens=False, wrapstring=wrap_string) -%}
66
+ {%- for w_line in wrapped.split('\n') -%}
67
+ {%- set prefix = "" if loop.index == 1 else " " -%}
68
+ {%- set _ = result_lines.append(prefix + w_line) -%}
24
69
  {%- endfor -%}
25
70
  {%- else -%}
26
71
  {# Do not add continuous blank lines #}
@@ -44,4 +89,4 @@
44
89
  {% set suffix = suffix_string if list_result | length == loop.index %}
45
90
  {{ prefix }}{{ line }}{{ suffix }}
46
91
  {% endfor %}
47
- {% endmacro %}
92
+ {% endmacro %}
@@ -1,4 +1,35 @@
1
- {% macro wrap_string(string, wrapstring, width=95) %}{{ string | replace("\\", "\\\\") | wordwrap(width=width, break_long_words=False, break_on_hyphens=False, wrapstring=wrapstring)}}{% endmacro %}
1
+ {% macro wrap_string(string, wrapstring, width=95) %}
2
+ {%- set normalized_string = string -%}
3
+ {# Normalize multi-line descriptions by collapsing internal newlines to spaces, but preserve special blocks #}
4
+ {# Skip normalization for pre-formatted content (strings starting with whitespace like code examples) #}
5
+ {%- if '\n* ' not in string and string == string.lstrip() -%}
6
+ {%- if '.. code-block::' in string -%}
7
+ {# Split at code block and only normalize the prose before it #}
8
+ {%- set parts = string.split('.. code-block::') -%}
9
+ {%- set prose = parts[0].rstrip() -%}
10
+ {%- set code_block = '.. code-block::' + parts[1:] | join('.. code-block::') -%}
11
+ {%- if '\n\n' not in prose -%}
12
+ {%- set normalized_lines = [] -%}
13
+ {%- for line in prose.split('\n') -%}
14
+ {%- set stripped = line.strip() -%}
15
+ {%- if stripped -%}
16
+ {%- set _ = normalized_lines.append(stripped) -%}
17
+ {%- endif -%}
18
+ {%- endfor -%}
19
+ {%- set normalized_string = normalized_lines | join(' ') + '\n\n' + code_block -%}
20
+ {%- endif -%}
21
+ {%- elif '\n\n' not in string -%}
22
+ {%- set normalized_lines = [] -%}
23
+ {%- for line in string.split('\n') -%}
24
+ {%- set stripped = line.strip() -%}
25
+ {%- if stripped -%}
26
+ {%- set _ = normalized_lines.append(stripped) -%}
27
+ {%- endif -%}
28
+ {%- endfor -%}
29
+ {%- set normalized_string = normalized_lines | join(' ') -%}
30
+ {%- endif -%}
31
+ {%- endif -%}
32
+ {{ normalized_string | replace("\\", "\\\\") | wordwrap(width=width, break_long_words=False, break_on_hyphens=False, wrapstring=wrapstring)}}{% endmacro %}
2
33
 
3
34
  {% macro description(builder, serializer) %}
4
35
  {% set example_template = serializer.example_template(builder) %}
@@ -109,13 +109,17 @@ exclude = [
109
109
  [tool.setuptools.package-data]
110
110
  pytyped = ["py.typed"]
111
111
  {% endif %}
112
- {% if KEEP_FIELDS and KEEP_FIELDS.get('tool.azure-sdk-build') %}
112
+ {% if KEEP_FIELDS %}
113
+ {% for field_key, field_val in KEEP_FIELDS.items() %}
114
+ {% if field_key.startswith('tool.azure-sdk') %}
113
115
 
114
- [tool.azure-sdk-build]
115
- {% for key, val in KEEP_FIELDS.get('tool.azure-sdk-build').items() %}
116
+ [{{ field_key }}]
117
+ {% for key, val in field_val.items() %}
116
118
  {{ key }} = {{ val|tojson }}
117
119
  {% endfor %}
118
120
  {% endif %}
121
+ {% endfor %}
122
+ {% endif %}
119
123
  {% if KEEP_FIELDS and KEEP_FIELDS.get('packaging') %}
120
124
 
121
125
  [packaging]
@@ -4,11 +4,11 @@
4
4
  "install": [
5
5
  {
6
6
  "download_info": {
7
- "url": "https://files.pythonhosted.org/packages/e0/76/f963c61683a39084aa575f98089253e1e852a4417cb8a3a8a422923a5246/setuptools-80.10.1-py3-none-any.whl",
7
+ "url": "https://files.pythonhosted.org/packages/94/b8/f1f62a5e3c0ad2ff1d189590bfa4c46b4f3b6e49cef6f26c6ee4e575394d/setuptools-80.10.2-py3-none-any.whl",
8
8
  "archive_info": {
9
- "hash": "sha256=fc30c51cbcb8199a219c12cc9c281b5925a4978d212f84229c909636d9f6984e",
9
+ "hash": "sha256=95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173",
10
10
  "hashes": {
11
- "sha256": "fc30c51cbcb8199a219c12cc9c281b5925a4978d212f84229c909636d9f6984e"
11
+ "sha256": "95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173"
12
12
  }
13
13
  }
14
14
  },
@@ -18,7 +18,7 @@
18
18
  "metadata": {
19
19
  "metadata_version": "2.4",
20
20
  "name": "setuptools",
21
- "version": "80.10.1",
21
+ "version": "80.10.2",
22
22
  "dynamic": [
23
23
  "license-file"
24
24
  ],
@@ -63,7 +63,6 @@
63
63
  "tomli-w>=1.0.0; extra == \"test\"",
64
64
  "pytest-timeout; extra == \"test\"",
65
65
  "pytest-perf; sys_platform != \"cygwin\" and extra == \"test\"",
66
- "pyobjc<12; (sys_platform == \"darwin\" and python_version <= \"3.9\") and extra == \"test\"",
67
66
  "jaraco.develop>=7.21; (python_version >= \"3.9\" and sys_platform != \"cygwin\") and extra == \"test\"",
68
67
  "pytest-home>=0.5; extra == \"test\"",
69
68
  "pytest-subprocess; extra == \"test\"",
@@ -448,7 +448,19 @@ class OperationBase( # pylint: disable=too-many-public-methods,too-many-instanc
448
448
  elif self.need_deserialize:
449
449
  file_import.add_submodule_import(relative_path, "_deserialize", ImportType.LOCAL)
450
450
  if self.default_error_deserialization(serialize_namespace) or self.non_default_errors:
451
- file_import.add_submodule_import(relative_path, "_failsafe_deserialize", ImportType.LOCAL)
451
+ xml_non_default_errors = any(
452
+ xml_serializable(str(e.default_content_type)) for e in self.non_default_errors
453
+ )
454
+ try:
455
+ default_error = next(e for e in self.exceptions if "default" in e.status_codes and e.type)
456
+ except StopIteration:
457
+ default_error = None
458
+ if xml_non_default_errors or (
459
+ default_error and xml_serializable(str(default_error.default_content_type))
460
+ ):
461
+ file_import.add_submodule_import(relative_path, "_failsafe_deserialize_xml", ImportType.LOCAL)
462
+ else:
463
+ file_import.add_submodule_import(relative_path, "_failsafe_deserialize", ImportType.LOCAL)
452
464
  return file_import
453
465
 
454
466
  def get_response_from_status(self, status_code: Optional[Union[str, int]]) -> ResponseType:
@@ -73,6 +73,13 @@ class PagingOperationBase(OperationBase[PagingResponseType]):
73
73
  def next_variable_name(self) -> str:
74
74
  return "_continuation_token" if self.has_continuation_token else "next_link"
75
75
 
76
+ @property
77
+ def is_xml_paging(self) -> bool:
78
+ try:
79
+ return bool(self.responses[0].item_type.xml_metadata)
80
+ except KeyError:
81
+ return False
82
+
76
83
  def _get_attr_name(self, wire_name: str) -> str:
77
84
  response_type = self.responses[0].type
78
85
  if not response_type:
@@ -176,6 +183,9 @@ class PagingOperationBase(OperationBase[PagingResponseType]):
176
183
  file_import.merge(self.item_type.imports(**kwargs))
177
184
  if self.default_error_deserialization(serialize_namespace) or self.need_deserialize:
178
185
  file_import.add_submodule_import(relative_path, "_deserialize", ImportType.LOCAL)
186
+ if self.is_xml_paging:
187
+ file_import.add_submodule_import("xml.etree", "ElementTree", ImportType.STDLIB, alias="ET")
188
+ file_import.add_submodule_import(relative_path, "_convert_element", ImportType.LOCAL)
179
189
  return file_import
180
190
 
181
191
 
@@ -1045,7 +1045,9 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
1045
1045
  retval.extend([f" {l}" for l in response_read])
1046
1046
  retval.append(" map_error(status_code=response.status_code, response=response, error_map=error_map)")
1047
1047
  error_model = ""
1048
- if builder.non_default_errors and self.code_model.options["models-mode"]:
1048
+ if ( # pylint: disable=too-many-nested-blocks
1049
+ builder.non_default_errors and self.code_model.options["models-mode"]
1050
+ ):
1049
1051
  error_model = ", model=error"
1050
1052
  condition = "if"
1051
1053
  retval.append(" error = None")
@@ -1062,9 +1064,11 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
1062
1064
  is_operation_file=True, skip_quote=True, serialize_namespace=self.serialize_namespace
1063
1065
  )
1064
1066
  if self.code_model.options["models-mode"] == "dpg":
1065
- retval.append(
1066
- f" error = _failsafe_deserialize({type_annotation},{pylint_disable}\n response)"
1067
- )
1067
+ if xml_serializable(str(e.default_content_type)):
1068
+ fn = "_failsafe_deserialize_xml"
1069
+ else:
1070
+ fn = "_failsafe_deserialize"
1071
+ retval.append(f" error = {fn}({type_annotation},{pylint_disable}\n response)")
1068
1072
  else:
1069
1073
  retval.extend(
1070
1074
  [
@@ -1130,9 +1134,14 @@ class _OperationSerializer(_BuilderBaseSerializer[OperationType]):
1130
1134
  if builder.non_default_errors:
1131
1135
  retval.append(" else:")
1132
1136
  if self.code_model.options["models-mode"] == "dpg":
1137
+ default_exception = next(e for e in builder.exceptions if "default" in e.status_codes and e.type)
1138
+ if xml_serializable(str(default_exception.default_content_type)):
1139
+ fn = "_failsafe_deserialize_xml"
1140
+ else:
1141
+ fn = "_failsafe_deserialize"
1133
1142
  retval.extend(
1134
1143
  [
1135
- f"{indent}error = _failsafe_deserialize(",
1144
+ f"{indent}error = {fn}(",
1136
1145
  f"{indent} {default_error_deserialization}",
1137
1146
  f"{indent} response,",
1138
1147
  f"{indent})",
@@ -1372,10 +1381,15 @@ class _PagingOperationSerializer(_OperationSerializer[PagingOperationType]):
1372
1381
  def _function_def(self) -> str:
1373
1382
  return "def"
1374
1383
 
1375
- def _extract_data_callback(self, builder: PagingOperationType) -> list[str]: # pylint: disable=too-many-statements
1384
+ def _extract_data_callback( # pylint: disable=too-many-statements,too-many-branches
1385
+ self, builder: PagingOperationType
1386
+ ) -> list[str]:
1376
1387
  retval = [f"{'async ' if self.async_mode else ''}def extract_data(pipeline_response):"]
1377
1388
  response = builder.responses[0]
1378
- deserialized = "pipeline_response.http_response.json()"
1389
+ if builder.is_xml_paging:
1390
+ deserialized = "ET.fromstring(pipeline_response.http_response.text())"
1391
+ else:
1392
+ deserialized = "pipeline_response.http_response.json()"
1379
1393
  if self.code_model.options["models-mode"] == "msrest":
1380
1394
  suffix = ".http_response" if hasattr(builder, "initial_operation") else ""
1381
1395
  deserialize_type = response.serialization_type(serialize_namespace=self.serialize_namespace)
@@ -1395,6 +1409,10 @@ class _PagingOperationSerializer(_OperationSerializer[PagingOperationType]):
1395
1409
  item_name = builder.item_name
1396
1410
  if self.code_model.options["models-mode"] == "msrest":
1397
1411
  access = f".{item_name}"
1412
+ elif builder.is_xml_paging:
1413
+ # For XML, use .find() to navigate the element tree
1414
+ item_name_array = item_name.split(".")
1415
+ access = "".join([f'.find("{i}")' for i in item_name_array])
1398
1416
  else:
1399
1417
  item_name_array = item_name.split(".")
1400
1418
  access = (
@@ -1412,11 +1430,17 @@ class _PagingOperationSerializer(_OperationSerializer[PagingOperationType]):
1412
1430
  retval.append(" if cls:")
1413
1431
  retval.append(" list_of_elem = cls(list_of_elem) # type: ignore")
1414
1432
 
1433
+ cont_token_expr: Optional[str] = None # For XML, we need to extract find() result first
1415
1434
  if builder.has_continuation_token:
1416
1435
  location = builder.continuation_token.get("output", {}).get("location")
1417
1436
  wire_name = builder.continuation_token.get("output", {}).get("wireName") or ""
1418
1437
  if location == "header":
1419
1438
  cont_token_property = f'pipeline_response.http_response.headers.get("{wire_name}") or None'
1439
+ elif builder.is_xml_paging:
1440
+ wire_name_array = wire_name.split(".")
1441
+ wire_name_call = "".join([f'.find("{i}")' for i in wire_name_array])
1442
+ cont_token_expr = f"deserialized{wire_name_call}"
1443
+ cont_token_property = "_cont_token_elem.text if _cont_token_elem is not None else None"
1420
1444
  else:
1421
1445
  wire_name_array = wire_name.split(".")
1422
1446
  wire_name_call = (
@@ -1429,6 +1453,11 @@ class _PagingOperationSerializer(_OperationSerializer[PagingOperationType]):
1429
1453
  cont_token_property = "None"
1430
1454
  elif self.code_model.options["models-mode"] == "msrest":
1431
1455
  cont_token_property = f"deserialized.{next_link_name} or None"
1456
+ elif builder.is_xml_paging:
1457
+ next_link_name_array = next_link_name.split(".")
1458
+ access = "".join([f'.find("{i}")' for i in next_link_name_array])
1459
+ cont_token_expr = f"deserialized{access}"
1460
+ cont_token_property = "_cont_token_elem.text if _cont_token_elem is not None else None"
1432
1461
  elif builder.next_link_is_nested:
1433
1462
  next_link_name_array = next_link_name.split(".")
1434
1463
  access = (
@@ -1439,6 +1468,8 @@ class _PagingOperationSerializer(_OperationSerializer[PagingOperationType]):
1439
1468
  else:
1440
1469
  cont_token_property = f'deserialized.get("{next_link_name}") or None'
1441
1470
  list_type = "AsyncList" if self.async_mode else "iter"
1471
+ if cont_token_expr:
1472
+ retval.append(f" _cont_token_elem = {cont_token_expr}")
1442
1473
  retval.append(f" return {cont_token_property}, {list_type}(list_of_elem)")
1443
1474
  return retval
1444
1475
 
@@ -75,9 +75,11 @@ class GeneralSerializer(BaseSerializer):
75
75
  # If parsing the pyproject.toml fails, we assume the it does not exist or is incorrectly formatted.
76
76
  return result
77
77
 
78
- # Keep "azure-sdk-build" and "packaging" configuration
79
- if "tool" in loaded_pyproject_toml and "azure-sdk-build" in loaded_pyproject_toml["tool"]:
80
- result["KEEP_FIELDS"]["tool.azure-sdk-build"] = loaded_pyproject_toml["tool"]["azure-sdk-build"]
78
+ # Keep "azure-sdk-*" and "packaging" configuration
79
+ if "tool" in loaded_pyproject_toml:
80
+ for key in loaded_pyproject_toml["tool"]:
81
+ if key.startswith("azure-sdk"):
82
+ result["KEEP_FIELDS"][f"tool.{key}"] = loaded_pyproject_toml["tool"][key]
81
83
  if "packaging" in loaded_pyproject_toml:
82
84
  result["KEEP_FIELDS"]["packaging"] = loaded_pyproject_toml["packaging"]
83
85
 
@@ -3,24 +3,69 @@
3
3
  {%- set is_sphinx_doc = doc_string.strip().startswith(':ivar') or doc_string.strip().startswith(':vartype') or doc_string.strip().startswith(':param') or doc_string.strip().startswith(':type') -%}
4
4
  {# Custom handling for bullet points - normalization is now done in preprocessing #}
5
5
  {% set enable_custom_handling = "\n* " in doc_string or doc_string.startswith("* ") %}
6
+ {# Normalize multi-line descriptions by collapsing internal new_lines to spaces, but preserve special blocks #}
7
+ {%- if not enable_custom_handling -%}
8
+ {%- if '.. code-block::' in doc_string -%}
9
+ {# Split at code block and only normalize the prose before it #}
10
+ {%- set parts = doc_string.split('.. code-block::') -%}
11
+ {%- set prose = parts[0].rstrip() -%}
12
+ {%- set code_block = '.. code-block::' + parts[1:] | join('.. code-block::') -%}
13
+ {%- if '\n\n' not in prose -%}
14
+ {%- set normalized_lines = [] -%}
15
+ {%- for line in prose.split('\n') -%}
16
+ {%- set stripped = line.strip() -%}
17
+ {%- if stripped -%}
18
+ {%- set _ = normalized_lines.append(stripped) -%}
19
+ {%- endif -%}
20
+ {%- endfor -%}
21
+ {%- set doc_string = normalized_lines | join(' ') + '\n\n' + code_block -%}
22
+ {%- endif -%}
23
+ {%- elif '\n\n' not in doc_string -%}
24
+ {%- set normalized_lines = [] -%}
25
+ {%- for line in doc_string.split('\n') -%}
26
+ {%- set stripped = line.strip() -%}
27
+ {%- if stripped -%}
28
+ {%- set _ = normalized_lines.append(stripped) -%}
29
+ {%- endif -%}
30
+ {%- endfor -%}
31
+ {%- set doc_string = normalized_lines | join(' ') -%}
32
+ {%- endif -%}
33
+ {%- endif -%}
6
34
  {%- if enable_custom_handling -%}
7
- {%- set lines = doc_string.split('\n') -%}
35
+ {# First, normalize prose before bullet points by splitting at first bullet #}
36
+ {%- set normalized_doc = doc_string -%}
37
+ {%- if '\n* ' in doc_string -%}
38
+ {%- set parts = doc_string.split('\n* ', 1) -%}
39
+ {%- set prose_part = parts[0] -%}
40
+ {%- set bullet_part = '* ' + parts[1] -%}
41
+ {# Normalize the prose part #}
42
+ {%- set prose_lines = [] -%}
43
+ {%- for line in prose_part.split('\n') -%}
44
+ {%- set stripped = line.strip() -%}
45
+ {%- if stripped -%}
46
+ {%- set _ = prose_lines.append(stripped) -%}
47
+ {%- endif -%}
48
+ {%- endfor -%}
49
+ {%- set normalized_doc = prose_lines | join(' ') + '\n\n' + bullet_part -%}
50
+ {%- endif -%}
51
+ {%- set lines = normalized_doc.split('\n') -%}
8
52
  {%- set base_indent = wrap_string.lstrip('\n') -%}
9
53
  {%- set result_lines = [] -%}
10
54
  {%- for line in lines -%}
11
- {%- if line.startswith('* ') -%}
55
+ {%- if line.strip().startswith('* ') -%}
12
56
  {# Handle bullet points with proper continuation alignment #}
13
- {%- set bullet_content = line[2:] -%}
57
+ {%- set bullet_content = line.strip()[2:] -%}
14
58
  {%- set bullet_line = base_indent + ' * ' + bullet_content -%}
15
59
  {%- set continuation_spaces = base_indent + ' ' -%}
16
60
  {%- set wrapped = bullet_line | wordwrap(width=95, break_long_words=False, break_on_hyphens=False, wrapstring='\n' + continuation_spaces) -%}
17
61
  {%- set _ = result_lines.append(wrapped) -%}
18
62
  {%- elif line.strip() -%}
19
- {%- set line_indent = '' if line.strip().startswith(':') or loop.index == 1 else (base_indent + ' ') -%}
20
- {%- set wrapped = (line_indent + line) | wordwrap(width=95, break_long_words=False, break_on_hyphens=False, wrapstring=wrap_string) -%}
21
- {%- for line in wrapped.split('\n') -%}
22
- {%- set prefix = "" if loop.index == 1 else " " -%}
23
- {%- set _ = result_lines.append(prefix + line) -%}
63
+ {%- set stripped_line = line.strip() -%}
64
+ {%- set line_indent = '' if stripped_line.startswith(':') or loop.index == 1 else (base_indent + ' ') -%}
65
+ {%- set wrapped = (line_indent + stripped_line) | wordwrap(width=95, break_long_words=False, break_on_hyphens=False, wrapstring=wrap_string) -%}
66
+ {%- for w_line in wrapped.split('\n') -%}
67
+ {%- set prefix = "" if loop.index == 1 else " " -%}
68
+ {%- set _ = result_lines.append(prefix + w_line) -%}
24
69
  {%- endfor -%}
25
70
  {%- else -%}
26
71
  {# Do not add continuous blank lines #}
@@ -44,4 +89,4 @@
44
89
  {% set suffix = suffix_string if list_result | length == loop.index %}
45
90
  {{ prefix }}{{ line }}{{ suffix }}
46
91
  {% endfor %}
47
- {% endmacro %}
92
+ {% endmacro %}
@@ -1,4 +1,35 @@
1
- {% macro wrap_string(string, wrapstring, width=95) %}{{ string | replace("\\", "\\\\") | wordwrap(width=width, break_long_words=False, break_on_hyphens=False, wrapstring=wrapstring)}}{% endmacro %}
1
+ {% macro wrap_string(string, wrapstring, width=95) %}
2
+ {%- set normalized_string = string -%}
3
+ {# Normalize multi-line descriptions by collapsing internal newlines to spaces, but preserve special blocks #}
4
+ {# Skip normalization for pre-formatted content (strings starting with whitespace like code examples) #}
5
+ {%- if '\n* ' not in string and string == string.lstrip() -%}
6
+ {%- if '.. code-block::' in string -%}
7
+ {# Split at code block and only normalize the prose before it #}
8
+ {%- set parts = string.split('.. code-block::') -%}
9
+ {%- set prose = parts[0].rstrip() -%}
10
+ {%- set code_block = '.. code-block::' + parts[1:] | join('.. code-block::') -%}
11
+ {%- if '\n\n' not in prose -%}
12
+ {%- set normalized_lines = [] -%}
13
+ {%- for line in prose.split('\n') -%}
14
+ {%- set stripped = line.strip() -%}
15
+ {%- if stripped -%}
16
+ {%- set _ = normalized_lines.append(stripped) -%}
17
+ {%- endif -%}
18
+ {%- endfor -%}
19
+ {%- set normalized_string = normalized_lines | join(' ') + '\n\n' + code_block -%}
20
+ {%- endif -%}
21
+ {%- elif '\n\n' not in string -%}
22
+ {%- set normalized_lines = [] -%}
23
+ {%- for line in string.split('\n') -%}
24
+ {%- set stripped = line.strip() -%}
25
+ {%- if stripped -%}
26
+ {%- set _ = normalized_lines.append(stripped) -%}
27
+ {%- endif -%}
28
+ {%- endfor -%}
29
+ {%- set normalized_string = normalized_lines | join(' ') -%}
30
+ {%- endif -%}
31
+ {%- endif -%}
32
+ {{ normalized_string | replace("\\", "\\\\") | wordwrap(width=width, break_long_words=False, break_on_hyphens=False, wrapstring=wrapstring)}}{% endmacro %}
2
33
 
3
34
  {% macro description(builder, serializer) %}
4
35
  {% set example_template = serializer.example_template(builder) %}
@@ -109,13 +109,17 @@ exclude = [
109
109
  [tool.setuptools.package-data]
110
110
  pytyped = ["py.typed"]
111
111
  {% endif %}
112
- {% if KEEP_FIELDS and KEEP_FIELDS.get('tool.azure-sdk-build') %}
112
+ {% if KEEP_FIELDS %}
113
+ {% for field_key, field_val in KEEP_FIELDS.items() %}
114
+ {% if field_key.startswith('tool.azure-sdk') %}
113
115
 
114
- [tool.azure-sdk-build]
115
- {% for key, val in KEEP_FIELDS.get('tool.azure-sdk-build').items() %}
116
+ [{{ field_key }}]
117
+ {% for key, val in field_val.items() %}
116
118
  {{ key }} = {{ val|tojson }}
117
119
  {% endfor %}
118
120
  {% endif %}
121
+ {% endfor %}
122
+ {% endif %}
119
123
  {% if KEEP_FIELDS and KEEP_FIELDS.get('packaging') %}
120
124
 
121
125
  [packaging]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autorest/python",
3
- "version": "6.47.0",
3
+ "version": "6.48.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.25.0",
22
+ "@typespec/http-client-python": "~0.26.1",
23
23
  "@autorest/system-requirements": "~1.0.2",
24
24
  "fs-extra": "~11.2.0",
25
25
  "tsx": "~4.19.1"