@azure-tools/typespec-python 0.24.2 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/dist/src/code-model.d.ts.map +1 -1
  2. package/dist/src/code-model.js +1 -4
  3. package/dist/src/code-model.js.map +1 -1
  4. package/dist/src/emitter.d.ts.map +1 -1
  5. package/dist/src/emitter.js +5 -4
  6. package/dist/src/emitter.js.map +1 -1
  7. package/dist/src/external-process.d.ts +0 -9
  8. package/dist/src/external-process.d.ts.map +1 -1
  9. package/dist/src/external-process.js +1 -25
  10. package/dist/src/external-process.js.map +1 -1
  11. package/dist/src/types.d.ts.map +1 -1
  12. package/dist/src/types.js +2 -0
  13. package/dist/src/types.js.map +1 -1
  14. package/generator/LICENSE +21 -0
  15. package/generator/README.md +1 -0
  16. package/generator/dev_requirements.txt +5 -0
  17. package/generator/pygen/__init__.py +107 -0
  18. package/generator/pygen/_version.py +7 -0
  19. package/generator/pygen/black.py +71 -0
  20. package/generator/pygen/codegen/__init__.py +334 -0
  21. package/generator/pygen/codegen/_utils.py +16 -0
  22. package/generator/pygen/codegen/models/__init__.py +202 -0
  23. package/generator/pygen/codegen/models/base.py +186 -0
  24. package/generator/pygen/codegen/models/base_builder.py +119 -0
  25. package/generator/pygen/codegen/models/client.py +429 -0
  26. package/generator/pygen/codegen/models/code_model.py +239 -0
  27. package/generator/pygen/codegen/models/combined_type.py +149 -0
  28. package/generator/pygen/codegen/models/constant_type.py +129 -0
  29. package/generator/pygen/codegen/models/credential_types.py +221 -0
  30. package/generator/pygen/codegen/models/dictionary_type.py +127 -0
  31. package/generator/pygen/codegen/models/enum_type.py +238 -0
  32. package/generator/pygen/codegen/models/imports.py +291 -0
  33. package/generator/pygen/codegen/models/list_type.py +143 -0
  34. package/generator/pygen/codegen/models/lro_operation.py +143 -0
  35. package/generator/pygen/codegen/models/lro_paging_operation.py +32 -0
  36. package/generator/pygen/codegen/models/model_type.py +361 -0
  37. package/generator/pygen/codegen/models/operation.py +518 -0
  38. package/generator/pygen/codegen/models/operation_group.py +184 -0
  39. package/generator/pygen/codegen/models/paging_operation.py +156 -0
  40. package/generator/pygen/codegen/models/parameter.py +402 -0
  41. package/generator/pygen/codegen/models/parameter_list.py +390 -0
  42. package/generator/pygen/codegen/models/primitive_types.py +626 -0
  43. package/generator/pygen/codegen/models/property.py +175 -0
  44. package/generator/pygen/codegen/models/request_builder.py +189 -0
  45. package/generator/pygen/codegen/models/request_builder_parameter.py +115 -0
  46. package/generator/pygen/codegen/models/response.py +348 -0
  47. package/generator/pygen/codegen/models/utils.py +23 -0
  48. package/generator/pygen/codegen/serializers/__init__.py +570 -0
  49. package/generator/pygen/codegen/serializers/base_serializer.py +21 -0
  50. package/generator/pygen/codegen/serializers/builder_serializer.py +1454 -0
  51. package/generator/pygen/codegen/serializers/client_serializer.py +295 -0
  52. package/generator/pygen/codegen/serializers/enum_serializer.py +15 -0
  53. package/generator/pygen/codegen/serializers/general_serializer.py +212 -0
  54. package/generator/pygen/codegen/serializers/import_serializer.py +127 -0
  55. package/generator/pygen/codegen/serializers/metadata_serializer.py +198 -0
  56. package/generator/pygen/codegen/serializers/model_init_serializer.py +33 -0
  57. package/generator/pygen/codegen/serializers/model_serializer.py +287 -0
  58. package/generator/pygen/codegen/serializers/operation_groups_serializer.py +89 -0
  59. package/generator/pygen/codegen/serializers/operations_init_serializer.py +44 -0
  60. package/generator/pygen/codegen/serializers/parameter_serializer.py +221 -0
  61. package/generator/pygen/codegen/serializers/patch_serializer.py +19 -0
  62. package/generator/pygen/codegen/serializers/request_builders_serializer.py +52 -0
  63. package/generator/pygen/codegen/serializers/sample_serializer.py +163 -0
  64. package/generator/pygen/codegen/serializers/test_serializer.py +287 -0
  65. package/generator/pygen/codegen/serializers/types_serializer.py +31 -0
  66. package/generator/pygen/codegen/serializers/utils.py +68 -0
  67. package/generator/pygen/codegen/templates/client.py.jinja2 +37 -0
  68. package/generator/pygen/codegen/templates/client_container.py.jinja2 +12 -0
  69. package/generator/pygen/codegen/templates/config.py.jinja2 +73 -0
  70. package/generator/pygen/codegen/templates/config_container.py.jinja2 +16 -0
  71. package/generator/pygen/codegen/templates/conftest.py.jinja2 +28 -0
  72. package/generator/pygen/codegen/templates/enum.py.jinja2 +13 -0
  73. package/generator/pygen/codegen/templates/enum_container.py.jinja2 +10 -0
  74. package/generator/pygen/codegen/templates/init.py.jinja2 +24 -0
  75. package/generator/pygen/codegen/templates/keywords.jinja2 +19 -0
  76. package/generator/pygen/codegen/templates/lro_operation.py.jinja2 +16 -0
  77. package/generator/pygen/codegen/templates/lro_paging_operation.py.jinja2 +18 -0
  78. package/generator/pygen/codegen/templates/macros.jinja2 +12 -0
  79. package/generator/pygen/codegen/templates/metadata.json.jinja2 +167 -0
  80. package/generator/pygen/codegen/templates/model_base.py.jinja2 +899 -0
  81. package/generator/pygen/codegen/templates/model_container.py.jinja2 +13 -0
  82. package/generator/pygen/codegen/templates/model_dpg.py.jinja2 +92 -0
  83. package/generator/pygen/codegen/templates/model_init.py.jinja2 +28 -0
  84. package/generator/pygen/codegen/templates/model_msrest.py.jinja2 +92 -0
  85. package/generator/pygen/codegen/templates/operation.py.jinja2 +21 -0
  86. package/generator/pygen/codegen/templates/operation_group.py.jinja2 +75 -0
  87. package/generator/pygen/codegen/templates/operation_groups_container.py.jinja2 +20 -0
  88. package/generator/pygen/codegen/templates/operation_tools.jinja2 +74 -0
  89. package/generator/pygen/codegen/templates/operations_folder_init.py.jinja2 +17 -0
  90. package/generator/pygen/codegen/templates/packaging_templates/CHANGELOG.md.jinja2 +6 -0
  91. package/generator/pygen/codegen/templates/packaging_templates/LICENSE.jinja2 +21 -0
  92. package/generator/pygen/codegen/templates/packaging_templates/MANIFEST.in.jinja2 +8 -0
  93. package/generator/pygen/codegen/templates/packaging_templates/README.md.jinja2 +107 -0
  94. package/generator/pygen/codegen/templates/packaging_templates/dev_requirements.txt.jinja2 +9 -0
  95. package/generator/pygen/codegen/templates/packaging_templates/setup.py.jinja2 +108 -0
  96. package/generator/pygen/codegen/templates/paging_operation.py.jinja2 +21 -0
  97. package/generator/pygen/codegen/templates/patch.py.jinja2 +19 -0
  98. package/generator/pygen/codegen/templates/pkgutil_init.py.jinja2 +1 -0
  99. package/generator/pygen/codegen/templates/request_builder.py.jinja2 +28 -0
  100. package/generator/pygen/codegen/templates/request_builders.py.jinja2 +10 -0
  101. package/generator/pygen/codegen/templates/rest_init.py.jinja2 +12 -0
  102. package/generator/pygen/codegen/templates/sample.py.jinja2 +44 -0
  103. package/generator/pygen/codegen/templates/serialization.py.jinja2 +2006 -0
  104. package/generator/pygen/codegen/templates/test.py.jinja2 +50 -0
  105. package/generator/pygen/codegen/templates/testpreparer.py.jinja2 +26 -0
  106. package/generator/pygen/codegen/templates/types.py.jinja2 +8 -0
  107. package/generator/pygen/codegen/templates/validation.py.jinja2 +38 -0
  108. package/generator/pygen/codegen/templates/vendor.py.jinja2 +98 -0
  109. package/generator/pygen/codegen/templates/version.py.jinja2 +4 -0
  110. package/generator/pygen/m2r.py +65 -0
  111. package/generator/pygen/postprocess/__init__.py +183 -0
  112. package/generator/pygen/postprocess/get_all.py +19 -0
  113. package/generator/pygen/postprocess/venvtools.py +77 -0
  114. package/generator/pygen/preprocess/__init__.py +503 -0
  115. package/generator/pygen/preprocess/helpers.py +27 -0
  116. package/generator/pygen/preprocess/python_mappings.py +222 -0
  117. package/generator/pygen/utils.py +149 -0
  118. package/generator/pygen.egg-info/PKG-INFO +25 -0
  119. package/generator/pygen.egg-info/SOURCES.txt +66 -0
  120. package/generator/pygen.egg-info/dependency_links.txt +1 -0
  121. package/generator/pygen.egg-info/requires.txt +4 -0
  122. package/generator/pygen.egg-info/top_level.txt +1 -0
  123. package/generator/requirements.txt +12 -0
  124. package/generator/setup.py +55 -0
  125. package/package.json +10 -7
  126. package/scripts/__pycache__/venvtools.cpython-38.pyc +0 -0
  127. package/scripts/install.py +49 -0
  128. package/scripts/prepare.py +38 -0
  129. package/scripts/run-python3.cjs +22 -0
  130. package/scripts/run_tsp.py +40 -0
  131. package/scripts/system-requirements.cjs +180 -0
  132. package/scripts/venvtools.py +81 -0
