azure-ai-textanalytics 5.3.0b1__py3-none-any.whl → 6.0.0b1__py3-none-any.whl

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.

Potentially problematic release.


This version of azure-ai-textanalytics might be problematic. Click here for more details.

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