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