@typespec/http-client-python 0.4.4 → 0.5.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.
Files changed (137) hide show
  1. package/dist/emitter/emitter.d.ts.map +1 -1
  2. package/dist/emitter/emitter.js +85 -24
  3. package/dist/emitter/emitter.js.map +1 -1
  4. package/dist/emitter/lib.d.ts +1 -0
  5. package/dist/emitter/lib.d.ts.map +1 -1
  6. package/dist/emitter/lib.js +1 -0
  7. package/dist/emitter/lib.js.map +1 -1
  8. package/dist/emitter/run-python3.d.ts +2 -0
  9. package/dist/emitter/run-python3.d.ts.map +1 -0
  10. package/dist/emitter/run-python3.js +19 -0
  11. package/dist/emitter/run-python3.js.map +1 -0
  12. package/dist/emitter/system-requirements.d.ts +17 -0
  13. package/dist/emitter/system-requirements.d.ts.map +1 -0
  14. package/dist/emitter/system-requirements.js +167 -0
  15. package/dist/emitter/system-requirements.js.map +1 -0
  16. package/emitter/src/emitter.ts +88 -23
  17. package/emitter/src/lib.ts +2 -0
  18. package/emitter/src/run-python3.ts +20 -0
  19. package/emitter/src/system-requirements.ts +261 -0
  20. package/emitter/temp/tsconfig.tsbuildinfo +1 -1
  21. package/eng/scripts/ci/regenerate.ts +16 -4
  22. package/eng/scripts/setup/__pycache__/venvtools.cpython-38.pyc +0 -0
  23. package/eng/scripts/setup/build.ts +16 -0
  24. package/eng/scripts/setup/build_pygen_wheel.py +40 -0
  25. package/eng/scripts/setup/install.py +9 -3
  26. package/eng/scripts/setup/install.ts +32 -0
  27. package/eng/scripts/setup/prepare.py +3 -1
  28. package/eng/scripts/setup/prepare.ts +11 -0
  29. package/eng/scripts/setup/run-python3.ts +1 -6
  30. package/generator/build/lib/pygen/__init__.py +107 -0
  31. package/generator/build/lib/pygen/_version.py +7 -0
  32. package/generator/build/lib/pygen/black.py +71 -0
  33. package/generator/build/lib/pygen/codegen/__init__.py +357 -0
  34. package/generator/build/lib/pygen/codegen/_utils.py +17 -0
  35. package/generator/build/lib/pygen/codegen/models/__init__.py +204 -0
  36. package/generator/build/lib/pygen/codegen/models/base.py +186 -0
  37. package/generator/build/lib/pygen/codegen/models/base_builder.py +118 -0
  38. package/generator/build/lib/pygen/codegen/models/client.py +435 -0
  39. package/generator/build/lib/pygen/codegen/models/code_model.py +237 -0
  40. package/generator/build/lib/pygen/codegen/models/combined_type.py +149 -0
  41. package/generator/build/lib/pygen/codegen/models/constant_type.py +129 -0
  42. package/generator/build/lib/pygen/codegen/models/credential_types.py +214 -0
  43. package/generator/build/lib/pygen/codegen/models/dictionary_type.py +127 -0
  44. package/generator/build/lib/pygen/codegen/models/enum_type.py +238 -0
  45. package/generator/build/lib/pygen/codegen/models/imports.py +291 -0
  46. package/generator/build/lib/pygen/codegen/models/list_type.py +143 -0
  47. package/generator/build/lib/pygen/codegen/models/lro_operation.py +142 -0
  48. package/generator/build/lib/pygen/codegen/models/lro_paging_operation.py +32 -0
  49. package/generator/build/lib/pygen/codegen/models/model_type.py +357 -0
  50. package/generator/build/lib/pygen/codegen/models/operation.py +509 -0
  51. package/generator/build/lib/pygen/codegen/models/operation_group.py +184 -0
  52. package/generator/build/lib/pygen/codegen/models/paging_operation.py +155 -0
  53. package/generator/build/lib/pygen/codegen/models/parameter.py +412 -0
  54. package/generator/build/lib/pygen/codegen/models/parameter_list.py +387 -0
  55. package/generator/build/lib/pygen/codegen/models/primitive_types.py +659 -0
  56. package/generator/build/lib/pygen/codegen/models/property.py +170 -0
  57. package/generator/build/lib/pygen/codegen/models/request_builder.py +189 -0
  58. package/generator/build/lib/pygen/codegen/models/request_builder_parameter.py +115 -0
  59. package/generator/build/lib/pygen/codegen/models/response.py +348 -0
  60. package/generator/build/lib/pygen/codegen/models/utils.py +21 -0
  61. package/generator/build/lib/pygen/codegen/serializers/__init__.py +574 -0
  62. package/generator/build/lib/pygen/codegen/serializers/base_serializer.py +21 -0
  63. package/generator/build/lib/pygen/codegen/serializers/builder_serializer.py +1533 -0
  64. package/generator/build/lib/pygen/codegen/serializers/client_serializer.py +294 -0
  65. package/generator/build/lib/pygen/codegen/serializers/enum_serializer.py +15 -0
  66. package/generator/build/lib/pygen/codegen/serializers/general_serializer.py +213 -0
  67. package/generator/build/lib/pygen/codegen/serializers/import_serializer.py +126 -0
  68. package/generator/build/lib/pygen/codegen/serializers/metadata_serializer.py +198 -0
  69. package/generator/build/lib/pygen/codegen/serializers/model_init_serializer.py +33 -0
  70. package/generator/build/lib/pygen/codegen/serializers/model_serializer.py +335 -0
  71. package/generator/build/lib/pygen/codegen/serializers/operation_groups_serializer.py +89 -0
  72. package/generator/build/lib/pygen/codegen/serializers/operations_init_serializer.py +44 -0
  73. package/generator/build/lib/pygen/codegen/serializers/parameter_serializer.py +221 -0
  74. package/generator/build/lib/pygen/codegen/serializers/patch_serializer.py +19 -0
  75. package/generator/build/lib/pygen/codegen/serializers/request_builders_serializer.py +52 -0
  76. package/generator/build/lib/pygen/codegen/serializers/sample_serializer.py +168 -0
  77. package/generator/build/lib/pygen/codegen/serializers/test_serializer.py +292 -0
  78. package/generator/build/lib/pygen/codegen/serializers/types_serializer.py +31 -0
  79. package/generator/build/lib/pygen/codegen/serializers/utils.py +68 -0
  80. package/generator/build/lib/pygen/codegen/templates/client.py.jinja2 +37 -0
  81. package/generator/build/lib/pygen/codegen/templates/client_container.py.jinja2 +12 -0
  82. package/generator/build/lib/pygen/codegen/templates/config.py.jinja2 +73 -0
  83. package/generator/build/lib/pygen/codegen/templates/config_container.py.jinja2 +16 -0
  84. package/generator/build/lib/pygen/codegen/templates/conftest.py.jinja2 +28 -0
  85. package/generator/build/lib/pygen/codegen/templates/enum.py.jinja2 +13 -0
  86. package/generator/build/lib/pygen/codegen/templates/enum_container.py.jinja2 +10 -0
  87. package/generator/build/lib/pygen/codegen/templates/init.py.jinja2 +24 -0
  88. package/generator/build/lib/pygen/codegen/templates/keywords.jinja2 +27 -0
  89. package/generator/build/lib/pygen/codegen/templates/lro_operation.py.jinja2 +16 -0
  90. package/generator/build/lib/pygen/codegen/templates/lro_paging_operation.py.jinja2 +18 -0
  91. package/generator/build/lib/pygen/codegen/templates/macros.jinja2 +12 -0
  92. package/generator/build/lib/pygen/codegen/templates/metadata.json.jinja2 +167 -0
  93. package/generator/build/lib/pygen/codegen/templates/model_base.py.jinja2 +1174 -0
  94. package/generator/build/lib/pygen/codegen/templates/model_container.py.jinja2 +15 -0
  95. package/generator/build/lib/pygen/codegen/templates/model_dpg.py.jinja2 +97 -0
  96. package/generator/build/lib/pygen/codegen/templates/model_init.py.jinja2 +33 -0
  97. package/generator/build/lib/pygen/codegen/templates/model_msrest.py.jinja2 +92 -0
  98. package/generator/build/lib/pygen/codegen/templates/operation.py.jinja2 +21 -0
  99. package/generator/build/lib/pygen/codegen/templates/operation_group.py.jinja2 +75 -0
  100. package/generator/build/lib/pygen/codegen/templates/operation_groups_container.py.jinja2 +19 -0
  101. package/generator/build/lib/pygen/codegen/templates/operation_tools.jinja2 +81 -0
  102. package/generator/build/lib/pygen/codegen/templates/operations_folder_init.py.jinja2 +17 -0
  103. package/generator/build/lib/pygen/codegen/templates/packaging_templates/CHANGELOG.md.jinja2 +6 -0
  104. package/generator/build/lib/pygen/codegen/templates/packaging_templates/LICENSE.jinja2 +21 -0
  105. package/generator/build/lib/pygen/codegen/templates/packaging_templates/MANIFEST.in.jinja2 +8 -0
  106. package/generator/build/lib/pygen/codegen/templates/packaging_templates/README.md.jinja2 +107 -0
  107. package/generator/build/lib/pygen/codegen/templates/packaging_templates/dev_requirements.txt.jinja2 +9 -0
  108. package/generator/build/lib/pygen/codegen/templates/packaging_templates/setup.py.jinja2 +108 -0
  109. package/generator/build/lib/pygen/codegen/templates/paging_operation.py.jinja2 +21 -0
  110. package/generator/build/lib/pygen/codegen/templates/patch.py.jinja2 +19 -0
  111. package/generator/build/lib/pygen/codegen/templates/pkgutil_init.py.jinja2 +1 -0
  112. package/generator/build/lib/pygen/codegen/templates/request_builder.py.jinja2 +28 -0
  113. package/generator/build/lib/pygen/codegen/templates/request_builders.py.jinja2 +10 -0
  114. package/generator/build/lib/pygen/codegen/templates/rest_init.py.jinja2 +12 -0
  115. package/generator/build/lib/pygen/codegen/templates/sample.py.jinja2 +44 -0
  116. package/generator/build/lib/pygen/codegen/templates/serialization.py.jinja2 +2117 -0
  117. package/generator/build/lib/pygen/codegen/templates/test.py.jinja2 +50 -0
  118. package/generator/build/lib/pygen/codegen/templates/testpreparer.py.jinja2 +26 -0
  119. package/generator/build/lib/pygen/codegen/templates/types.py.jinja2 +7 -0
  120. package/generator/build/lib/pygen/codegen/templates/validation.py.jinja2 +38 -0
  121. package/generator/build/lib/pygen/codegen/templates/vendor.py.jinja2 +96 -0
  122. package/generator/build/lib/pygen/codegen/templates/version.py.jinja2 +4 -0
  123. package/generator/build/lib/pygen/m2r.py +65 -0
  124. package/generator/build/lib/pygen/preprocess/__init__.py +515 -0
  125. package/generator/build/lib/pygen/preprocess/helpers.py +27 -0
  126. package/generator/build/lib/pygen/preprocess/python_mappings.py +226 -0
  127. package/generator/build/lib/pygen/utils.py +163 -0
  128. package/generator/component-detection-pip-report.json +134 -0
  129. package/generator/dev_requirements.txt +0 -1
  130. package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
  131. package/generator/pygen.egg-info/PKG-INFO +7 -4
  132. package/generator/pygen.egg-info/requires.txt +7 -4
  133. package/generator/requirements.txt +5 -10
  134. package/generator/setup.py +7 -4
  135. package/generator/test/azure/requirements.txt +1 -1
  136. package/generator/test/unbranded/requirements.txt +1 -1
  137. package/package.json +6 -5