@@ -0,0 +1,899 @@
1
+ # coding=utf-8
2
+ # --------------------------------------------------------------------------
3
+ # Copyright (c) {{ code_model.options["company_name"] }} Corporation. All rights reserved.
4
+ # Licensed under the MIT License. See License.txt in the project root for
5
+ # license information.
6
+ # --------------------------------------------------------------------------
7
+ # pylint: disable=protected-access, arguments-differ, signature-differs, broad-except
8
+
9
+ import copy
10
+ import calendar
11
+ import decimal
12
+ import functools
13
+ import sys
14
+ import logging
15
+ import base64
16
+ import re
17
+ import typing
18
+ import enum
19
+ import email.utils
20
+ from datetime import datetime, date, time, timedelta, timezone
21
+ from json import JSONEncoder
22
+ from typing_extensions import Self
23
+ import isodate
24
+ from {{ code_model.core_library }}.exceptions import DeserializationError
25
+ from {{ code_model.core_library }}{{ "" if code_model.is_azure_flavor else ".utils" }} import CaseInsensitiveEnumMeta
26
+ from {{ code_model.core_library }}.{{ "" if code_model.is_azure_flavor else "runtime." }}pipeline import PipelineResponse
27
+ from {{ code_model.core_library }}.serialization import _Null
28
+
29
+ if sys.version_info >= (3, 9):
30
+ from collections.abc import MutableMapping
31
+ else:
32
+ from typing import MutableMapping
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"] # pylint: disable=protected-access
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
+ _DESERIALIZE_MAPPING = {
290
+ datetime: _deserialize_datetime,
291
+ date: _deserialize_date,
292
+ time: _deserialize_time,
293
+ bytes: _deserialize_bytes,
294
+ bytearray: _deserialize_bytes,
295
+ timedelta: _deserialize_duration,
296
+ typing.Any: lambda x: x,
297
+ decimal.Decimal: _deserialize_decimal,
298
+ }
299
+
300
+ _DESERIALIZE_MAPPING_WITHFORMAT = {
301
+ "rfc3339": _deserialize_datetime,
302
+ "rfc7231": _deserialize_datetime_rfc7231,
303
+ "unix-timestamp": _deserialize_datetime_unix_timestamp,
304
+ "base64": _deserialize_bytes,
305
+ "base64url": _deserialize_bytes_base64,
306
+ }
307
+
308
+
309
+ def get_deserializer(annotation: typing.Any, rf: typing.Optional["_RestField"] = None):
310
+ if rf and rf._format:
311
+ return _DESERIALIZE_MAPPING_WITHFORMAT.get(rf._format)
312
+ return _DESERIALIZE_MAPPING.get(annotation)
313
+
314
+
315
+ def _get_type_alias_type(module_name: str, alias_name: str):
316
+ types = {
317
+ k: v
318
+ for k, v in sys.modules[module_name].__dict__.items()
319
+ if isinstance(v, typing._GenericAlias) # type: ignore
320
+ }
321
+ if alias_name not in types:
322
+ return alias_name
323
+ return types[alias_name]
324
+
325
+
326
+ def _get_model(module_name: str, model_name: str):
327
+ models = {
328
+ k: v
329
+ for k, v in sys.modules[module_name].__dict__.items()
330
+ if isinstance(v, type)
331
+ }
332
+ module_end = module_name.rsplit(".", 1)[0]
333
+ models.update({
334
+ k: v
335
+ for k, v in sys.modules[module_end].__dict__.items()
336
+ if isinstance(v, type)
337
+ })
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]): # pylint: disable=unsubscriptable-object
349
+ def __init__(self, data: typing.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
+ return self._data.keys()
375
+
376
+ def values(self) -> typing.ValuesView[typing.Any]:
377
+ return self._data.values()
378
+
379
+ def items(self) -> typing.ItemsView[str, typing.Any]:
380
+ return self._data.items()
381
+
382
+ def get(self, key: str, default: typing.Any = None) -> typing.Any:
383
+ try:
384
+ return self[key]
385
+ except KeyError:
386
+ return default
387
+
388
+ @typing.overload
389
+ def pop(self, key: str) -> typing.Any:
390
+ ...
391
+
392
+ @typing.overload
393
+ def pop(self, key: str, default: _T) -> _T:
394
+ ...
395
+
396
+ @typing.overload
397
+ def pop(self, key: str, default: typing.Any) -> typing.Any:
398
+ ...
399
+
400
+ def pop(self, key: str, default: typing.Any = _UNSET) -> typing.Any:
401
+ if default is _UNSET:
402
+ return self._data.pop(key)
403
+ return self._data.pop(key, default)
404
+
405
+ def popitem(self) -> typing.Tuple[str, typing.Any]:
406
+ return self._data.popitem()
407
+
408
+ def clear(self) -> None:
409
+ self._data.clear()
410
+
411
+ def update(self, *args: typing.Any, **kwargs: typing.Any) -> None:
412
+ self._data.update(*args, **kwargs)
413
+
414
+ @typing.overload
415
+ def setdefault(self, key: str, default: None = None) -> None:
416
+ ...
417
+
418
+ @typing.overload
419
+ def setdefault(self, key: str, default: typing.Any) -> typing.Any:
420
+ ...
421
+
422
+ def setdefault(self, key: str, default: typing.Any = _UNSET) -> typing.Any:
423
+ if default is _UNSET:
424
+ return self._data.setdefault(key)
425
+ return self._data.setdefault(key, default)
426
+
427
+ def __eq__(self, other: typing.Any) -> bool:
428
+ try:
429
+ other_model = self.__class__(other)
430
+ except Exception:
431
+ return False
432
+ return self._data == other_model._data
433
+
434
+ def __repr__(self) -> str:
435
+ return str(self._data)
436
+
437
+
438
+ def _is_model(obj: typing.Any) -> bool:
439
+ return getattr(obj, "_is_model", False)
440
+
441
+
442
+ def _serialize(o, format: typing.Optional[str] = None): # pylint: disable=too-many-return-statements
443
+ if isinstance(o, list):
444
+ return [_serialize(x, format) for x in o]
445
+ if isinstance(o, dict):
446
+ return {k: _serialize(v, format) for k, v in o.items()}
447
+ if isinstance(o, set):
448
+ return {_serialize(x, format) for x in o}
449
+ if isinstance(o, tuple):
450
+ return tuple(_serialize(x, format) for x in o)
451
+ if isinstance(o, (bytes, bytearray)):
452
+ return _serialize_bytes(o, format)
453
+ if isinstance(o, decimal.Decimal):
454
+ return float(o)
455
+ if isinstance(o, enum.Enum):
456
+ return o.value
457
+ try:
458
+ # First try datetime.datetime
459
+ return _serialize_datetime(o, format)
460
+ except AttributeError:
461
+ pass
462
+ # Last, try datetime.timedelta
463
+ try:
464
+ return _timedelta_as_isostr(o)
465
+ except AttributeError:
466
+ # This will be raised when it hits value.total_seconds in the method above
467
+ pass
468
+ return o
469
+
470
+
471
+ def _get_rest_field(
472
+ attr_to_rest_field: typing.Dict[str, "_RestField"], rest_name: str
473
+ ) -> typing.Optional["_RestField"]:
474
+ try:
475
+ return next(rf for rf in attr_to_rest_field.values() if rf._rest_name == rest_name)
476
+ except StopIteration:
477
+ return None
478
+
479
+
480
+ def _create_value(rf: typing.Optional["_RestField"], value: typing.Any) -> typing.Any:
481
+ if not rf:
482
+ return _serialize(value, None)
483
+ if rf._is_multipart_file_input:
484
+ return value
485
+ if rf._is_model:
486
+ return _deserialize(rf._type, value)
487
+ return _serialize(value, rf._format)
488
+
489
+
490
+ class Model(_MyMutableMapping):
491
+ _is_model = True
492
+
493
+ def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
494
+ class_name = self.__class__.__name__
495
+ if len(args) > 1:
496
+ raise TypeError(f"{class_name}.__init__() takes 2 positional arguments but {len(args) + 1} were given")
497
+ dict_to_pass = {
498
+ rest_field._rest_name: rest_field._default
499
+ for rest_field in self._attr_to_rest_field.values()
500
+ if rest_field._default is not _UNSET
501
+ }
502
+ if args:
503
+ dict_to_pass.update(
504
+ {k: _create_value(_get_rest_field(self._attr_to_rest_field, k), v) for k, v in args[0].items()}
505
+ )
506
+ else:
507
+ non_attr_kwargs = [k for k in kwargs if k not in self._attr_to_rest_field]
508
+ if non_attr_kwargs:
509
+ # actual type errors only throw the first wrong keyword arg they see, so following that.
510
+ raise TypeError(f"{class_name}.__init__() got an unexpected keyword argument '{non_attr_kwargs[0]}'")
511
+ dict_to_pass.update(
512
+ {
513
+ self._attr_to_rest_field[k]._rest_name: _create_value(self._attr_to_rest_field[k], v)
514
+ for k, v in kwargs.items()
515
+ if v is not None
516
+ }
517
+ )
518
+ super().__init__(dict_to_pass)
519
+
520
+ def copy(self) -> "Model":
521
+ return Model(self.__dict__)
522
+
523
+ def __new__(cls, *args: typing.Any, **kwargs: typing.Any) -> Self: # pylint: disable=unused-argument
524
+ # we know the last three classes in mro are going to be 'Model', 'dict', and 'object'
525
+ mros = cls.__mro__[:-3][::-1] # ignore model, dict, and object parents, and reverse the mro order
526
+ attr_to_rest_field: typing.Dict[str, _RestField] = { # map attribute name to rest_field property
527
+ k: v for mro_class in mros for k, v in mro_class.__dict__.items() if k[0] != "_" and hasattr(v, "_type")
528
+ }
529
+ annotations = {
530
+ k: v
531
+ for mro_class in mros
532
+ if hasattr(mro_class, "__annotations__") # pylint: disable=no-member
533
+ for k, v in mro_class.__annotations__.items() # pylint: disable=no-member
534
+ }
535
+ for attr, rf in attr_to_rest_field.items():
536
+ rf._module = cls.__module__
537
+ if not rf._type:
538
+ rf._type = rf._get_deserialize_callable_from_annotation(annotations.get(attr, None))
539
+ if not rf._rest_name_input:
540
+ rf._rest_name_input = attr
541
+ cls._attr_to_rest_field: typing.Dict[str, _RestField] = dict(attr_to_rest_field.items())
542
+
543
+ return super().__new__(cls) # pylint: disable=no-value-for-parameter
544
+
545
+ def __init_subclass__(cls, discriminator: typing.Optional[str] = None) -> None:
546
+ for base in cls.__bases__:
547
+ if hasattr(base, "__mapping__"): # pylint: disable=no-member
548
+ base.__mapping__[discriminator or cls.__name__] = cls # type: ignore # pylint: disable=no-member
549
+
550
+ @classmethod
551
+ def _get_discriminator(cls, exist_discriminators) -> typing.Optional[str]:
552
+ for v in cls.__dict__.values():
553
+ if isinstance(v, _RestField) and v._is_discriminator and v._rest_name not in exist_discriminators: # pylint: disable=protected-access
554
+ return v._rest_name # pylint: disable=protected-access
555
+ return None
556
+
557
+ @classmethod
558
+ def _deserialize(cls, data, exist_discriminators):
559
+ if not hasattr(cls, "__mapping__"): # pylint: disable=no-member
560
+ return cls(data)
561
+ discriminator = cls._get_discriminator(exist_discriminators)
562
+ exist_discriminators.append(discriminator)
563
+ mapped_cls = cls.__mapping__.get(
564
+ data.get(discriminator), cls
565
+ ) # pyright: ignore # pylint: disable=no-member
566
+ if mapped_cls == cls:
567
+ return cls(data)
568
+ return mapped_cls._deserialize(data, exist_discriminators) # pylint: disable=protected-access
569
+
570
+ def as_dict(self, *, exclude_readonly: bool = False) -> typing.Dict[str, typing.Any]:
571
+ """Return a dict that can be JSONify using json.dump.
572
+
573
+ :keyword bool exclude_readonly: Whether to remove the readonly properties.
574
+ :returns: A dict JSON compatible object
575
+ :rtype: dict
576
+ """
577
+
578
+ result = {}
579
+ if exclude_readonly:
580
+ readonly_props = [p._rest_name for p in self._attr_to_rest_field.values() if _is_readonly(p)]
581
+ for k, v in self.items():
582
+ if exclude_readonly and k in readonly_props: # pyright: ignore
583
+ continue
584
+ is_multipart_file_input = False
585
+ try:
586
+ is_multipart_file_input = next(rf for rf in self._attr_to_rest_field.values() if rf._rest_name == k)._is_multipart_file_input
587
+ except StopIteration:
588
+ pass
589
+ result[k] = v if is_multipart_file_input else Model._as_dict_value(v, exclude_readonly=exclude_readonly)
590
+ return result
591
+
592
+ @staticmethod
593
+ def _as_dict_value(v: typing.Any, exclude_readonly: bool = False) -> typing.Any:
594
+ if v is None or isinstance(v, _Null):
595
+ return None
596
+ if isinstance(v, (list, tuple, set)):
597
+ return type(v)(
598
+ Model._as_dict_value(x, exclude_readonly=exclude_readonly)
599
+ for x in v
600
+ )
601
+ if isinstance(v, dict):
602
+ return {
603
+ dk: Model._as_dict_value(dv, exclude_readonly=exclude_readonly)
604
+ for dk, dv in v.items()
605
+ }
606
+ return v.as_dict(exclude_readonly=exclude_readonly) if hasattr(v, "as_dict") else v
607
+
608
+ def _deserialize_model(model_deserializer: typing.Optional[typing.Callable], obj):
609
+ if _is_model(obj):
610
+ return obj
611
+ return _deserialize(model_deserializer, obj)
612
+
613
+ def _deserialize_with_optional(if_obj_deserializer: typing.Optional[typing.Callable], obj):
614
+ if obj is None:
615
+ return obj
616
+ return _deserialize_with_callable(if_obj_deserializer, obj)
617
+
618
+ def _deserialize_with_union(deserializers, obj):
619
+ for deserializer in deserializers:
620
+ try:
621
+ return _deserialize(deserializer, obj)
622
+ except DeserializationError:
623
+ pass
624
+ raise DeserializationError()
625
+
626
+ def _deserialize_dict(
627
+ value_deserializer: typing.Optional[typing.Callable],
628
+ module: typing.Optional[str],
629
+ obj: typing.Dict[typing.Any, typing.Any],
630
+ ):
631
+ if obj is None:
632
+ return obj
633
+ return {
634
+ k: _deserialize(value_deserializer, v, module)
635
+ for k, v in obj.items()
636
+ }
637
+
638
+ def _deserialize_multiple_sequence(
639
+ entry_deserializers: typing.List[typing.Optional[typing.Callable]],
640
+ module: typing.Optional[str],
641
+ obj,
642
+ ):
643
+ if obj is None:
644
+ return obj
645
+ return type(obj)(
646
+ _deserialize(deserializer, entry, module)
647
+ for entry, deserializer in zip(obj, entry_deserializers)
648
+ )
649
+
650
+ def _deserialize_sequence(
651
+ deserializer: typing.Optional[typing.Callable],
652
+ module: typing.Optional[str],
653
+ obj,
654
+ ):
655
+ if obj is None:
656
+ return obj
657
+ return type(obj)(_deserialize(deserializer, entry, module) for entry in obj)
658
+
659
+ def _sorted_annotations(types: typing.List[typing.Any]) -> typing.List[typing.Any]:
660
+ return sorted(
661
+ types,
662
+ key=lambda x: hasattr(x, "__name__") and x.__name__.lower() in ("str", "float", "int", "bool"),
663
+ )
664
+
665
+ def _get_deserialize_callable_from_annotation( # pylint: disable=R0911, R0915, R0912
666
+ annotation: typing.Any,
667
+ module: typing.Optional[str],
668
+ rf: typing.Optional["_RestField"] = None,
669
+ ) -> typing.Optional[typing.Callable[[typing.Any], typing.Any]]:
670
+ if not annotation or annotation in [int, float]:
671
+ return None
672
+
673
+ # is it a type alias?
674
+ if isinstance(annotation, str):
675
+ if module is not None:
676
+ annotation = _get_type_alias_type(module, annotation)
677
+
678
+ # is it a forward ref / in quotes?
679
+ if isinstance(annotation, (str, typing.ForwardRef)):
680
+ try:
681
+ model_name = annotation.__forward_arg__ # type: ignore
682
+ except AttributeError:
683
+ model_name = annotation
684
+ if module is not None:
685
+ annotation = _get_model(module, model_name)
686
+
687
+ try:
688
+ if module and _is_model(annotation):
689
+ if rf:
690
+ rf._is_model = True
691
+
692
+ return functools.partial(_deserialize_model, annotation) # pyright: ignore
693
+ except Exception:
694
+ pass
695
+
696
+ # is it a literal?
697
+ try:
698
+ if annotation.__origin__ is typing.Literal: # pyright: ignore
699
+ return None
700
+ except AttributeError:
701
+ pass
702
+
703
+ # is it optional?
704
+ try:
705
+ if any(a for a in annotation.__args__ if a == type(None)): # pyright: ignore
706
+ if len(annotation.__args__) <= 2: # pyright: ignore
707
+ if_obj_deserializer = _get_deserialize_callable_from_annotation(
708
+ next(a for a in annotation.__args__ if a != type(None)), module, rf # pyright: ignore
709
+ )
710
+
711
+ return functools.partial(_deserialize_with_optional, if_obj_deserializer)
712
+ # the type is Optional[Union[...]], we need to remove the None type from the Union
713
+ annotation_copy = copy.copy(annotation)
714
+ annotation_copy.__args__ = [a for a in annotation_copy.__args__ if a != type(None)] # pyright: ignore
715
+ return _get_deserialize_callable_from_annotation(annotation_copy, module, rf)
716
+ except AttributeError:
717
+ pass
718
+
719
+ # is it union?
720
+ if getattr(annotation, "__origin__", None) is typing.Union:
721
+ # initial ordering is we make `string` the last deserialization option, because it is often them most generic
722
+ deserializers = [
723
+ _get_deserialize_callable_from_annotation(arg, module, rf)
724
+ for arg in _sorted_annotations(annotation.__args__) # pyright: ignore
725
+ ]
726
+
727
+ return functools.partial(_deserialize_with_union, deserializers)
728
+
729
+ try:
730
+ if annotation._name == "Dict": # pyright: ignore
731
+ value_deserializer = _get_deserialize_callable_from_annotation(
732
+ annotation.__args__[1], module, rf # pyright: ignore
733
+ )
734
+
735
+
736
+ return functools.partial(
737
+ _deserialize_dict,
738
+ value_deserializer,
739
+ module,
740
+ )
741
+ except (AttributeError, IndexError):
742
+ pass
743
+ try:
744
+ if annotation._name in ["List", "Set", "Tuple", "Sequence"]: # pyright: ignore
745
+ if len(annotation.__args__) > 1: # pyright: ignore
746
+
747
+
748
+ entry_deserializers = [
749
+ _get_deserialize_callable_from_annotation(dt, module, rf) for dt in annotation.__args__ # pyright: ignore
750
+ ]
751
+ return functools.partial(_deserialize_multiple_sequence, entry_deserializers, module)
752
+ deserializer = _get_deserialize_callable_from_annotation(
753
+ annotation.__args__[0], module, rf # pyright: ignore
754
+ )
755
+
756
+
757
+
758
+ return functools.partial(_deserialize_sequence, deserializer, module)
759
+ except (TypeError, IndexError, AttributeError, SyntaxError):
760
+ pass
761
+
762
+ def _deserialize_default(
763
+ deserializer,
764
+ obj,
765
+ ):
766
+ if obj is None:
767
+ return obj
768
+ try:
769
+ return _deserialize_with_callable(deserializer, obj)
770
+ except Exception:
771
+ pass
772
+ return obj
773
+
774
+ if get_deserializer(annotation, rf):
775
+ return functools.partial(_deserialize_default, get_deserializer(annotation, rf))
776
+
777
+ return functools.partial(_deserialize_default, annotation)
778
+
779
+
780
+ def _deserialize_with_callable(
781
+ deserializer: typing.Optional[typing.Callable[[typing.Any], typing.Any]],
782
+ value: typing.Any,
783
+ ):
784
+ try:
785
+ if value is None or isinstance(value, _Null):
786
+ return None
787
+ if deserializer is None:
788
+ return value
789
+ if isinstance(deserializer, CaseInsensitiveEnumMeta):
790
+ try:
791
+ return deserializer(value)
792
+ except ValueError:
793
+ # for unknown value, return raw value
794
+ return value
795
+ if isinstance(deserializer, type) and issubclass(deserializer, Model):
796
+ return deserializer._deserialize(value, [])
797
+ return typing.cast(typing.Callable[[typing.Any], typing.Any], deserializer)(value)
798
+ except Exception as e:
799
+ raise DeserializationError() from e
800
+
801
+
802
+ def _deserialize(
803
+ deserializer: typing.Any,
804
+ value: typing.Any,
805
+ module: typing.Optional[str] = None,
806
+ rf: typing.Optional["_RestField"] = None,
807
+ format: typing.Optional[str] = None,
808
+ ) -> typing.Any:
809
+ if isinstance(value, PipelineResponse):
810
+ value = value.http_response.json()
811
+ if rf is None and format:
812
+ rf = _RestField(format=format)
813
+ if not isinstance(deserializer, functools.partial):
814
+ deserializer = _get_deserialize_callable_from_annotation(deserializer, module, rf)
815
+ return _deserialize_with_callable(deserializer, value)
816
+
817
+
818
+ class _RestField:
819
+ def __init__(
820
+ self,
821
+ *,
822
+ name: typing.Optional[str] = None,
823
+ type: typing.Optional[typing.Callable] = None, # pylint: disable=redefined-builtin
824
+ is_discriminator: bool = False,
825
+ visibility: typing.Optional[typing.List[str]] = None,
826
+ default: typing.Any = _UNSET,
827
+ format: typing.Optional[str] = None,
828
+ is_multipart_file_input: bool = False,
829
+ ):
830
+ self._type = type
831
+ self._rest_name_input = name
832
+ self._module: typing.Optional[str] = None
833
+ self._is_discriminator = is_discriminator
834
+ self._visibility = visibility
835
+ self._is_model = False
836
+ self._default = default
837
+ self._format = format
838
+ self._is_multipart_file_input = is_multipart_file_input
839
+
840
+ @property
841
+ def _class_type(self) -> typing.Any:
842
+ return getattr(self._type, "args", [None])[0]
843
+
844
+ @property
845
+ def _rest_name(self) -> str:
846
+ if self._rest_name_input is None:
847
+ raise ValueError("Rest name was never set")
848
+ return self._rest_name_input
849
+
850
+ def __get__(self, obj: Model, type=None): # pylint: disable=redefined-builtin
851
+ # by this point, type and rest_name will have a value bc we default
852
+ # them in __new__ of the Model class
853
+ item = obj.get(self._rest_name)
854
+ if item is None:
855
+ return item
856
+ if self._is_model:
857
+ return item
858
+ return _deserialize(self._type, _serialize(item, self._format), rf=self)
859
+
860
+ def __set__(self, obj: Model, value) -> None:
861
+ if value is None:
862
+ # we want to wipe out entries if users set attr to None
863
+ try:
864
+ obj.__delitem__(self._rest_name)
865
+ except KeyError:
866
+ pass
867
+ return
868
+ if self._is_model:
869
+ if not _is_model(value):
870
+ value = _deserialize(self._type, value)
871
+ obj.__setitem__(self._rest_name, value)
872
+ return
873
+ obj.__setitem__(self._rest_name, _serialize(value, self._format))
874
+
875
+ def _get_deserialize_callable_from_annotation(
876
+ self, annotation: typing.Any
877
+ ) -> typing.Optional[typing.Callable[[typing.Any], typing.Any]]:
878
+ return _get_deserialize_callable_from_annotation(annotation, self._module, self)
879
+
880
+
881
+ def rest_field(
882
+ *,
883
+ name: typing.Optional[str] = None,
884
+ type: typing.Optional[typing.Callable] = None, # pylint: disable=redefined-builtin
885
+ visibility: typing.Optional[typing.List[str]] = None,
886
+ default: typing.Any = _UNSET,
887
+ format: typing.Optional[str] = None,
888
+ is_multipart_file_input: bool = False,
889
+ ) -> typing.Any:
890
+ return _RestField(name=name, type=type, visibility=visibility, default=default, format=format, is_multipart_file_input=is_multipart_file_input)
891
+
892
+
893
+ def rest_discriminator(
894
+ *,
895
+ name: typing.Optional[str] = None,
896
+ type: typing.Optional[typing.Callable] = None, # pylint: disable=redefined-builtin
897
+ visibility: typing.Optional[typing.List[str]] = None,
898
+ ) -> typing.Any:
899
+ return _RestField(name=name, type=type, is_discriminator=True, visibility=visibility)