@@ -0,0 +1,1174 @@
1
+ # coding=utf-8
2
+ # --------------------------------------------------------------------------
3
+ # Copyright (c) {{ code_model.options["company_name"] }} Corporation. All rights reserved.
4
+ # Licensed under the MIT License. See License.txt in the project root for
5
+ # license information.
6
+ # --------------------------------------------------------------------------
7
+ # pylint: disable=protected-access, broad-except
8
+
9
+ import copy
10
+ import calendar
11
+ import decimal
12
+ import functools
13
+ import sys
14
+ import logging
15
+ import base64
16
+ import re
17
+ import typing
18
+ import enum
19
+ import email.utils
20
+ from datetime import datetime, date, time, timedelta, timezone
21
+ from json import JSONEncoder
22
+ import xml.etree.ElementTree as ET
23
+ from typing_extensions import Self
24
+ import isodate
25
+ from {{ code_model.core_library }}.exceptions import DeserializationError
26
+ from {{ code_model.core_library }}{{ "" if code_model.is_azure_flavor else ".utils" }} import CaseInsensitiveEnumMeta
27
+ from {{ code_model.core_library }}.{{ "" if code_model.is_azure_flavor else "runtime." }}pipeline import PipelineResponse
28
+ from {{ code_model.core_library }}.serialization import _Null
29
+
30
+ if sys.version_info >= (3, 9):
31
+ from collections.abc import MutableMapping
32
+ else:
33
+ from typing import MutableMapping
34
+
35
+ _LOGGER = logging.getLogger(__name__)
36
+
37
+ __all__ = ["SdkJSONEncoder", "Model", "rest_field", "rest_discriminator"]
38
+
39
+ TZ_UTC = timezone.utc
40
+ _T = typing.TypeVar("_T")
41
+
42
+
43
+ def _timedelta_as_isostr(td: timedelta) -> str:
44
+ """Converts a datetime.timedelta object into an ISO 8601 formatted string, e.g. 'P4DT12H30M05S'
45
+
46
+ Function adapted from the Tin Can Python project: https://github.com/RusticiSoftware/TinCanPython
47
+
48
+ :param timedelta td: The timedelta to convert
49
+ :rtype: str
50
+ :return: ISO8601 version of this timedelta
51
+ """
52
+
53
+ # Split seconds to larger units
54
+ seconds = td.total_seconds()
55
+ minutes, seconds = divmod(seconds, 60)
56
+ hours, minutes = divmod(minutes, 60)
57
+ days, hours = divmod(hours, 24)
58
+
59
+ days, hours, minutes = list(map(int, (days, hours, minutes)))
60
+ seconds = round(seconds, 6)
61
+
62
+ # Build date
63
+ date_str = ""
64
+ if days:
65
+ date_str = "%sD" % days
66
+
67
+ if hours or minutes or seconds:
68
+ # Build time
69
+ time_str = "T"
70
+
71
+ # Hours
72
+ bigger_exists = date_str or hours
73
+ if bigger_exists:
74
+ time_str += "{:02}H".format(hours)
75
+
76
+ # Minutes
77
+ bigger_exists = bigger_exists or minutes
78
+ if bigger_exists:
79
+ time_str += "{:02}M".format(minutes)
80
+
81
+ # Seconds
82
+ try:
83
+ if seconds.is_integer():
84
+ seconds_string = "{:02}".format(int(seconds))
85
+ else:
86
+ # 9 chars long w/ leading 0, 6 digits after decimal
87
+ seconds_string = "%09.6f" % seconds
88
+ # Remove trailing zeros
89
+ seconds_string = seconds_string.rstrip("0")
90
+ except AttributeError: # int.is_integer() raises
91
+ seconds_string = "{:02}".format(seconds)
92
+
93
+ time_str += "{}S".format(seconds_string)
94
+ else:
95
+ time_str = ""
96
+
97
+ return "P" + date_str + time_str
98
+
99
+
100
+ def _serialize_bytes(o, format: typing.Optional[str] = None) -> str:
101
+ encoded = base64.b64encode(o).decode()
102
+ if format == "base64url":
103
+ return encoded.strip("=").replace("+", "-").replace("/", "_")
104
+ return encoded
105
+
106
+
107
+ def _serialize_datetime(o, format: typing.Optional[str] = None):
108
+ if hasattr(o, "year") and hasattr(o, "hour"):
109
+ if format == "rfc7231":
110
+ return email.utils.format_datetime(o, usegmt=True)
111
+ if format == "unix-timestamp":
112
+ return int(calendar.timegm(o.utctimetuple()))
113
+
114
+ # astimezone() fails for naive times in Python 2.7, so make make sure o is aware (tzinfo is set)
115
+ if not o.tzinfo:
116
+ iso_formatted = o.replace(tzinfo=TZ_UTC).isoformat()
117
+ else:
118
+ iso_formatted = o.astimezone(TZ_UTC).isoformat()
119
+ # Replace the trailing "+00:00" UTC offset with "Z" (RFC 3339: https://www.ietf.org/rfc/rfc3339.txt)
120
+ return iso_formatted.replace("+00:00", "Z")
121
+ # Next try datetime.date or datetime.time
122
+ return o.isoformat()
123
+
124
+
125
+ def _is_readonly(p):
126
+ try:
127
+ return p._visibility == ["read"]
128
+ except AttributeError:
129
+ return False
130
+
131
+
132
+ class SdkJSONEncoder(JSONEncoder):
133
+ """A JSON encoder that's capable of serializing datetime objects and bytes."""
134
+
135
+ def __init__(self, *args, exclude_readonly: bool = False, format: typing.Optional[str] = None, **kwargs):
136
+ super().__init__(*args, **kwargs)
137
+ self.exclude_readonly = exclude_readonly
138
+ self.format = format
139
+
140
+ def default(self, o): # pylint: disable=too-many-return-statements
141
+ if _is_model(o):
142
+ if self.exclude_readonly:
143
+ readonly_props = [p._rest_name for p in o._attr_to_rest_field.values() if _is_readonly(p)]
144
+ return {k: v for k, v in o.items() if k not in readonly_props}
145
+ return dict(o.items())
146
+ try:
147
+ return super(SdkJSONEncoder, self).default(o)
148
+ except TypeError:
149
+ if isinstance(o, _Null):
150
+ return None
151
+ if isinstance(o, decimal.Decimal):
152
+ return float(o)
153
+ if isinstance(o, (bytes, bytearray)):
154
+ return _serialize_bytes(o, self.format)
155
+ try:
156
+ # First try datetime.datetime
157
+ return _serialize_datetime(o, self.format)
158
+ except AttributeError:
159
+ pass
160
+ # Last, try datetime.timedelta
161
+ try:
162
+ return _timedelta_as_isostr(o)
163
+ except AttributeError:
164
+ # This will be raised when it hits value.total_seconds in the method above
165
+ pass
166
+ return super(SdkJSONEncoder, self).default(o)
167
+
168
+
169
+ _VALID_DATE = re.compile(r"\d{4}[-]\d{2}[-]\d{2}T\d{2}:\d{2}:\d{2}" + r"\.?\d*Z?[-+]?[\d{2}]?:?[\d{2}]?")
170
+ _VALID_RFC7231 = re.compile(
171
+ r"(Mon|Tue|Wed|Thu|Fri|Sat|Sun),\s\d{2}\s"
172
+ r"(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s\d{4}\s\d{2}:\d{2}:\d{2}\sGMT"
173
+ )
174
+
175
+
176
+ def _deserialize_datetime(attr: typing.Union[str, datetime]) -> datetime:
177
+ """Deserialize ISO-8601 formatted string into Datetime object.
178
+
179
+ :param str attr: response string to be deserialized.
180
+ :rtype: ~datetime.datetime
181
+ :returns: The datetime object from that input
182
+ """
183
+ if isinstance(attr, datetime):
184
+ # i'm already deserialized
185
+ return attr
186
+ attr = attr.upper()
187
+ match = _VALID_DATE.match(attr)
188
+ if not match:
189
+ raise ValueError("Invalid datetime string: " + attr)
190
+
191
+ check_decimal = attr.split(".")
192
+ if len(check_decimal) > 1:
193
+ decimal_str = ""
194
+ for digit in check_decimal[1]:
195
+ if digit.isdigit():
196
+ decimal_str += digit
197
+ else:
198
+ break
199
+ if len(decimal_str) > 6:
200
+ attr = attr.replace(decimal_str, decimal_str[0:6])
201
+
202
+ date_obj = isodate.parse_datetime(attr)
203
+ test_utc = date_obj.utctimetuple()
204
+ if test_utc.tm_year > 9999 or test_utc.tm_year < 1:
205
+ raise OverflowError("Hit max or min date")
206
+ return date_obj
207
+
208
+
209
+ def _deserialize_datetime_rfc7231(attr: typing.Union[str, datetime]) -> datetime:
210
+ """Deserialize RFC7231 formatted string into Datetime object.
211
+
212
+ :param str attr: response string to be deserialized.
213
+ :rtype: ~datetime.datetime
214
+ :returns: The datetime object from that input
215
+ """
216
+ if isinstance(attr, datetime):
217
+ # i'm already deserialized
218
+ return attr
219
+ match = _VALID_RFC7231.match(attr)
220
+ if not match:
221
+ raise ValueError("Invalid datetime string: " + attr)
222
+
223
+ return email.utils.parsedate_to_datetime(attr)
224
+
225
+
226
+ def _deserialize_datetime_unix_timestamp(attr: typing.Union[float, datetime]) -> datetime:
227
+ """Deserialize unix timestamp into Datetime object.
228
+
229
+ :param str attr: response string to be deserialized.
230
+ :rtype: ~datetime.datetime
231
+ :returns: The datetime object from that input
232
+ """
233
+ if isinstance(attr, datetime):
234
+ # i'm already deserialized
235
+ return attr
236
+ return datetime.fromtimestamp(attr, TZ_UTC)
237
+
238
+
239
+ def _deserialize_date(attr: typing.Union[str, date]) -> date:
240
+ """Deserialize ISO-8601 formatted string into Date object.
241
+ :param str attr: response string to be deserialized.
242
+ :rtype: date
243
+ :returns: The date object from that input
244
+ """
245
+ # This must NOT use defaultmonth/defaultday. Using None ensure this raises an exception.
246
+ if isinstance(attr, date):
247
+ return attr
248
+ return isodate.parse_date(attr, defaultmonth=None, defaultday=None) # type: ignore
249
+
250
+
251
+ def _deserialize_time(attr: typing.Union[str, time]) -> time:
252
+ """Deserialize ISO-8601 formatted string into time object.
253
+
254
+ :param str attr: response string to be deserialized.
255
+ :rtype: datetime.time
256
+ :returns: The time object from that input
257
+ """
258
+ if isinstance(attr, time):
259
+ return attr
260
+ return isodate.parse_time(attr)
261
+
262
+
263
+ def _deserialize_bytes(attr):
264
+ if isinstance(attr, (bytes, bytearray)):
265
+ return attr
266
+ return bytes(base64.b64decode(attr))
267
+
268
+
269
+ def _deserialize_bytes_base64(attr):
270
+ if isinstance(attr, (bytes, bytearray)):
271
+ return attr
272
+ padding = "=" * (3 - (len(attr) + 3) % 4) # type: ignore
273
+ attr = attr + padding # type: ignore
274
+ encoded = attr.replace("-", "+").replace("_", "/")
275
+ return bytes(base64.b64decode(encoded))
276
+
277
+
278
+ def _deserialize_duration(attr):
279
+ if isinstance(attr, timedelta):
280
+ return attr
281
+ return isodate.parse_duration(attr)
282
+
283
+
284
+ def _deserialize_decimal(attr):
285
+ if isinstance(attr, decimal.Decimal):
286
+ return attr
287
+ return decimal.Decimal(str(attr))
288
+
289
+
290
+ def _deserialize_int_as_str(attr):
291
+ if isinstance(attr, int):
292
+ return attr
293
+ return int(attr)
294
+
295
+
296
+ _DESERIALIZE_MAPPING = {
297
+ datetime: _deserialize_datetime,
298
+ date: _deserialize_date,
299
+ time: _deserialize_time,
300
+ bytes: _deserialize_bytes,
301
+ bytearray: _deserialize_bytes,
302
+ timedelta: _deserialize_duration,
303
+ typing.Any: lambda x: x,
304
+ decimal.Decimal: _deserialize_decimal,
305
+ }
306
+
307
+ _DESERIALIZE_MAPPING_WITHFORMAT = {
308
+ "rfc3339": _deserialize_datetime,
309
+ "rfc7231": _deserialize_datetime_rfc7231,
310
+ "unix-timestamp": _deserialize_datetime_unix_timestamp,
311
+ "base64": _deserialize_bytes,
312
+ "base64url": _deserialize_bytes_base64,
313
+ }
314
+
315
+
316
+ def get_deserializer(annotation: typing.Any, rf: typing.Optional["_RestField"] = None):
317
+ if annotation is int and rf and rf._format == "str":
318
+ return _deserialize_int_as_str
319
+ if rf and rf._format:
320
+ return _DESERIALIZE_MAPPING_WITHFORMAT.get(rf._format)
321
+ return _DESERIALIZE_MAPPING.get(annotation) # pyright: ignore
322
+
323
+
324
+ def _get_type_alias_type(module_name: str, alias_name: str):
325
+ types = {
326
+ k: v
327
+ for k, v in sys.modules[module_name].__dict__.items()
328
+ if isinstance(v, typing._GenericAlias) # type: ignore
329
+ }
330
+ if alias_name not in types:
331
+ return alias_name
332
+ return types[alias_name]
333
+
334
+
335
+ def _get_model(module_name: str, model_name: str):
336
+ models = {k: v for k, v in sys.modules[module_name].__dict__.items() if isinstance(v, type)}
337
+ module_end = module_name.rsplit(".", 1)[0]
338
+ models.update({k: v for k, v in sys.modules[module_end].__dict__.items() if isinstance(v, type)})
339
+ if isinstance(model_name, str):
340
+ model_name = model_name.split(".")[-1]
341
+ if model_name not in models:
342
+ return model_name
343
+ return models[model_name]
344
+
345
+
346
+ _UNSET = object()
347
+
348
+
349
+ class _MyMutableMapping(MutableMapping[str, typing.Any]): # pylint: disable=unsubscriptable-object
350
+ def __init__(self, data: typing.Dict[str, typing.Any]) -> None:
351
+ self._data = data
352
+
353
+ def __contains__(self, key: typing.Any) -> bool:
354
+ return key in self._data
355
+
356
+ def __getitem__(self, key: str) -> typing.Any:
357
+ return self._data.__getitem__(key)
358
+
359
+ def __setitem__(self, key: str, value: typing.Any) -> None:
360
+ self._data.__setitem__(key, value)
361
+
362
+ def __delitem__(self, key: str) -> None:
363
+ self._data.__delitem__(key)
364
+
365
+ def __iter__(self) -> typing.Iterator[typing.Any]:
366
+ return self._data.__iter__()
367
+
368
+ def __len__(self) -> int:
369
+ return self._data.__len__()
370
+
371
+ def __ne__(self, other: typing.Any) -> bool:
372
+ return not self.__eq__(other)
373
+
374
+ def keys(self) -> typing.KeysView[str]:
375
+ return self._data.keys()
376
+
377
+ def values(self) -> typing.ValuesView[typing.Any]:
378
+ return self._data.values()
379
+
380
+ def items(self) -> typing.ItemsView[str, typing.Any]:
381
+ return self._data.items()
382
+
383
+ def get(self, key: str, default: typing.Any = None) -> typing.Any:
384
+ try:
385
+ return self[key]
386
+ except KeyError:
387
+ return default
388
+
389
+ @typing.overload
390
+ def pop(self, key: str) -> typing.Any: ...
391
+
392
+ @typing.overload
393
+ def pop(self, key: str, default: _T) -> _T: ...
394
+
395
+ @typing.overload
396
+ def pop(self, key: str, default: typing.Any) -> typing.Any: ...
397
+
398
+ def pop(self, key: str, default: typing.Any = _UNSET) -> typing.Any:
399
+ if default is _UNSET:
400
+ return self._data.pop(key)
401
+ return self._data.pop(key, default)
402
+
403
+ def popitem(self) -> typing.Tuple[str, typing.Any]:
404
+ return self._data.popitem()
405
+
406
+ def clear(self) -> None:
407
+ self._data.clear()
408
+
409
+ def update(self, *args: typing.Any, **kwargs: typing.Any) -> None:
410
+ self._data.update(*args, **kwargs)
411
+
412
+ @typing.overload
413
+ def setdefault(self, key: str, default: None = None) -> None: ...
414
+
415
+ @typing.overload
416
+ def setdefault(self, key: str, default: typing.Any) -> typing.Any: ...
417
+
418
+ def setdefault(self, key: str, default: typing.Any = _UNSET) -> typing.Any:
419
+ if default is _UNSET:
420
+ return self._data.setdefault(key)
421
+ return self._data.setdefault(key, default)
422
+
423
+ def __eq__(self, other: typing.Any) -> bool:
424
+ try:
425
+ other_model = self.__class__(other)
426
+ except Exception:
427
+ return False
428
+ return self._data == other_model._data
429
+
430
+ def __repr__(self) -> str:
431
+ return str(self._data)
432
+
433
+
434
+ def _is_model(obj: typing.Any) -> bool:
435
+ return getattr(obj, "_is_model", False)
436
+
437
+
438
+ def _serialize(o, format: typing.Optional[str] = None): # pylint: disable=too-many-return-statements
439
+ if isinstance(o, list):
440
+ return [_serialize(x, format) for x in o]
441
+ if isinstance(o, dict):
442
+ return {k: _serialize(v, format) for k, v in o.items()}
443
+ if isinstance(o, set):
444
+ return {_serialize(x, format) for x in o}
445
+ if isinstance(o, tuple):
446
+ return tuple(_serialize(x, format) for x in o)
447
+ if isinstance(o, (bytes, bytearray)):
448
+ return _serialize_bytes(o, format)
449
+ if isinstance(o, decimal.Decimal):
450
+ return float(o)
451
+ if isinstance(o, enum.Enum):
452
+ return o.value
453
+ if isinstance(o, int):
454
+ if format == "str":
455
+ return str(o)
456
+ return o
457
+ try:
458
+ # First try datetime.datetime
459
+ return _serialize_datetime(o, format)
460
+ except AttributeError:
461
+ pass
462
+ # Last, try datetime.timedelta
463
+ try:
464
+ return _timedelta_as_isostr(o)
465
+ except AttributeError:
466
+ # This will be raised when it hits value.total_seconds in the method above
467
+ pass
468
+ return o
469
+
470
+
471
+ def _get_rest_field(
472
+ attr_to_rest_field: typing.Dict[str, "_RestField"], rest_name: str
473
+ ) -> typing.Optional["_RestField"]:
474
+ try:
475
+ return next(rf for rf in attr_to_rest_field.values() if rf._rest_name == rest_name)
476
+ except StopIteration:
477
+ return None
478
+
479
+
480
+ def _create_value(rf: typing.Optional["_RestField"], value: typing.Any) -> typing.Any:
481
+ if not rf:
482
+ return _serialize(value, None)
483
+ if rf._is_multipart_file_input:
484
+ return value
485
+ if rf._is_model:
486
+ return _deserialize(rf._type, value)
487
+ if isinstance(value, ET.Element):
488
+ value = _deserialize(rf._type, value)
489
+ return _serialize(value, rf._format)
490
+
491
+
492
+ class Model(_MyMutableMapping):
493
+ _is_model = True
494
+ # label whether current class's _attr_to_rest_field has been calculated
495
+ # could not see _attr_to_rest_field directly because subclass inherits it from parent class
496
+ _calculated: typing.Set[str] = set()
497
+
498
+ def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
499
+ class_name = self.__class__.__name__
500
+ if len(args) > 1:
501
+ raise TypeError(f"{class_name}.__init__() takes 2 positional arguments but {len(args) + 1} were given")
502
+ dict_to_pass = {
503
+ rest_field._rest_name: rest_field._default
504
+ for rest_field in self._attr_to_rest_field.values()
505
+ if rest_field._default is not _UNSET
506
+ }
507
+ if args: # pylint: disable=too-many-nested-blocks
508
+ if isinstance(args[0], ET.Element):
509
+ existed_attr_keys = []
510
+ model_meta = getattr(self, "_xml", {})
511
+
512
+ for rf in self._attr_to_rest_field.values():
513
+ prop_meta = getattr(rf, "_xml", {})
514
+ xml_name = prop_meta.get("name", rf._rest_name)
515
+ xml_ns = prop_meta.get("ns", model_meta.get("ns", None))
516
+ if xml_ns:
517
+ xml_name = "{" + xml_ns + "}" + xml_name
518
+
519
+ # attribute
520
+ if prop_meta.get("attribute", False) and args[0].get(xml_name) is not None:
521
+ existed_attr_keys.append(xml_name)
522
+ dict_to_pass[rf._rest_name] = _deserialize(rf._type, args[0].get(xml_name))
523
+ continue
524
+
525
+ # unwrapped element is array
526
+ if prop_meta.get("unwrapped", False):
527
+ # unwrapped array could either use prop items meta/prop meta
528
+ if prop_meta.get("itemsName"):
529
+ xml_name = prop_meta.get("itemsName")
530
+ xml_ns = prop_meta.get("itemNs")
531
+ if xml_ns:
532
+ xml_name = "{" + xml_ns + "}" + xml_name
533
+ items = args[0].findall(xml_name) # pyright: ignore
534
+ if len(items) > 0:
535
+ existed_attr_keys.append(xml_name)
536
+ dict_to_pass[rf._rest_name] = _deserialize(rf._type, items)
537
+ continue
538
+
539
+ # text element is primitive type
540
+ if prop_meta.get("text", False):
541
+ if args[0].text is not None:
542
+ dict_to_pass[rf._rest_name] = _deserialize(rf._type, args[0].text)
543
+ continue
544
+
545
+ # wrapped element could be normal property or array, it should only have one element
546
+ item = args[0].find(xml_name)
547
+ if item is not None:
548
+ existed_attr_keys.append(xml_name)
549
+ dict_to_pass[rf._rest_name] = _deserialize(rf._type, item)
550
+
551
+ # rest thing is additional properties
552
+ for e in args[0]:
553
+ if e.tag not in existed_attr_keys:
554
+ dict_to_pass[e.tag] = _convert_element(e)
555
+ else:
556
+ dict_to_pass.update(
557
+ {k: _create_value(_get_rest_field(self._attr_to_rest_field, k), v) for k, v in args[0].items()}
558
+ )
559
+ else:
560
+ non_attr_kwargs = [k for k in kwargs if k not in self._attr_to_rest_field]
561
+ if non_attr_kwargs:
562
+ # actual type errors only throw the first wrong keyword arg they see, so following that.
563
+ raise TypeError(f"{class_name}.__init__() got an unexpected keyword argument '{non_attr_kwargs[0]}'")
564
+ dict_to_pass.update(
565
+ {
566
+ self._attr_to_rest_field[k]._rest_name: _create_value(self._attr_to_rest_field[k], v)
567
+ for k, v in kwargs.items()
568
+ if v is not None
569
+ }
570
+ )
571
+ super().__init__(dict_to_pass)
572
+
573
+ def copy(self) -> "Model":
574
+ return Model(self.__dict__)
575
+
576
+ def __new__(cls, *args: typing.Any, **kwargs: typing.Any) -> Self:
577
+ if f"{cls.__module__}.{cls.__qualname__}" not in cls._calculated:
578
+ # we know the last nine classes in mro are going to be 'Model', '_MyMutableMapping', 'MutableMapping',
579
+ # 'Mapping', 'Collection', 'Sized', 'Iterable', 'Container' and 'object'
580
+ mros = cls.__mro__[:-9][::-1] # ignore parents, and reverse the mro order
581
+ attr_to_rest_field: typing.Dict[str, _RestField] = { # map attribute name to rest_field property
582
+ k: v for mro_class in mros for k, v in mro_class.__dict__.items() if k[0] != "_" and hasattr(v, "_type")
583
+ }
584
+ annotations = {
585
+ k: v
586
+ for mro_class in mros
587
+ if hasattr(mro_class, "__annotations__")
588
+ for k, v in mro_class.__annotations__.items()
589
+ }
590
+ for attr, rf in attr_to_rest_field.items():
591
+ rf._module = cls.__module__
592
+ if not rf._type:
593
+ rf._type = rf._get_deserialize_callable_from_annotation(annotations.get(attr, None))
594
+ if not rf._rest_name_input:
595
+ rf._rest_name_input = attr
596
+ cls._attr_to_rest_field: typing.Dict[str, _RestField] = dict(attr_to_rest_field.items())
597
+ cls._calculated.add(f"{cls.__module__}.{cls.__qualname__}")
598
+
599
+ return super().__new__(cls) # pylint: disable=no-value-for-parameter
600
+
601
+ def __init_subclass__(cls, discriminator: typing.Optional[str] = None) -> None:
602
+ for base in cls.__bases__:
603
+ if hasattr(base, "__mapping__"):
604
+ base.__mapping__[discriminator or cls.__name__] = cls # type: ignore
605
+
606
+ @classmethod
607
+ def _get_discriminator(cls, exist_discriminators) -> typing.Optional["_RestField"]:
608
+ for v in cls.__dict__.values():
609
+ if isinstance(v, _RestField) and v._is_discriminator and v._rest_name not in exist_discriminators:
610
+ return v
611
+ return None
612
+
613
+ @classmethod
614
+ def _deserialize(cls, data, exist_discriminators):
615
+ if not hasattr(cls, "__mapping__"):
616
+ return cls(data)
617
+ discriminator = cls._get_discriminator(exist_discriminators)
618
+ if discriminator is None:
619
+ return cls(data)
620
+ exist_discriminators.append(discriminator._rest_name)
621
+ if isinstance(data, ET.Element):
622
+ model_meta = getattr(cls, "_xml", {})
623
+ prop_meta = getattr(discriminator, "_xml", {})
624
+ xml_name = prop_meta.get("name", discriminator._rest_name)
625
+ xml_ns = prop_meta.get("ns", model_meta.get("ns", None))
626
+ if xml_ns:
627
+ xml_name = "{" + xml_ns + "}" + xml_name
628
+
629
+ if data.get(xml_name) is not None:
630
+ discriminator_value = data.get(xml_name)
631
+ else:
632
+ discriminator_value = data.find(xml_name).text # pyright: ignore
633
+ else:
634
+ discriminator_value = data.get(discriminator._rest_name)
635
+ mapped_cls = cls.__mapping__.get(discriminator_value, cls) # pyright: ignore
636
+ return mapped_cls._deserialize(data, exist_discriminators)
637
+
638
+ def as_dict(self, *, exclude_readonly: bool = False) -> typing.Dict[str, typing.Any]:
639
+ """Return a dict that can be turned into json using json.dump.
640
+
641
+ :keyword bool exclude_readonly: Whether to remove the readonly properties.
642
+ :returns: A dict JSON compatible object
643
+ :rtype: dict
644
+ """
645
+
646
+ result = {}
647
+ readonly_props = []
648
+ if exclude_readonly:
649
+ readonly_props = [p._rest_name for p in self._attr_to_rest_field.values() if _is_readonly(p)]
650
+ for k, v in self.items():
651
+ if exclude_readonly and k in readonly_props: # pyright: ignore
652
+ continue
653
+ is_multipart_file_input = False
654
+ try:
655
+ is_multipart_file_input = next(
656
+ rf for rf in self._attr_to_rest_field.values() if rf._rest_name == k
657
+ )._is_multipart_file_input
658
+ except StopIteration:
659
+ pass
660
+ result[k] = v if is_multipart_file_input else Model._as_dict_value(v, exclude_readonly=exclude_readonly)
661
+ return result
662
+
663
+ @staticmethod
664
+ def _as_dict_value(v: typing.Any, exclude_readonly: bool = False) -> typing.Any:
665
+ if v is None or isinstance(v, _Null):
666
+ return None
667
+ if isinstance(v, (list, tuple, set)):
668
+ return type(v)(Model._as_dict_value(x, exclude_readonly=exclude_readonly) for x in v)
669
+ if isinstance(v, dict):
670
+ return {dk: Model._as_dict_value(dv, exclude_readonly=exclude_readonly) for dk, dv in v.items()}
671
+ return v.as_dict(exclude_readonly=exclude_readonly) if hasattr(v, "as_dict") else v
672
+
673
+
674
+ def _deserialize_model(model_deserializer: typing.Optional[typing.Callable], obj):
675
+ if _is_model(obj):
676
+ return obj
677
+ return _deserialize(model_deserializer, obj)
678
+
679
+
680
+ def _deserialize_with_optional(if_obj_deserializer: typing.Optional[typing.Callable], obj):
681
+ if obj is None:
682
+ return obj
683
+ return _deserialize_with_callable(if_obj_deserializer, obj)
684
+
685
+
686
+ def _deserialize_with_union(deserializers, obj):
687
+ for deserializer in deserializers:
688
+ try:
689
+ return _deserialize(deserializer, obj)
690
+ except DeserializationError:
691
+ pass
692
+ raise DeserializationError()
693
+
694
+
695
+ def _deserialize_dict(
696
+ value_deserializer: typing.Optional[typing.Callable],
697
+ module: typing.Optional[str],
698
+ obj: typing.Dict[typing.Any, typing.Any],
699
+ ):
700
+ if obj is None:
701
+ return obj
702
+ if isinstance(obj, ET.Element):
703
+ obj = {child.tag: child for child in obj}
704
+ return {k: _deserialize(value_deserializer, v, module) for k, v in obj.items()}
705
+
706
+
707
+ def _deserialize_multiple_sequence(
708
+ entry_deserializers: typing.List[typing.Optional[typing.Callable]],
709
+ module: typing.Optional[str],
710
+ obj,
711
+ ):
712
+ if obj is None:
713
+ return obj
714
+ return type(obj)(_deserialize(deserializer, entry, module) for entry, deserializer in zip(obj, entry_deserializers))
715
+
716
+
717
+ def _deserialize_sequence(
718
+ deserializer: typing.Optional[typing.Callable],
719
+ module: typing.Optional[str],
720
+ obj,
721
+ ):
722
+ if obj is None:
723
+ return obj
724
+ if isinstance(obj, ET.Element):
725
+ obj = list(obj)
726
+ return type(obj)(_deserialize(deserializer, entry, module) for entry in obj)
727
+
728
+
729
+ def _sorted_annotations(types: typing.List[typing.Any]) -> typing.List[typing.Any]:
730
+ return sorted(
731
+ types,
732
+ key=lambda x: hasattr(x, "__name__") and x.__name__.lower() in ("str", "float", "int", "bool"),
733
+ )
734
+
735
+ def _get_deserialize_callable_from_annotation( # pylint: disable=too-many-return-statements, too-many-branches
736
+ annotation: typing.Any,
737
+ module: typing.Optional[str],
738
+ rf: typing.Optional["_RestField"] = None,
739
+ ) -> typing.Optional[typing.Callable[[typing.Any], typing.Any]]:
740
+ if not annotation:
741
+ return None
742
+
743
+ # is it a type alias?
744
+ if isinstance(annotation, str):
745
+ if module is not None:
746
+ annotation = _get_type_alias_type(module, annotation)
747
+
748
+ # is it a forward ref / in quotes?
749
+ if isinstance(annotation, (str, typing.ForwardRef)):
750
+ try:
751
+ model_name = annotation.__forward_arg__ # type: ignore
752
+ except AttributeError:
753
+ model_name = annotation
754
+ if module is not None:
755
+ annotation = _get_model(module, model_name) # type: ignore
756
+
757
+ try:
758
+ if module and _is_model(annotation):
759
+ if rf:
760
+ rf._is_model = True
761
+
762
+ return functools.partial(_deserialize_model, annotation) # pyright: ignore
763
+ except Exception:
764
+ pass
765
+
766
+ # is it a literal?
767
+ try:
768
+ if annotation.__origin__ is typing.Literal: # pyright: ignore
769
+ return None
770
+ except AttributeError:
771
+ pass
772
+
773
+ # is it optional?
774
+ try:
775
+ if any(a for a in annotation.__args__ if a == type(None)): # pyright: ignore
776
+ if len(annotation.__args__) <= 2: # pyright: ignore
777
+ if_obj_deserializer = _get_deserialize_callable_from_annotation(
778
+ next(a for a in annotation.__args__ if a != type(None)), module, rf # pyright: ignore
779
+ )
780
+
781
+ return functools.partial(_deserialize_with_optional, if_obj_deserializer)
782
+ # the type is Optional[Union[...]], we need to remove the None type from the Union
783
+ annotation_copy = copy.copy(annotation)
784
+ annotation_copy.__args__ = [a for a in annotation_copy.__args__ if a != type(None)] # pyright: ignore
785
+ return _get_deserialize_callable_from_annotation(annotation_copy, module, rf)
786
+ except AttributeError:
787
+ pass
788
+
789
+ # is it union?
790
+ if getattr(annotation, "__origin__", None) is typing.Union:
791
+ # initial ordering is we make `string` the last deserialization option, because it is often them most generic
792
+ deserializers = [
793
+ _get_deserialize_callable_from_annotation(arg, module, rf)
794
+ for arg in _sorted_annotations(annotation.__args__) # pyright: ignore
795
+ ]
796
+
797
+ return functools.partial(_deserialize_with_union, deserializers)
798
+
799
+ try:
800
+ if annotation._name == "Dict": # pyright: ignore
801
+ value_deserializer = _get_deserialize_callable_from_annotation(
802
+ annotation.__args__[1], module, rf # pyright: ignore
803
+ )
804
+
805
+ return functools.partial(
806
+ _deserialize_dict,
807
+ value_deserializer,
808
+ module,
809
+ )
810
+ except (AttributeError, IndexError):
811
+ pass
812
+ try:
813
+ if annotation._name in ["List", "Set", "Tuple", "Sequence"]: # pyright: ignore
814
+ if len(annotation.__args__) > 1: # pyright: ignore
815
+ entry_deserializers = [
816
+ _get_deserialize_callable_from_annotation(dt, module, rf)
817
+ for dt in annotation.__args__ # pyright: ignore
818
+ ]
819
+ return functools.partial(_deserialize_multiple_sequence, entry_deserializers, module)
820
+ deserializer = _get_deserialize_callable_from_annotation(
821
+ annotation.__args__[0], module, rf # pyright: ignore
822
+ )
823
+
824
+ return functools.partial(_deserialize_sequence, deserializer, module)
825
+ except (TypeError, IndexError, AttributeError, SyntaxError):
826
+ pass
827
+
828
+ def _deserialize_default(
829
+ deserializer,
830
+ obj,
831
+ ):
832
+ if obj is None:
833
+ return obj
834
+ try:
835
+ return _deserialize_with_callable(deserializer, obj)
836
+ except Exception:
837
+ pass
838
+ return obj
839
+
840
+ if get_deserializer(annotation, rf):
841
+ return functools.partial(_deserialize_default, get_deserializer(annotation, rf))
842
+
843
+ return functools.partial(_deserialize_default, annotation)
844
+
845
+
846
+ def _deserialize_with_callable(
847
+ deserializer: typing.Optional[typing.Callable[[typing.Any], typing.Any]],
848
+ value: typing.Any,
849
+ ): # pylint: disable=too-many-return-statements
850
+ try:
851
+ if value is None or isinstance(value, _Null):
852
+ return None
853
+ if isinstance(value, ET.Element):
854
+ if deserializer is str:
855
+ return value.text or ""
856
+ if deserializer is int:
857
+ return int(value.text) if value.text else None
858
+ if deserializer is float:
859
+ return float(value.text) if value.text else None
860
+ if deserializer is bool:
861
+ return value.text == "true" if value.text else None
862
+ if deserializer is None:
863
+ return value
864
+ if deserializer in [int, float, bool]:
865
+ return deserializer(value)
866
+ if isinstance(deserializer, CaseInsensitiveEnumMeta):
867
+ try:
868
+ return deserializer(value)
869
+ except ValueError:
870
+ # for unknown value, return raw value
871
+ return value
872
+ if isinstance(deserializer, type) and issubclass(deserializer, Model):
873
+ return deserializer._deserialize(value, [])
874
+ return typing.cast(typing.Callable[[typing.Any], typing.Any], deserializer)(value)
875
+ except Exception as e:
876
+ raise DeserializationError() from e
877
+
878
+
879
+ def _deserialize(
880
+ deserializer: typing.Any,
881
+ value: typing.Any,
882
+ module: typing.Optional[str] = None,
883
+ rf: typing.Optional["_RestField"] = None,
884
+ format: typing.Optional[str] = None,
885
+ ) -> typing.Any:
886
+ if isinstance(value, PipelineResponse):
887
+ value = value.http_response.json()
888
+ if rf is None and format:
889
+ rf = _RestField(format=format)
890
+ if not isinstance(deserializer, functools.partial):
891
+ deserializer = _get_deserialize_callable_from_annotation(deserializer, module, rf)
892
+ return _deserialize_with_callable(deserializer, value)
893
+
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
+
912
+ class _RestField:
913
+ def __init__(
914
+ self,
915
+ *,
916
+ name: typing.Optional[str] = None,
917
+ type: typing.Optional[typing.Callable] = None, # pylint: disable=redefined-builtin
918
+ is_discriminator: bool = False,
919
+ visibility: typing.Optional[typing.List[str]] = None,
920
+ default: typing.Any = _UNSET,
921
+ format: typing.Optional[str] = None,
922
+ is_multipart_file_input: bool = False,
923
+ xml: typing.Optional[typing.Dict[str, typing.Any]] = None,
924
+ ):
925
+ self._type = type
926
+ self._rest_name_input = name
927
+ self._module: typing.Optional[str] = None
928
+ self._is_discriminator = is_discriminator
929
+ self._visibility = visibility
930
+ self._is_model = False
931
+ self._default = default
932
+ self._format = format
933
+ self._is_multipart_file_input = is_multipart_file_input
934
+ self._xml = xml if xml is not None else {}
935
+
936
+ @property
937
+ def _class_type(self) -> typing.Any:
938
+ return getattr(self._type, "args", [None])[0]
939
+
940
+ @property
941
+ def _rest_name(self) -> str:
942
+ if self._rest_name_input is None:
943
+ raise ValueError("Rest name was never set")
944
+ return self._rest_name_input
945
+
946
+ def __get__(self, obj: Model, type=None): # pylint: disable=redefined-builtin
947
+ # by this point, type and rest_name will have a value bc we default
948
+ # them in __new__ of the Model class
949
+ item = obj.get(self._rest_name)
950
+ if item is None:
951
+ return item
952
+ if self._is_model:
953
+ return item
954
+ return _deserialize(self._type, _serialize(item, self._format), rf=self)
955
+
956
+ def __set__(self, obj: Model, value) -> None:
957
+ if value is None:
958
+ # we want to wipe out entries if users set attr to None
959
+ try:
960
+ obj.__delitem__(self._rest_name)
961
+ except KeyError:
962
+ pass
963
+ return
964
+ if self._is_model:
965
+ if not _is_model(value):
966
+ value = _deserialize(self._type, value)
967
+ obj.__setitem__(self._rest_name, value)
968
+ return
969
+ obj.__setitem__(self._rest_name, _serialize(value, self._format))
970
+
971
+ def _get_deserialize_callable_from_annotation(
972
+ self, annotation: typing.Any
973
+ ) -> typing.Optional[typing.Callable[[typing.Any], typing.Any]]:
974
+ return _get_deserialize_callable_from_annotation(annotation, self._module, self)
975
+
976
+
977
+ def rest_field(
978
+ *,
979
+ name: typing.Optional[str] = None,
980
+ type: typing.Optional[typing.Callable] = None, # pylint: disable=redefined-builtin
981
+ visibility: typing.Optional[typing.List[str]] = None,
982
+ default: typing.Any = _UNSET,
983
+ format: typing.Optional[str] = None,
984
+ is_multipart_file_input: bool = False,
985
+ xml: typing.Optional[typing.Dict[str, typing.Any]] = None,
986
+ ) -> typing.Any:
987
+ return _RestField(
988
+ name=name,
989
+ type=type,
990
+ visibility=visibility,
991
+ default=default,
992
+ format=format,
993
+ is_multipart_file_input=is_multipart_file_input,
994
+ xml=xml,
995
+ )
996
+
997
+
998
+ def rest_discriminator(
999
+ *,
1000
+ name: typing.Optional[str] = None,
1001
+ type: typing.Optional[typing.Callable] = None, # pylint: disable=redefined-builtin
1002
+ visibility: typing.Optional[typing.List[str]] = None,
1003
+ xml: typing.Optional[typing.Dict[str, typing.Any]] = None,
1004
+ ) -> typing.Any:
1005
+ return _RestField(name=name, type=type, is_discriminator=True, visibility=visibility, xml=xml)
1006
+
1007
+
1008
+ def serialize_xml(model: Model, exclude_readonly: bool = False) -> str:
1009
+ """Serialize a model to XML.
1010
+
1011
+ :param Model model: The model to serialize.
1012
+ :param bool exclude_readonly: Whether to exclude readonly properties.
1013
+ :returns: The XML representation of the model.
1014
+ :rtype: str
1015
+ """
1016
+ return ET.tostring(_get_element(model, exclude_readonly), encoding="unicode") # type: ignore
1017
+
1018
+
1019
+ def _get_element(
1020
+ o: typing.Any,
1021
+ exclude_readonly: bool = False,
1022
+ parent_meta: typing.Optional[typing.Dict[str, typing.Any]] = None,
1023
+ wrapped_element: typing.Optional[ET.Element] = None,
1024
+ ) -> typing.Union[ET.Element, typing.List[ET.Element]]:
1025
+ if _is_model(o):
1026
+ model_meta = getattr(o, "_xml", {})
1027
+
1028
+ # if prop is a model, then use the prop element directly, else generate a wrapper of model
1029
+ if wrapped_element is None:
1030
+ wrapped_element = _create_xml_element(
1031
+ model_meta.get("name", o.__class__.__name__),
1032
+ model_meta.get("prefix"),
1033
+ model_meta.get("ns"),
1034
+ )
1035
+
1036
+ readonly_props = []
1037
+ if exclude_readonly:
1038
+ readonly_props = [p._rest_name for p in o._attr_to_rest_field.values() if _is_readonly(p)]
1039
+
1040
+ for k, v in o.items():
1041
+ # do not serialize readonly properties
1042
+ if exclude_readonly and k in readonly_props:
1043
+ continue
1044
+
1045
+ prop_rest_field = _get_rest_field(o._attr_to_rest_field, k)
1046
+ if prop_rest_field:
1047
+ prop_meta = getattr(prop_rest_field, "_xml").copy()
1048
+ # use the wire name as xml name if no specific name is set
1049
+ if prop_meta.get("name") is None:
1050
+ prop_meta["name"] = k
1051
+ else:
1052
+ # additional properties will not have rest field, use the wire name as xml name
1053
+ prop_meta = {"name": k}
1054
+
1055
+ # if no ns for prop, use model's
1056
+ if prop_meta.get("ns") is None and model_meta.get("ns"):
1057
+ prop_meta["ns"] = model_meta.get("ns")
1058
+ prop_meta["prefix"] = model_meta.get("prefix")
1059
+
1060
+ if prop_meta.get("unwrapped", False):
1061
+ # unwrapped could only set on array
1062
+ wrapped_element.extend(_get_element(v, exclude_readonly, prop_meta))
1063
+ elif prop_meta.get("text", False):
1064
+ # text could only set on primitive type
1065
+ wrapped_element.text = _get_primitive_type_value(v)
1066
+ elif prop_meta.get("attribute", False):
1067
+ xml_name = prop_meta.get("name", k)
1068
+ if prop_meta.get("ns"):
1069
+ ET.register_namespace(prop_meta.get("prefix"), prop_meta.get("ns")) # pyright: ignore
1070
+ xml_name = "{" + prop_meta.get("ns") + "}" + xml_name # pyright: ignore
1071
+ # attribute should be primitive type
1072
+ wrapped_element.set(xml_name, _get_primitive_type_value(v))
1073
+ else:
1074
+ # other wrapped prop element
1075
+ wrapped_element.append(_get_wrapped_element(v, exclude_readonly, prop_meta))
1076
+ return wrapped_element
1077
+ if isinstance(o, list):
1078
+ return [_get_element(x, exclude_readonly, parent_meta) for x in o] # type: ignore
1079
+ if isinstance(o, dict):
1080
+ result = []
1081
+ for k, v in o.items():
1082
+ result.append(
1083
+ _get_wrapped_element(
1084
+ v,
1085
+ exclude_readonly,
1086
+ {
1087
+ "name": k,
1088
+ "ns": parent_meta.get("ns") if parent_meta else None,
1089
+ "prefix": parent_meta.get("prefix") if parent_meta else None,
1090
+ },
1091
+ )
1092
+ )
1093
+ return result
1094
+
1095
+ # primitive case need to create element based on parent_meta
1096
+ if parent_meta:
1097
+ return _get_wrapped_element(
1098
+ o,
1099
+ exclude_readonly,
1100
+ {
1101
+ "name": parent_meta.get("itemsName", parent_meta.get("name")),
1102
+ "prefix": parent_meta.get("itemsPrefix", parent_meta.get("prefix")),
1103
+ "ns": parent_meta.get("itemsNs", parent_meta.get("ns")),
1104
+ },
1105
+ )
1106
+
1107
+ raise ValueError("Could not serialize value into xml: " + o)
1108
+
1109
+
1110
+ def _get_wrapped_element(
1111
+ v: typing.Any,
1112
+ exclude_readonly: bool,
1113
+ meta: typing.Optional[typing.Dict[str, typing.Any]],
1114
+ ) -> ET.Element:
1115
+ wrapped_element = _create_xml_element(
1116
+ meta.get("name") if meta else None, meta.get("prefix") if meta else None, meta.get("ns") if meta else None
1117
+ )
1118
+ if isinstance(v, (dict, list)):
1119
+ wrapped_element.extend(_get_element(v, exclude_readonly, meta))
1120
+ elif _is_model(v):
1121
+ _get_element(v, exclude_readonly, meta, wrapped_element)
1122
+ else:
1123
+ wrapped_element.text = _get_primitive_type_value(v)
1124
+ return wrapped_element
1125
+
1126
+
1127
+ def _get_primitive_type_value(v) -> str:
1128
+ if v is True:
1129
+ return "true"
1130
+ if v is False:
1131
+ return "false"
1132
+ if isinstance(v, _Null):
1133
+ return ""
1134
+ return str(v)
1135
+
1136
+
1137
+ def _create_xml_element(tag, prefix=None, ns=None):
1138
+ if prefix and ns:
1139
+ ET.register_namespace(prefix, ns)
1140
+ if ns:
1141
+ return ET.Element("{" + ns + "}" + tag)
1142
+ return ET.Element(tag)
1143
+
1144
+
1145
+ def _deserialize_xml(
1146
+ deserializer: typing.Any,
1147
+ value: str,
1148
+ ) -> typing.Any:
1149
+ element = ET.fromstring(value) # nosec
1150
+ return _deserialize(deserializer, element)
1151
+
1152
+
1153
+ def _convert_element(e: ET.Element):
1154
+ # dict case
1155
+ if len(e.attrib) > 0 or len({child.tag for child in e}) > 1:
1156
+ dict_result: typing.Dict[str, typing.Any] = {}
1157
+ for child in e:
1158
+ if dict_result.get(child.tag) is not None:
1159
+ if isinstance(dict_result[child.tag], list):
1160
+ dict_result[child.tag].append(_convert_element(child))
1161
+ else:
1162
+ dict_result[child.tag] = [dict_result[child.tag], _convert_element(child)]
1163
+ else:
1164
+ dict_result[child.tag] = _convert_element(child)
1165
+ dict_result.update(e.attrib)
1166
+ return dict_result
1167
+ # array case
1168
+ if len(e) > 0:
1169
+ array_result: typing.List[typing.Any] = []
1170
+ for child in e:
1171
+ array_result.append(_convert_element(child))
1172
+ return array_result
1173
+ # primitive case
1174
+ return e.text