@autorest/python 5.18.0 → 6.0.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 (51) hide show
  1. package/ChangeLog.md +58 -13
  2. package/README.md +9 -0
  3. package/autorest/codegen/__init__.py +24 -30
  4. package/autorest/codegen/models/base_builder.py +17 -6
  5. package/autorest/codegen/models/client.py +9 -6
  6. package/autorest/codegen/models/code_model.py +20 -14
  7. package/autorest/codegen/models/imports.py +57 -2
  8. package/autorest/codegen/models/lro_operation.py +4 -6
  9. package/autorest/codegen/models/lro_paging_operation.py +3 -9
  10. package/autorest/codegen/models/model_type.py +1 -6
  11. package/autorest/codegen/models/operation.py +47 -79
  12. package/autorest/codegen/models/operation_group.py +10 -9
  13. package/autorest/codegen/models/paging_operation.py +19 -7
  14. package/autorest/codegen/models/parameter.py +3 -7
  15. package/autorest/codegen/models/parameter_list.py +20 -36
  16. package/autorest/codegen/models/request_builder.py +31 -42
  17. package/autorest/codegen/serializers/__init__.py +12 -50
  18. package/autorest/codegen/serializers/builder_serializer.py +53 -40
  19. package/autorest/codegen/serializers/client_serializer.py +22 -31
  20. package/autorest/codegen/serializers/general_serializer.py +12 -12
  21. package/autorest/codegen/serializers/import_serializer.py +11 -22
  22. package/autorest/codegen/serializers/metadata_serializer.py +0 -2
  23. package/autorest/codegen/serializers/{model_base_serializer.py → model_serializer.py} +57 -43
  24. package/autorest/codegen/serializers/operation_groups_serializer.py +3 -7
  25. package/autorest/codegen/serializers/operations_init_serializer.py +2 -23
  26. package/autorest/codegen/serializers/patch_serializer.py +1 -3
  27. package/autorest/codegen/serializers/request_builders_serializer.py +2 -5
  28. package/autorest/codegen/serializers/utils.py +1 -4
  29. package/autorest/codegen/templates/client.py.jinja2 +6 -3
  30. package/autorest/codegen/templates/config.py.jinja2 +2 -2
  31. package/autorest/codegen/templates/metadata.json.jinja2 +4 -4
  32. package/autorest/codegen/templates/model_init.py.jinja2 +5 -12
  33. package/autorest/codegen/templates/operation_group.py.jinja2 +16 -3
  34. package/autorest/codegen/templates/operation_groups_container.py.jinja2 +2 -8
  35. package/autorest/codegen/templates/operation_tools.jinja2 +3 -1
  36. package/autorest/codegen/templates/patch.py.jinja2 +1 -2
  37. package/autorest/codegen/templates/request_builder.py.jinja2 +0 -7
  38. package/autorest/codegen/templates/request_builders.py.jinja2 +1 -4
  39. package/autorest/codegen/templates/rest_init.py.jinja2 +3 -8
  40. package/autorest/codegen/templates/serialization.py.jinja2 +2006 -0
  41. package/autorest/codegen/templates/setup.py.jinja2 +4 -0
  42. package/autorest/codegen/templates/vendor.py.jinja2 +10 -0
  43. package/autorest/m4reformatter/__init__.py +9 -3
  44. package/autorest/multiapi/models/client.py +12 -2
  45. package/autorest/multiapi/serializers/__init__.py +17 -8
  46. package/autorest/multiapi/serializers/import_serializer.py +4 -8
  47. package/autorest/multiapi/templates/multiapi_operations_mixin.py.jinja2 +1 -1
  48. package/autorest/preprocess/__init__.py +24 -4
  49. package/package.json +2 -2
  50. package/autorest/codegen/serializers/model_generic_serializer.py +0 -32
  51. package/autorest/codegen/serializers/model_python3_serializer.py +0 -72
@@ -0,0 +1,2006 @@
1
+ # --------------------------------------------------------------------------
2
+ #
3
+ # Copyright (c) Microsoft Corporation. All rights reserved.
4
+ #
5
+ # The MIT License (MIT)
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ # of this software and associated documentation files (the ""Software""), to
9
+ # deal in the Software without restriction, including without limitation the
10
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
11
+ # sell copies of the Software, and to permit persons to whom the Software is
12
+ # furnished to do so, subject to the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be included in
15
+ # all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
23
+ # IN THE SOFTWARE.
24
+ #
25
+ # --------------------------------------------------------------------------
26
+
27
+ # pylint: skip-file
28
+
29
+ from base64 import b64decode, b64encode
30
+ import calendar
31
+ import datetime
32
+ import decimal
33
+ import email
34
+ from enum import Enum
35
+ import json
36
+ import logging
37
+ import re
38
+ import sys
39
+ import codecs
40
+ try:
41
+ from urllib import quote # type: ignore
42
+ except ImportError:
43
+ from urllib.parse import quote # type: ignore
44
+ import xml.etree.ElementTree as ET
45
+
46
+ import isodate
47
+
48
+ from typing import Dict, Any, cast, TYPE_CHECKING
49
+
50
+ from azure.core.exceptions import DeserializationError, SerializationError, raise_with_traceback
51
+
52
+ _BOM = codecs.BOM_UTF8.decode(encoding='utf-8')
53
+
54
+ if TYPE_CHECKING:
55
+ from typing import Optional, Union, AnyStr, IO, Mapping
56
+
57
+ class RawDeserializer:
58
+
59
+ # Accept "text" because we're open minded people...
60
+ JSON_REGEXP = re.compile(r'^(application|text)/([a-z+.]+\+)?json$')
61
+
62
+ # Name used in context
63
+ CONTEXT_NAME = "deserialized_data"
64
+
65
+ @classmethod
66
+ def deserialize_from_text(cls, data, content_type=None):
67
+ # type: (Optional[Union[AnyStr, IO]], Optional[str]) -> Any
68
+ """Decode data according to content-type.
69
+
70
+ Accept a stream of data as well, but will be load at once in memory for now.
71
+
72
+ If no content-type, will return the string version (not bytes, not stream)
73
+
74
+ :param data: Input, could be bytes or stream (will be decoded with UTF8) or text
75
+ :type data: str or bytes or IO
76
+ :param str content_type: The content type.
77
+ """
78
+ if hasattr(data, 'read'):
79
+ # Assume a stream
80
+ data = cast(IO, data).read()
81
+
82
+ if isinstance(data, bytes):
83
+ data_as_str = data.decode(encoding='utf-8-sig')
84
+ else:
85
+ # Explain to mypy the correct type.
86
+ data_as_str = cast(str, data)
87
+
88
+ # Remove Byte Order Mark if present in string
89
+ data_as_str = data_as_str.lstrip(_BOM)
90
+
91
+ if content_type is None:
92
+ return data
93
+
94
+ if cls.JSON_REGEXP.match(content_type):
95
+ try:
96
+ return json.loads(data_as_str)
97
+ except ValueError as err:
98
+ raise DeserializationError("JSON is invalid: {}".format(err), err)
99
+ elif "xml" in (content_type or []):
100
+ try:
101
+
102
+ try:
103
+ if isinstance(data, unicode): # type: ignore
104
+ # If I'm Python 2.7 and unicode XML will scream if I try a "fromstring" on unicode string
105
+ data_as_str = data_as_str.encode(encoding="utf-8") # type: ignore
106
+ except NameError:
107
+ pass
108
+
109
+ return ET.fromstring(data_as_str) # nosec
110
+ except ET.ParseError:
111
+ # It might be because the server has an issue, and returned JSON with
112
+ # content-type XML....
113
+ # So let's try a JSON load, and if it's still broken
114
+ # let's flow the initial exception
115
+ def _json_attemp(data):
116
+ try:
117
+ return True, json.loads(data)
118
+ except ValueError:
119
+ return False, None # Don't care about this one
120
+ success, json_result = _json_attemp(data)
121
+ if success:
122
+ return json_result
123
+ # If i'm here, it's not JSON, it's not XML, let's scream
124
+ # and raise the last context in this block (the XML exception)
125
+ # The function hack is because Py2.7 messes up with exception
126
+ # context otherwise.
127
+ _LOGGER.critical("Wasn't XML not JSON, failing")
128
+ raise_with_traceback(DeserializationError, "XML is invalid")
129
+ raise DeserializationError("Cannot deserialize content-type: {}".format(content_type))
130
+
131
+ @classmethod
132
+ def deserialize_from_http_generics(cls, body_bytes, headers):
133
+ # type: (Optional[Union[AnyStr, IO]], Mapping) -> Any
134
+ """Deserialize from HTTP response.
135
+
136
+ Use bytes and headers to NOT use any requests/aiohttp or whatever
137
+ specific implementation.
138
+ Headers will tested for "content-type"
139
+ """
140
+ # Try to use content-type from headers if available
141
+ content_type = None
142
+ if 'content-type' in headers:
143
+ content_type = headers['content-type'].split(";")[0].strip().lower()
144
+ # Ouch, this server did not declare what it sent...
145
+ # Let's guess it's JSON...
146
+ # Also, since Autorest was considering that an empty body was a valid JSON,
147
+ # need that test as well....
148
+ else:
149
+ content_type = "application/json"
150
+
151
+ if body_bytes:
152
+ return cls.deserialize_from_text(body_bytes, content_type)
153
+ return None
154
+
155
+ try:
156
+ basestring # type: ignore
157
+ unicode_str = unicode # type: ignore
158
+ except NameError:
159
+ basestring = str # type: ignore
160
+ unicode_str = str # type: ignore
161
+
162
+ _LOGGER = logging.getLogger(__name__)
163
+
164
+ try:
165
+ _long_type = long # type: ignore
166
+ except NameError:
167
+ _long_type = int
168
+
169
+ class UTC(datetime.tzinfo):
170
+ """Time Zone info for handling UTC"""
171
+
172
+ def utcoffset(self, dt):
173
+ """UTF offset for UTC is 0."""
174
+ return datetime.timedelta(0)
175
+
176
+ def tzname(self, dt):
177
+ """Timestamp representation."""
178
+ return "Z"
179
+
180
+ def dst(self, dt):
181
+ """No daylight saving for UTC."""
182
+ return datetime.timedelta(hours=1)
183
+
184
+ try:
185
+ from datetime import timezone as _FixedOffset
186
+ except ImportError: # Python 2.7
187
+ class _FixedOffset(datetime.tzinfo): # type: ignore
188
+ """Fixed offset in minutes east from UTC.
189
+ Copy/pasted from Python doc
190
+ :param datetime.timedelta offset: offset in timedelta format
191
+ """
192
+
193
+ def __init__(self, offset):
194
+ self.__offset = offset
195
+
196
+ def utcoffset(self, dt):
197
+ return self.__offset
198
+
199
+ def tzname(self, dt):
200
+ return str(self.__offset.total_seconds()/3600)
201
+
202
+ def __repr__(self):
203
+ return "<FixedOffset {}>".format(self.tzname(None))
204
+
205
+ def dst(self, dt):
206
+ return datetime.timedelta(0)
207
+
208
+ def __getinitargs__(self):
209
+ return (self.__offset,)
210
+
211
+ try:
212
+ from datetime import timezone
213
+ TZ_UTC = timezone.utc # type: ignore
214
+ except ImportError:
215
+ TZ_UTC = UTC() # type: ignore
216
+
217
+ _FLATTEN = re.compile(r"(?<!\\)\.")
218
+
219
+ def attribute_transformer(key, attr_desc, value):
220
+ """A key transformer that returns the Python attribute.
221
+
222
+ :param str key: The attribute name
223
+ :param dict attr_desc: The attribute metadata
224
+ :param object value: The value
225
+ :returns: A key using attribute name
226
+ """
227
+ return (key, value)
228
+
229
+ def full_restapi_key_transformer(key, attr_desc, value):
230
+ """A key transformer that returns the full RestAPI key path.
231
+
232
+ :param str _: The attribute name
233
+ :param dict attr_desc: The attribute metadata
234
+ :param object value: The value
235
+ :returns: A list of keys using RestAPI syntax.
236
+ """
237
+ keys = _FLATTEN.split(attr_desc['key'])
238
+ return ([_decode_attribute_map_key(k) for k in keys], value)
239
+
240
+ def last_restapi_key_transformer(key, attr_desc, value):
241
+ """A key transformer that returns the last RestAPI key.
242
+
243
+ :param str key: The attribute name
244
+ :param dict attr_desc: The attribute metadata
245
+ :param object value: The value
246
+ :returns: The last RestAPI key.
247
+ """
248
+ key, value = full_restapi_key_transformer(key, attr_desc, value)
249
+ return (key[-1], value)
250
+
251
+ def _create_xml_node(tag, prefix=None, ns=None):
252
+ """Create a XML node."""
253
+ if prefix and ns:
254
+ ET.register_namespace(prefix, ns)
255
+ if ns:
256
+ return ET.Element("{"+ns+"}"+tag)
257
+ else:
258
+ return ET.Element(tag)
259
+
260
+ class Model(object):
261
+ """Mixin for all client request body/response body models to support
262
+ serialization and deserialization.
263
+ """
264
+
265
+ _subtype_map = {} # type: Dict[str, Dict[str, Any]]
266
+ _attribute_map = {} # type: Dict[str, Dict[str, Any]]
267
+ _validation = {} # type: Dict[str, Dict[str, Any]]
268
+
269
+ def __init__(self, **kwargs):
270
+ self.additional_properties = {}
271
+ for k in kwargs:
272
+ if k not in self._attribute_map:
273
+ _LOGGER.warning("%s is not a known attribute of class %s and will be ignored", k, self.__class__)
274
+ elif k in self._validation and self._validation[k].get("readonly", False):
275
+ _LOGGER.warning("Readonly attribute %s will be ignored in class %s", k, self.__class__)
276
+ else:
277
+ setattr(self, k, kwargs[k])
278
+
279
+ def __eq__(self, other):
280
+ """Compare objects by comparing all attributes."""
281
+ if isinstance(other, self.__class__):
282
+ return self.__dict__ == other.__dict__
283
+ return False
284
+
285
+ def __ne__(self, other):
286
+ """Compare objects by comparing all attributes."""
287
+ return not self.__eq__(other)
288
+
289
+ def __str__(self):
290
+ return str(self.__dict__)
291
+
292
+ @classmethod
293
+ def enable_additional_properties_sending(cls):
294
+ cls._attribute_map['additional_properties'] = {'key': '', 'type': '{object}'}
295
+
296
+ @classmethod
297
+ def is_xml_model(cls):
298
+ try:
299
+ cls._xml_map
300
+ except AttributeError:
301
+ return False
302
+ return True
303
+
304
+ @classmethod
305
+ def _create_xml_node(cls):
306
+ """Create XML node.
307
+ """
308
+ try:
309
+ xml_map = cls._xml_map
310
+ except AttributeError:
311
+ xml_map = {}
312
+
313
+ return _create_xml_node(
314
+ xml_map.get('name', cls.__name__),
315
+ xml_map.get("prefix", None),
316
+ xml_map.get("ns", None)
317
+ )
318
+
319
+ def serialize(self, keep_readonly=False, **kwargs):
320
+ """Return the JSON that would be sent to azure from this model.
321
+
322
+ This is an alias to `as_dict(full_restapi_key_transformer, keep_readonly=False)`.
323
+
324
+ If you want XML serialization, you can pass the kwargs is_xml=True.
325
+
326
+ :param bool keep_readonly: If you want to serialize the readonly attributes
327
+ :returns: A dict JSON compatible object
328
+ :rtype: dict
329
+ """
330
+ serializer = Serializer(self._infer_class_models())
331
+ return serializer._serialize(self, keep_readonly=keep_readonly, **kwargs)
332
+
333
+ def as_dict(self, keep_readonly=True, key_transformer=attribute_transformer, **kwargs):
334
+ """Return a dict that can be JSONify using json.dump.
335
+
336
+ Advanced usage might optionally use a callback as parameter:
337
+
338
+ .. code::python
339
+
340
+ def my_key_transformer(key, attr_desc, value):
341
+ return key
342
+
343
+ Key is the attribute name used in Python. Attr_desc
344
+ is a dict of metadata. Currently contains 'type' with the
345
+ msrest type and 'key' with the RestAPI encoded key.
346
+ Value is the current value in this object.
347
+
348
+ The string returned will be used to serialize the key.
349
+ If the return type is a list, this is considered hierarchical
350
+ result dict.
351
+
352
+ See the three examples in this file:
353
+
354
+ - attribute_transformer
355
+ - full_restapi_key_transformer
356
+ - last_restapi_key_transformer
357
+
358
+ If you want XML serialization, you can pass the kwargs is_xml=True.
359
+
360
+ :param function key_transformer: A key transformer function.
361
+ :returns: A dict JSON compatible object
362
+ :rtype: dict
363
+ """
364
+ serializer = Serializer(self._infer_class_models())
365
+ return serializer._serialize(self, key_transformer=key_transformer, keep_readonly=keep_readonly, **kwargs)
366
+
367
+ @classmethod
368
+ def _infer_class_models(cls):
369
+ try:
370
+ str_models = cls.__module__.rsplit('.', 1)[0]
371
+ models = sys.modules[str_models]
372
+ client_models = {k: v for k, v in models.__dict__.items() if isinstance(v, type)}
373
+ if cls.__name__ not in client_models:
374
+ raise ValueError("Not Autorest generated code")
375
+ except Exception:
376
+ # Assume it's not Autorest generated (tests?). Add ourselves as dependencies.
377
+ client_models = {cls.__name__: cls}
378
+ return client_models
379
+
380
+ @classmethod
381
+ def deserialize(cls, data, content_type=None):
382
+ """Parse a str using the RestAPI syntax and return a model.
383
+
384
+ :param str data: A str using RestAPI structure. JSON by default.
385
+ :param str content_type: JSON by default, set application/xml if XML.
386
+ :returns: An instance of this model
387
+ :raises: DeserializationError if something went wrong
388
+ """
389
+ deserializer = Deserializer(cls._infer_class_models())
390
+ return deserializer(cls.__name__, data, content_type=content_type)
391
+
392
+ @classmethod
393
+ def from_dict(cls, data, key_extractors=None, content_type=None):
394
+ """Parse a dict using given key extractor return a model.
395
+
396
+ By default consider key
397
+ extractors (rest_key_case_insensitive_extractor, attribute_key_case_insensitive_extractor
398
+ and last_rest_key_case_insensitive_extractor)
399
+
400
+ :param dict data: A dict using RestAPI structure
401
+ :param str content_type: JSON by default, set application/xml if XML.
402
+ :returns: An instance of this model
403
+ :raises: DeserializationError if something went wrong
404
+ """
405
+ deserializer = Deserializer(cls._infer_class_models())
406
+ deserializer.key_extractors = [
407
+ attribute_key_case_insensitive_extractor,
408
+ rest_key_case_insensitive_extractor,
409
+ last_rest_key_case_insensitive_extractor
410
+ ] if key_extractors is None else key_extractors
411
+ return deserializer(cls.__name__, data, content_type=content_type)
412
+
413
+ @classmethod
414
+ def _flatten_subtype(cls, key, objects):
415
+ if '_subtype_map' not in cls.__dict__:
416
+ return {}
417
+ result = dict(cls._subtype_map[key])
418
+ for valuetype in cls._subtype_map[key].values():
419
+ result.update(objects[valuetype]._flatten_subtype(key, objects))
420
+ return result
421
+
422
+ @classmethod
423
+ def _classify(cls, response, objects):
424
+ """Check the class _subtype_map for any child classes.
425
+ We want to ignore any inherited _subtype_maps.
426
+ Remove the polymorphic key from the initial data.
427
+ """
428
+ for subtype_key in cls.__dict__.get('_subtype_map', {}).keys():
429
+ subtype_value = None
430
+
431
+ if not isinstance(response, ET.Element):
432
+ rest_api_response_key = cls._get_rest_key_parts(subtype_key)[-1]
433
+ subtype_value = response.pop(rest_api_response_key, None) or response.pop(subtype_key, None)
434
+ else:
435
+ subtype_value = xml_key_extractor(
436
+ subtype_key,
437
+ cls._attribute_map[subtype_key],
438
+ response
439
+ )
440
+ if subtype_value:
441
+ # Try to match base class. Can be class name only
442
+ # (bug to fix in Autorest to support x-ms-discriminator-name)
443
+ if cls.__name__ == subtype_value:
444
+ return cls
445
+ flatten_mapping_type = cls._flatten_subtype(subtype_key, objects)
446
+ try:
447
+ return objects[flatten_mapping_type[subtype_value]]
448
+ except KeyError:
449
+ _LOGGER.warning(
450
+ "Subtype value %s has no mapping, use base class %s.",
451
+ subtype_value,
452
+ cls.__name__,
453
+ )
454
+ break
455
+ else:
456
+ _LOGGER.warning(
457
+ "Discriminator %s is absent or null, use base class %s.",
458
+ subtype_key,
459
+ cls.__name__
460
+ )
461
+ break
462
+ return cls
463
+
464
+ @classmethod
465
+ def _get_rest_key_parts(cls, attr_key):
466
+ """Get the RestAPI key of this attr, split it and decode part
467
+ :param str attr_key: Attribute key must be in attribute_map.
468
+ :returns: A list of RestAPI part
469
+ :rtype: list
470
+ """
471
+ rest_split_key = _FLATTEN.split(cls._attribute_map[attr_key]['key'])
472
+ return [_decode_attribute_map_key(key_part) for key_part in rest_split_key]
473
+
474
+
475
+ def _decode_attribute_map_key(key):
476
+ """This decode a key in an _attribute_map to the actual key we want to look at
477
+ inside the received data.
478
+
479
+ :param str key: A key string from the generated code
480
+ """
481
+ return key.replace('\\.', '.')
482
+
483
+
484
+ class Serializer(object):
485
+ """Request object model serializer."""
486
+
487
+ basic_types = {str: 'str', int: 'int', bool: 'bool', float: 'float'}
488
+
489
+ _xml_basic_types_serializers = {'bool': lambda x:str(x).lower()}
490
+ days = {0: "Mon", 1: "Tue", 2: "Wed", 3: "Thu",
491
+ 4: "Fri", 5: "Sat", 6: "Sun"}
492
+ months = {1: "Jan", 2: "Feb", 3: "Mar", 4: "Apr", 5: "May", 6: "Jun",
493
+ 7: "Jul", 8: "Aug", 9: "Sep", 10: "Oct", 11: "Nov", 12: "Dec"}
494
+ validation = {
495
+ "min_length": lambda x, y: len(x) < y,
496
+ "max_length": lambda x, y: len(x) > y,
497
+ "minimum": lambda x, y: x < y,
498
+ "maximum": lambda x, y: x > y,
499
+ "minimum_ex": lambda x, y: x <= y,
500
+ "maximum_ex": lambda x, y: x >= y,
501
+ "min_items": lambda x, y: len(x) < y,
502
+ "max_items": lambda x, y: len(x) > y,
503
+ "pattern": lambda x, y: not re.match(y, x, re.UNICODE),
504
+ "unique": lambda x, y: len(x) != len(set(x)),
505
+ "multiple": lambda x, y: x % y != 0
506
+ }
507
+
508
+ def __init__(self, classes=None):
509
+ self.serialize_type = {
510
+ 'iso-8601': Serializer.serialize_iso,
511
+ 'rfc-1123': Serializer.serialize_rfc,
512
+ 'unix-time': Serializer.serialize_unix,
513
+ 'duration': Serializer.serialize_duration,
514
+ 'date': Serializer.serialize_date,
515
+ 'time': Serializer.serialize_time,
516
+ 'decimal': Serializer.serialize_decimal,
517
+ 'long': Serializer.serialize_long,
518
+ 'bytearray': Serializer.serialize_bytearray,
519
+ 'base64': Serializer.serialize_base64,
520
+ 'object': self.serialize_object,
521
+ '[]': self.serialize_iter,
522
+ '{}': self.serialize_dict
523
+ }
524
+ self.dependencies = dict(classes) if classes else {}
525
+ self.key_transformer = full_restapi_key_transformer
526
+ self.client_side_validation = True
527
+
528
+ def _serialize(self, target_obj, data_type=None, **kwargs):
529
+ """Serialize data into a string according to type.
530
+
531
+ :param target_obj: The data to be serialized.
532
+ :param str data_type: The type to be serialized from.
533
+ :rtype: str, dict
534
+ :raises: SerializationError if serialization fails.
535
+ """
536
+ key_transformer = kwargs.get("key_transformer", self.key_transformer)
537
+ keep_readonly = kwargs.get("keep_readonly", False)
538
+ if target_obj is None:
539
+ return None
540
+
541
+ attr_name = None
542
+ class_name = target_obj.__class__.__name__
543
+
544
+ if data_type:
545
+ return self.serialize_data(
546
+ target_obj, data_type, **kwargs)
547
+
548
+ if not hasattr(target_obj, "_attribute_map"):
549
+ data_type = type(target_obj).__name__
550
+ if data_type in self.basic_types.values():
551
+ return self.serialize_data(
552
+ target_obj, data_type, **kwargs)
553
+
554
+ # Force "is_xml" kwargs if we detect a XML model
555
+ try:
556
+ is_xml_model_serialization = kwargs["is_xml"]
557
+ except KeyError:
558
+ is_xml_model_serialization = kwargs.setdefault("is_xml", target_obj.is_xml_model())
559
+
560
+ serialized = {}
561
+ if is_xml_model_serialization:
562
+ serialized = target_obj._create_xml_node()
563
+ try:
564
+ attributes = target_obj._attribute_map
565
+ for attr, attr_desc in attributes.items():
566
+ attr_name = attr
567
+ if not keep_readonly and target_obj._validation.get(attr_name, {}).get('readonly', False):
568
+ continue
569
+
570
+ if attr_name == "additional_properties" and attr_desc["key"] == '':
571
+ if target_obj.additional_properties is not None:
572
+ serialized.update(target_obj.additional_properties)
573
+ continue
574
+ try:
575
+ ### Extract sub-data to serialize from model ###
576
+ orig_attr = getattr(target_obj, attr)
577
+ if is_xml_model_serialization:
578
+ pass # Don't provide "transformer" for XML for now. Keep "orig_attr"
579
+ else: # JSON
580
+ keys, orig_attr = key_transformer(attr, attr_desc.copy(), orig_attr)
581
+ keys = keys if isinstance(keys, list) else [keys]
582
+
583
+ ### Serialize this data ###
584
+ kwargs["serialization_ctxt"] = attr_desc
585
+ new_attr = self.serialize_data(orig_attr, attr_desc['type'], **kwargs)
586
+
587
+ ### Incorporate this data in the right place ###
588
+ if is_xml_model_serialization:
589
+ xml_desc = attr_desc.get('xml', {})
590
+ xml_name = xml_desc.get('name', attr_desc['key'])
591
+ xml_prefix = xml_desc.get('prefix', None)
592
+ xml_ns = xml_desc.get('ns', None)
593
+ if xml_desc.get("attr", False):
594
+ if xml_ns:
595
+ ET.register_namespace(xml_prefix, xml_ns)
596
+ xml_name = "{{{}}}{}".format(xml_ns, xml_name)
597
+ serialized.set(xml_name, new_attr)
598
+ continue
599
+ if xml_desc.get("text", False):
600
+ serialized.text = new_attr
601
+ continue
602
+ if isinstance(new_attr, list):
603
+ serialized.extend(new_attr)
604
+ elif isinstance(new_attr, ET.Element):
605
+ # If the down XML has no XML/Name, we MUST replace the tag with the local tag. But keeping the namespaces.
606
+ if 'name' not in getattr(orig_attr, '_xml_map', {}):
607
+ splitted_tag = new_attr.tag.split("}")
608
+ if len(splitted_tag) == 2: # Namespace
609
+ new_attr.tag = "}".join([splitted_tag[0], xml_name])
610
+ else:
611
+ new_attr.tag = xml_name
612
+ serialized.append(new_attr)
613
+ else: # That's a basic type
614
+ # Integrate namespace if necessary
615
+ local_node = _create_xml_node(
616
+ xml_name,
617
+ xml_prefix,
618
+ xml_ns
619
+ )
620
+ local_node.text = unicode_str(new_attr)
621
+ serialized.append(local_node)
622
+ else: # JSON
623
+ for k in reversed(keys):
624
+ unflattened = {k: new_attr}
625
+ new_attr = unflattened
626
+
627
+ _new_attr = new_attr
628
+ _serialized = serialized
629
+ for k in keys:
630
+ if k not in _serialized:
631
+ _serialized.update(_new_attr)
632
+ _new_attr = _new_attr[k]
633
+ _serialized = _serialized[k]
634
+ except ValueError:
635
+ continue
636
+
637
+ except (AttributeError, KeyError, TypeError) as err:
638
+ msg = "Attribute {} in object {} cannot be serialized.\n{}".format(
639
+ attr_name, class_name, str(target_obj))
640
+ raise_with_traceback(SerializationError, msg, err)
641
+ else:
642
+ return serialized
643
+
644
+ def body(self, data, data_type, **kwargs):
645
+ """Serialize data intended for a request body.
646
+
647
+ :param data: The data to be serialized.
648
+ :param str data_type: The type to be serialized from.
649
+ :rtype: dict
650
+ :raises: SerializationError if serialization fails.
651
+ :raises: ValueError if data is None
652
+ """
653
+
654
+ # Just in case this is a dict
655
+ internal_data_type = data_type.strip('[]{}')
656
+ internal_data_type = self.dependencies.get(internal_data_type, None)
657
+ try:
658
+ is_xml_model_serialization = kwargs["is_xml"]
659
+ except KeyError:
660
+ if internal_data_type and issubclass(internal_data_type, Model):
661
+ is_xml_model_serialization = kwargs.setdefault("is_xml", internal_data_type.is_xml_model())
662
+ else:
663
+ is_xml_model_serialization = False
664
+ if internal_data_type and not isinstance(internal_data_type, Enum):
665
+ try:
666
+ deserializer = Deserializer(self.dependencies)
667
+ # Since it's on serialization, it's almost sure that format is not JSON REST
668
+ # We're not able to deal with additional properties for now.
669
+ deserializer.additional_properties_detection = False
670
+ if is_xml_model_serialization:
671
+ deserializer.key_extractors = [
672
+ attribute_key_case_insensitive_extractor,
673
+ ]
674
+ else:
675
+ deserializer.key_extractors = [
676
+ rest_key_case_insensitive_extractor,
677
+ attribute_key_case_insensitive_extractor,
678
+ last_rest_key_case_insensitive_extractor
679
+ ]
680
+ data = deserializer._deserialize(data_type, data)
681
+ except DeserializationError as err:
682
+ raise_with_traceback(
683
+ SerializationError, "Unable to build a model: "+str(err), err)
684
+
685
+ return self._serialize(data, data_type, **kwargs)
686
+
687
+ def url(self, name, data, data_type, **kwargs):
688
+ """Serialize data intended for a URL path.
689
+
690
+ :param data: The data to be serialized.
691
+ :param str data_type: The type to be serialized from.
692
+ :rtype: str
693
+ :raises: TypeError if serialization fails.
694
+ :raises: ValueError if data is None
695
+ """
696
+ try:
697
+ output = self.serialize_data(data, data_type, **kwargs)
698
+ if data_type == 'bool':
699
+ output = json.dumps(output)
700
+
701
+ if kwargs.get('skip_quote') is True:
702
+ output = str(output)
703
+ else:
704
+ output = quote(str(output), safe='')
705
+ except SerializationError:
706
+ raise TypeError("{} must be type {}.".format(name, data_type))
707
+ else:
708
+ return output
709
+
710
+ def query(self, name, data, data_type, **kwargs):
711
+ """Serialize data intended for a URL query.
712
+
713
+ :param data: The data to be serialized.
714
+ :param str data_type: The type to be serialized from.
715
+ :rtype: str
716
+ :raises: TypeError if serialization fails.
717
+ :raises: ValueError if data is None
718
+ """
719
+ try:
720
+ # Treat the list aside, since we don't want to encode the div separator
721
+ if data_type.startswith("["):
722
+ internal_data_type = data_type[1:-1]
723
+ data = [
724
+ self.serialize_data(d, internal_data_type, **kwargs) if d is not None else ""
725
+ for d
726
+ in data
727
+ ]
728
+ if not kwargs.get('skip_quote', False):
729
+ data = [
730
+ quote(str(d), safe='')
731
+ for d
732
+ in data
733
+ ]
734
+ return str(self.serialize_iter(data, internal_data_type, **kwargs))
735
+
736
+ # Not a list, regular serialization
737
+ output = self.serialize_data(data, data_type, **kwargs)
738
+ if data_type == 'bool':
739
+ output = json.dumps(output)
740
+ if kwargs.get('skip_quote') is True:
741
+ output = str(output)
742
+ else:
743
+ output = quote(str(output), safe='')
744
+ except SerializationError:
745
+ raise TypeError("{} must be type {}.".format(name, data_type))
746
+ else:
747
+ return str(output)
748
+
749
+ def header(self, name, data, data_type, **kwargs):
750
+ """Serialize data intended for a request header.
751
+
752
+ :param data: The data to be serialized.
753
+ :param str data_type: The type to be serialized from.
754
+ :rtype: str
755
+ :raises: TypeError if serialization fails.
756
+ :raises: ValueError if data is None
757
+ """
758
+ try:
759
+ if data_type in ['[str]']:
760
+ data = ["" if d is None else d for d in data]
761
+
762
+ output = self.serialize_data(data, data_type, **kwargs)
763
+ if data_type == 'bool':
764
+ output = json.dumps(output)
765
+ except SerializationError:
766
+ raise TypeError("{} must be type {}.".format(name, data_type))
767
+ else:
768
+ return str(output)
769
+
770
+ def serialize_data(self, data, data_type, **kwargs):
771
+ """Serialize generic data according to supplied data type.
772
+
773
+ :param data: The data to be serialized.
774
+ :param str data_type: The type to be serialized from.
775
+ :param bool required: Whether it's essential that the data not be
776
+ empty or None
777
+ :raises: AttributeError if required data is None.
778
+ :raises: ValueError if data is None
779
+ :raises: SerializationError if serialization fails.
780
+ """
781
+ if data is None:
782
+ raise ValueError("No value for given attribute")
783
+
784
+ try:
785
+ if data_type in self.basic_types.values():
786
+ return self.serialize_basic(data, data_type, **kwargs)
787
+
788
+ elif data_type in self.serialize_type:
789
+ return self.serialize_type[data_type](data, **kwargs)
790
+
791
+ # If dependencies is empty, try with current data class
792
+ # It has to be a subclass of Enum anyway
793
+ enum_type = self.dependencies.get(data_type, data.__class__)
794
+ if issubclass(enum_type, Enum):
795
+ return Serializer.serialize_enum(data, enum_obj=enum_type)
796
+
797
+ iter_type = data_type[0] + data_type[-1]
798
+ if iter_type in self.serialize_type:
799
+ return self.serialize_type[iter_type](
800
+ data, data_type[1:-1], **kwargs)
801
+
802
+ except (ValueError, TypeError) as err:
803
+ msg = "Unable to serialize value: {!r} as type: {!r}."
804
+ raise_with_traceback(
805
+ SerializationError, msg.format(data, data_type), err)
806
+ else:
807
+ return self._serialize(data, **kwargs)
808
+
809
+ @classmethod
810
+ def _get_custom_serializers(cls, data_type, **kwargs):
811
+ custom_serializer = kwargs.get("basic_types_serializers", {}).get(data_type)
812
+ if custom_serializer:
813
+ return custom_serializer
814
+ if kwargs.get("is_xml", False):
815
+ return cls._xml_basic_types_serializers.get(data_type)
816
+
817
+ @classmethod
818
+ def serialize_basic(cls, data, data_type, **kwargs):
819
+ """Serialize basic builting data type.
820
+ Serializes objects to str, int, float or bool.
821
+
822
+ Possible kwargs:
823
+ - basic_types_serializers dict[str, callable] : If set, use the callable as serializer
824
+ - is_xml bool : If set, use xml_basic_types_serializers
825
+
826
+ :param data: Object to be serialized.
827
+ :param str data_type: Type of object in the iterable.
828
+ """
829
+ custom_serializer = cls._get_custom_serializers(data_type, **kwargs)
830
+ if custom_serializer:
831
+ return custom_serializer(data)
832
+ if data_type == 'str':
833
+ return cls.serialize_unicode(data)
834
+ return eval(data_type)(data) # nosec
835
+
836
+ @classmethod
837
+ def serialize_unicode(cls, data):
838
+ """Special handling for serializing unicode strings in Py2.
839
+ Encode to UTF-8 if unicode, otherwise handle as a str.
840
+
841
+ :param data: Object to be serialized.
842
+ :rtype: str
843
+ """
844
+ try: # If I received an enum, return its value
845
+ return data.value
846
+ except AttributeError:
847
+ pass
848
+
849
+ try:
850
+ if isinstance(data, unicode):
851
+ # Don't change it, JSON and XML ElementTree are totally able
852
+ # to serialize correctly u'' strings
853
+ return data
854
+ except NameError:
855
+ return str(data)
856
+ else:
857
+ return str(data)
858
+
859
+ def serialize_iter(self, data, iter_type, div=None, **kwargs):
860
+ """Serialize iterable.
861
+
862
+ Supported kwargs:
863
+ - serialization_ctxt dict : The current entry of _attribute_map, or same format.
864
+ serialization_ctxt['type'] should be same as data_type.
865
+ - is_xml bool : If set, serialize as XML
866
+
867
+ :param list attr: Object to be serialized.
868
+ :param str iter_type: Type of object in the iterable.
869
+ :param bool required: Whether the objects in the iterable must
870
+ not be None or empty.
871
+ :param str div: If set, this str will be used to combine the elements
872
+ in the iterable into a combined string. Default is 'None'.
873
+ :rtype: list, str
874
+ """
875
+ if isinstance(data, str):
876
+ raise SerializationError("Refuse str type as a valid iter type.")
877
+
878
+ serialization_ctxt = kwargs.get("serialization_ctxt", {})
879
+ is_xml = kwargs.get("is_xml", False)
880
+
881
+ serialized = []
882
+ for d in data:
883
+ try:
884
+ serialized.append(self.serialize_data(d, iter_type, **kwargs))
885
+ except ValueError:
886
+ serialized.append(None)
887
+
888
+ if div:
889
+ serialized = ['' if s is None else str(s) for s in serialized]
890
+ serialized = div.join(serialized)
891
+
892
+ if 'xml' in serialization_ctxt or is_xml:
893
+ # XML serialization is more complicated
894
+ xml_desc = serialization_ctxt.get('xml', {})
895
+ xml_name = xml_desc.get('name')
896
+ if not xml_name:
897
+ xml_name = serialization_ctxt['key']
898
+
899
+ # Create a wrap node if necessary (use the fact that Element and list have "append")
900
+ is_wrapped = xml_desc.get("wrapped", False)
901
+ node_name = xml_desc.get("itemsName", xml_name)
902
+ if is_wrapped:
903
+ final_result = _create_xml_node(
904
+ xml_name,
905
+ xml_desc.get('prefix', None),
906
+ xml_desc.get('ns', None)
907
+ )
908
+ else:
909
+ final_result = []
910
+ # All list elements to "local_node"
911
+ for el in serialized:
912
+ if isinstance(el, ET.Element):
913
+ el_node = el
914
+ else:
915
+ el_node = _create_xml_node(
916
+ node_name,
917
+ xml_desc.get('prefix', None),
918
+ xml_desc.get('ns', None)
919
+ )
920
+ if el is not None: # Otherwise it writes "None" :-p
921
+ el_node.text = str(el)
922
+ final_result.append(el_node)
923
+ return final_result
924
+ return serialized
925
+
926
+ def serialize_dict(self, attr, dict_type, **kwargs):
927
+ """Serialize a dictionary of objects.
928
+
929
+ :param dict attr: Object to be serialized.
930
+ :param str dict_type: Type of object in the dictionary.
931
+ :param bool required: Whether the objects in the dictionary must
932
+ not be None or empty.
933
+ :rtype: dict
934
+ """
935
+ serialization_ctxt = kwargs.get("serialization_ctxt", {})
936
+ serialized = {}
937
+ for key, value in attr.items():
938
+ try:
939
+ serialized[self.serialize_unicode(key)] = self.serialize_data(
940
+ value, dict_type, **kwargs)
941
+ except ValueError:
942
+ serialized[self.serialize_unicode(key)] = None
943
+
944
+ if 'xml' in serialization_ctxt:
945
+ # XML serialization is more complicated
946
+ xml_desc = serialization_ctxt['xml']
947
+ xml_name = xml_desc['name']
948
+
949
+ final_result = _create_xml_node(
950
+ xml_name,
951
+ xml_desc.get('prefix', None),
952
+ xml_desc.get('ns', None)
953
+ )
954
+ for key, value in serialized.items():
955
+ ET.SubElement(final_result, key).text = value
956
+ return final_result
957
+
958
+ return serialized
959
+
960
+ def serialize_object(self, attr, **kwargs):
961
+ """Serialize a generic object.
962
+ This will be handled as a dictionary. If object passed in is not
963
+ a basic type (str, int, float, dict, list) it will simply be
964
+ cast to str.
965
+
966
+ :param dict attr: Object to be serialized.
967
+ :rtype: dict or str
968
+ """
969
+ if attr is None:
970
+ return None
971
+ if isinstance(attr, ET.Element):
972
+ return attr
973
+ obj_type = type(attr)
974
+ if obj_type in self.basic_types:
975
+ return self.serialize_basic(attr, self.basic_types[obj_type], **kwargs)
976
+ if obj_type is _long_type:
977
+ return self.serialize_long(attr)
978
+ if obj_type is unicode_str:
979
+ return self.serialize_unicode(attr)
980
+ if obj_type is datetime.datetime:
981
+ return self.serialize_iso(attr)
982
+ if obj_type is datetime.date:
983
+ return self.serialize_date(attr)
984
+ if obj_type is datetime.time:
985
+ return self.serialize_time(attr)
986
+ if obj_type is datetime.timedelta:
987
+ return self.serialize_duration(attr)
988
+ if obj_type is decimal.Decimal:
989
+ return self.serialize_decimal(attr)
990
+
991
+ # If it's a model or I know this dependency, serialize as a Model
992
+ elif obj_type in self.dependencies.values() or isinstance(attr, Model):
993
+ return self._serialize(attr)
994
+
995
+ if obj_type == dict:
996
+ serialized = {}
997
+ for key, value in attr.items():
998
+ try:
999
+ serialized[self.serialize_unicode(key)] = self.serialize_object(
1000
+ value, **kwargs)
1001
+ except ValueError:
1002
+ serialized[self.serialize_unicode(key)] = None
1003
+ return serialized
1004
+
1005
+ if obj_type == list:
1006
+ serialized = []
1007
+ for obj in attr:
1008
+ try:
1009
+ serialized.append(self.serialize_object(
1010
+ obj, **kwargs))
1011
+ except ValueError:
1012
+ pass
1013
+ return serialized
1014
+ return str(attr)
1015
+
1016
+ @staticmethod
1017
+ def serialize_enum(attr, enum_obj=None):
1018
+ try:
1019
+ result = attr.value
1020
+ except AttributeError:
1021
+ result = attr
1022
+ try:
1023
+ enum_obj(result)
1024
+ return result
1025
+ except ValueError:
1026
+ for enum_value in enum_obj:
1027
+ if enum_value.value.lower() == str(attr).lower():
1028
+ return enum_value.value
1029
+ error = "{!r} is not valid value for enum {!r}"
1030
+ raise SerializationError(error.format(attr, enum_obj))
1031
+
1032
+ @staticmethod
1033
+ def serialize_bytearray(attr, **kwargs):
1034
+ """Serialize bytearray into base-64 string.
1035
+
1036
+ :param attr: Object to be serialized.
1037
+ :rtype: str
1038
+ """
1039
+ return b64encode(attr).decode()
1040
+
1041
+ @staticmethod
1042
+ def serialize_base64(attr, **kwargs):
1043
+ """Serialize str into base-64 string.
1044
+
1045
+ :param attr: Object to be serialized.
1046
+ :rtype: str
1047
+ """
1048
+ encoded = b64encode(attr).decode('ascii')
1049
+ return encoded.strip('=').replace('+', '-').replace('/', '_')
1050
+
1051
+ @staticmethod
1052
+ def serialize_decimal(attr, **kwargs):
1053
+ """Serialize Decimal object to float.
1054
+
1055
+ :param attr: Object to be serialized.
1056
+ :rtype: float
1057
+ """
1058
+ return float(attr)
1059
+
1060
+ @staticmethod
1061
+ def serialize_long(attr, **kwargs):
1062
+ """Serialize long (Py2) or int (Py3).
1063
+
1064
+ :param attr: Object to be serialized.
1065
+ :rtype: int/long
1066
+ """
1067
+ return _long_type(attr)
1068
+
1069
+ @staticmethod
1070
+ def serialize_date(attr, **kwargs):
1071
+ """Serialize Date object into ISO-8601 formatted string.
1072
+
1073
+ :param Date attr: Object to be serialized.
1074
+ :rtype: str
1075
+ """
1076
+ if isinstance(attr, str):
1077
+ attr = isodate.parse_date(attr)
1078
+ t = "{:04}-{:02}-{:02}".format(attr.year, attr.month, attr.day)
1079
+ return t
1080
+
1081
+ @staticmethod
1082
+ def serialize_time(attr, **kwargs):
1083
+ """Serialize Time object into ISO-8601 formatted string.
1084
+
1085
+ :param datetime.time attr: Object to be serialized.
1086
+ :rtype: str
1087
+ """
1088
+ if isinstance(attr, str):
1089
+ attr = isodate.parse_time(attr)
1090
+ t = "{:02}:{:02}:{:02}".format(attr.hour, attr.minute, attr.second)
1091
+ if attr.microsecond:
1092
+ t += ".{:02}".format(attr.microsecond)
1093
+ return t
1094
+
1095
+ @staticmethod
1096
+ def serialize_duration(attr, **kwargs):
1097
+ """Serialize TimeDelta object into ISO-8601 formatted string.
1098
+
1099
+ :param TimeDelta attr: Object to be serialized.
1100
+ :rtype: str
1101
+ """
1102
+ if isinstance(attr, str):
1103
+ attr = isodate.parse_duration(attr)
1104
+ return isodate.duration_isoformat(attr)
1105
+
1106
+ @staticmethod
1107
+ def serialize_rfc(attr, **kwargs):
1108
+ """Serialize Datetime object into RFC-1123 formatted string.
1109
+
1110
+ :param Datetime attr: Object to be serialized.
1111
+ :rtype: str
1112
+ :raises: TypeError if format invalid.
1113
+ """
1114
+ try:
1115
+ if not attr.tzinfo:
1116
+ _LOGGER.warning(
1117
+ "Datetime with no tzinfo will be considered UTC.")
1118
+ utc = attr.utctimetuple()
1119
+ except AttributeError:
1120
+ raise TypeError("RFC1123 object must be valid Datetime object.")
1121
+
1122
+ return "{}, {:02} {} {:04} {:02}:{:02}:{:02} GMT".format(
1123
+ Serializer.days[utc.tm_wday], utc.tm_mday,
1124
+ Serializer.months[utc.tm_mon], utc.tm_year,
1125
+ utc.tm_hour, utc.tm_min, utc.tm_sec)
1126
+
1127
+ @staticmethod
1128
+ def serialize_iso(attr, **kwargs):
1129
+ """Serialize Datetime object into ISO-8601 formatted string.
1130
+
1131
+ :param Datetime attr: Object to be serialized.
1132
+ :rtype: str
1133
+ :raises: SerializationError if format invalid.
1134
+ """
1135
+ if isinstance(attr, str):
1136
+ attr = isodate.parse_datetime(attr)
1137
+ try:
1138
+ if not attr.tzinfo:
1139
+ _LOGGER.warning(
1140
+ "Datetime with no tzinfo will be considered UTC.")
1141
+ utc = attr.utctimetuple()
1142
+ if utc.tm_year > 9999 or utc.tm_year < 1:
1143
+ raise OverflowError("Hit max or min date")
1144
+
1145
+ microseconds = str(attr.microsecond).rjust(6,'0').rstrip('0').ljust(3, '0')
1146
+ if microseconds:
1147
+ microseconds = '.'+microseconds
1148
+ date = "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}".format(
1149
+ utc.tm_year, utc.tm_mon, utc.tm_mday,
1150
+ utc.tm_hour, utc.tm_min, utc.tm_sec)
1151
+ return date + microseconds + 'Z'
1152
+ except (ValueError, OverflowError) as err:
1153
+ msg = "Unable to serialize datetime object."
1154
+ raise_with_traceback(SerializationError, msg, err)
1155
+ except AttributeError as err:
1156
+ msg = "ISO-8601 object must be valid Datetime object."
1157
+ raise_with_traceback(TypeError, msg, err)
1158
+
1159
+ @staticmethod
1160
+ def serialize_unix(attr, **kwargs):
1161
+ """Serialize Datetime object into IntTime format.
1162
+ This is represented as seconds.
1163
+
1164
+ :param Datetime attr: Object to be serialized.
1165
+ :rtype: int
1166
+ :raises: SerializationError if format invalid
1167
+ """
1168
+ if isinstance(attr, int):
1169
+ return attr
1170
+ try:
1171
+ if not attr.tzinfo:
1172
+ _LOGGER.warning(
1173
+ "Datetime with no tzinfo will be considered UTC.")
1174
+ return int(calendar.timegm(attr.utctimetuple()))
1175
+ except AttributeError:
1176
+ raise TypeError("Unix time object must be valid Datetime object.")
1177
+
1178
+ def rest_key_extractor(attr, attr_desc, data):
1179
+ key = attr_desc['key']
1180
+ working_data = data
1181
+
1182
+ while '.' in key:
1183
+ dict_keys = _FLATTEN.split(key)
1184
+ if len(dict_keys) == 1:
1185
+ key = _decode_attribute_map_key(dict_keys[0])
1186
+ break
1187
+ working_key = _decode_attribute_map_key(dict_keys[0])
1188
+ working_data = working_data.get(working_key, data)
1189
+ if working_data is None:
1190
+ # If at any point while following flatten JSON path see None, it means
1191
+ # that all properties under are None as well
1192
+ # https://github.com/Azure/msrest-for-python/issues/197
1193
+ return None
1194
+ key = '.'.join(dict_keys[1:])
1195
+
1196
+ return working_data.get(key)
1197
+
1198
+ def rest_key_case_insensitive_extractor(attr, attr_desc, data):
1199
+ key = attr_desc['key']
1200
+ working_data = data
1201
+
1202
+ while '.' in key:
1203
+ dict_keys = _FLATTEN.split(key)
1204
+ if len(dict_keys) == 1:
1205
+ key = _decode_attribute_map_key(dict_keys[0])
1206
+ break
1207
+ working_key = _decode_attribute_map_key(dict_keys[0])
1208
+ working_data = attribute_key_case_insensitive_extractor(working_key, None, working_data)
1209
+ if working_data is None:
1210
+ # If at any point while following flatten JSON path see None, it means
1211
+ # that all properties under are None as well
1212
+ # https://github.com/Azure/msrest-for-python/issues/197
1213
+ return None
1214
+ key = '.'.join(dict_keys[1:])
1215
+
1216
+ if working_data:
1217
+ return attribute_key_case_insensitive_extractor(key, None, working_data)
1218
+
1219
+ def last_rest_key_extractor(attr, attr_desc, data):
1220
+ """Extract the attribute in "data" based on the last part of the JSON path key.
1221
+ """
1222
+ key = attr_desc['key']
1223
+ dict_keys = _FLATTEN.split(key)
1224
+ return attribute_key_extractor(dict_keys[-1], None, data)
1225
+
1226
+ def last_rest_key_case_insensitive_extractor(attr, attr_desc, data):
1227
+ """Extract the attribute in "data" based on the last part of the JSON path key.
1228
+
1229
+ This is the case insensitive version of "last_rest_key_extractor"
1230
+ """
1231
+ key = attr_desc['key']
1232
+ dict_keys = _FLATTEN.split(key)
1233
+ return attribute_key_case_insensitive_extractor(dict_keys[-1], None, data)
1234
+
1235
+ def attribute_key_extractor(attr, _, data):
1236
+ return data.get(attr)
1237
+
1238
+ def attribute_key_case_insensitive_extractor(attr, _, data):
1239
+ found_key = None
1240
+ lower_attr = attr.lower()
1241
+ for key in data:
1242
+ if lower_attr == key.lower():
1243
+ found_key = key
1244
+ break
1245
+
1246
+ return data.get(found_key)
1247
+
1248
+ def _extract_name_from_internal_type(internal_type):
1249
+ """Given an internal type XML description, extract correct XML name with namespace.
1250
+
1251
+ :param dict internal_type: An model type
1252
+ :rtype: tuple
1253
+ :returns: A tuple XML name + namespace dict
1254
+ """
1255
+ internal_type_xml_map = getattr(internal_type, "_xml_map", {})
1256
+ xml_name = internal_type_xml_map.get('name', internal_type.__name__)
1257
+ xml_ns = internal_type_xml_map.get("ns", None)
1258
+ if xml_ns:
1259
+ xml_name = "{{{}}}{}".format(xml_ns, xml_name)
1260
+ return xml_name
1261
+
1262
+
1263
+ def xml_key_extractor(attr, attr_desc, data):
1264
+ if isinstance(data, dict):
1265
+ return None
1266
+
1267
+ # Test if this model is XML ready first
1268
+ if not isinstance(data, ET.Element):
1269
+ return None
1270
+
1271
+ xml_desc = attr_desc.get('xml', {})
1272
+ xml_name = xml_desc.get('name', attr_desc['key'])
1273
+
1274
+ # Look for a children
1275
+ is_iter_type = attr_desc['type'].startswith("[")
1276
+ is_wrapped = xml_desc.get("wrapped", False)
1277
+ internal_type = attr_desc.get("internalType", None)
1278
+ internal_type_xml_map = getattr(internal_type, "_xml_map", {})
1279
+
1280
+ # Integrate namespace if necessary
1281
+ xml_ns = xml_desc.get('ns', internal_type_xml_map.get("ns", None))
1282
+ if xml_ns:
1283
+ xml_name = "{{{}}}{}".format(xml_ns, xml_name)
1284
+
1285
+ # If it's an attribute, that's simple
1286
+ if xml_desc.get("attr", False):
1287
+ return data.get(xml_name)
1288
+
1289
+ # If it's x-ms-text, that's simple too
1290
+ if xml_desc.get("text", False):
1291
+ return data.text
1292
+
1293
+ # Scenario where I take the local name:
1294
+ # - Wrapped node
1295
+ # - Internal type is an enum (considered basic types)
1296
+ # - Internal type has no XML/Name node
1297
+ if is_wrapped or (internal_type and (issubclass(internal_type, Enum) or 'name' not in internal_type_xml_map)):
1298
+ children = data.findall(xml_name)
1299
+ # If internal type has a local name and it's not a list, I use that name
1300
+ elif not is_iter_type and internal_type and 'name' in internal_type_xml_map:
1301
+ xml_name = _extract_name_from_internal_type(internal_type)
1302
+ children = data.findall(xml_name)
1303
+ # That's an array
1304
+ else:
1305
+ if internal_type: # Complex type, ignore itemsName and use the complex type name
1306
+ items_name = _extract_name_from_internal_type(internal_type)
1307
+ else:
1308
+ items_name = xml_desc.get("itemsName", xml_name)
1309
+ children = data.findall(items_name)
1310
+
1311
+ if len(children) == 0:
1312
+ if is_iter_type:
1313
+ if is_wrapped:
1314
+ return None # is_wrapped no node, we want None
1315
+ else:
1316
+ return [] # not wrapped, assume empty list
1317
+ return None # Assume it's not there, maybe an optional node.
1318
+
1319
+ # If is_iter_type and not wrapped, return all found children
1320
+ if is_iter_type:
1321
+ if not is_wrapped:
1322
+ return children
1323
+ else: # Iter and wrapped, should have found one node only (the wrap one)
1324
+ if len(children) != 1:
1325
+ raise DeserializationError(
1326
+ "Tried to deserialize an array not wrapped, and found several nodes '{}'. Maybe you should declare this array as wrapped?".format(
1327
+ xml_name
1328
+ ))
1329
+ return list(children[0]) # Might be empty list and that's ok.
1330
+
1331
+ # Here it's not a itertype, we should have found one element only or empty
1332
+ if len(children) > 1:
1333
+ raise DeserializationError("Find several XML '{}' where it was not expected".format(xml_name))
1334
+ return children[0]
1335
+
1336
+ class Deserializer(object):
1337
+ """Response object model deserializer.
1338
+
1339
+ :param dict classes: Class type dictionary for deserializing complex types.
1340
+ :ivar list key_extractors: Ordered list of extractors to be used by this deserializer.
1341
+ """
1342
+
1343
+ basic_types = {str: 'str', int: 'int', bool: 'bool', float: 'float'}
1344
+
1345
+ valid_date = re.compile(
1346
+ r'\d{4}[-]\d{2}[-]\d{2}T\d{2}:\d{2}:\d{2}'
1347
+ r'\.?\d*Z?[-+]?[\d{2}]?:?[\d{2}]?')
1348
+
1349
+ def __init__(self, classes=None):
1350
+ self.deserialize_type = {
1351
+ 'iso-8601': Deserializer.deserialize_iso,
1352
+ 'rfc-1123': Deserializer.deserialize_rfc,
1353
+ 'unix-time': Deserializer.deserialize_unix,
1354
+ 'duration': Deserializer.deserialize_duration,
1355
+ 'date': Deserializer.deserialize_date,
1356
+ 'time': Deserializer.deserialize_time,
1357
+ 'decimal': Deserializer.deserialize_decimal,
1358
+ 'long': Deserializer.deserialize_long,
1359
+ 'bytearray': Deserializer.deserialize_bytearray,
1360
+ 'base64': Deserializer.deserialize_base64,
1361
+ 'object': self.deserialize_object,
1362
+ '[]': self.deserialize_iter,
1363
+ '{}': self.deserialize_dict
1364
+ }
1365
+ self.deserialize_expected_types = {
1366
+ 'duration': (isodate.Duration, datetime.timedelta),
1367
+ 'iso-8601': (datetime.datetime)
1368
+ }
1369
+ self.dependencies = dict(classes) if classes else {}
1370
+ self.key_extractors = [
1371
+ rest_key_extractor,
1372
+ xml_key_extractor
1373
+ ]
1374
+ # Additional properties only works if the "rest_key_extractor" is used to
1375
+ # extract the keys. Making it to work whatever the key extractor is too much
1376
+ # complicated, with no real scenario for now.
1377
+ # So adding a flag to disable additional properties detection. This flag should be
1378
+ # used if your expect the deserialization to NOT come from a JSON REST syntax.
1379
+ # Otherwise, result are unexpected
1380
+ self.additional_properties_detection = True
1381
+
1382
+ def __call__(self, target_obj, response_data, content_type=None):
1383
+ """Call the deserializer to process a REST response.
1384
+
1385
+ :param str target_obj: Target data type to deserialize to.
1386
+ :param requests.Response response_data: REST response object.
1387
+ :param str content_type: Swagger "produces" if available.
1388
+ :raises: DeserializationError if deserialization fails.
1389
+ :return: Deserialized object.
1390
+ """
1391
+ data = self._unpack_content(response_data, content_type)
1392
+ return self._deserialize(target_obj, data)
1393
+
1394
+ def _deserialize(self, target_obj, data):
1395
+ """Call the deserializer on a model.
1396
+
1397
+ Data needs to be already deserialized as JSON or XML ElementTree
1398
+
1399
+ :param str target_obj: Target data type to deserialize to.
1400
+ :param object data: Object to deserialize.
1401
+ :raises: DeserializationError if deserialization fails.
1402
+ :return: Deserialized object.
1403
+ """
1404
+ # This is already a model, go recursive just in case
1405
+ if hasattr(data, "_attribute_map"):
1406
+ constants = [name for name, config in getattr(data, '_validation', {}).items()
1407
+ if config.get('constant')]
1408
+ try:
1409
+ for attr, mapconfig in data._attribute_map.items():
1410
+ if attr in constants:
1411
+ continue
1412
+ value = getattr(data, attr)
1413
+ if value is None:
1414
+ continue
1415
+ local_type = mapconfig['type']
1416
+ internal_data_type = local_type.strip('[]{}')
1417
+ if internal_data_type not in self.dependencies or isinstance(internal_data_type, Enum):
1418
+ continue
1419
+ setattr(
1420
+ data,
1421
+ attr,
1422
+ self._deserialize(local_type, value)
1423
+ )
1424
+ return data
1425
+ except AttributeError:
1426
+ return
1427
+
1428
+ response, class_name = self._classify_target(target_obj, data)
1429
+
1430
+ if isinstance(response, basestring):
1431
+ return self.deserialize_data(data, response)
1432
+ elif isinstance(response, type) and issubclass(response, Enum):
1433
+ return self.deserialize_enum(data, response)
1434
+
1435
+ if data is None:
1436
+ return data
1437
+ try:
1438
+ attributes = response._attribute_map
1439
+ d_attrs = {}
1440
+ for attr, attr_desc in attributes.items():
1441
+ # Check empty string. If it's not empty, someone has a real "additionalProperties"...
1442
+ if attr == "additional_properties" and attr_desc["key"] == '':
1443
+ continue
1444
+ raw_value = None
1445
+ # Enhance attr_desc with some dynamic data
1446
+ attr_desc = attr_desc.copy() # Do a copy, do not change the real one
1447
+ internal_data_type = attr_desc["type"].strip('[]{}')
1448
+ if internal_data_type in self.dependencies:
1449
+ attr_desc["internalType"] = self.dependencies[internal_data_type]
1450
+
1451
+ for key_extractor in self.key_extractors:
1452
+ found_value = key_extractor(attr, attr_desc, data)
1453
+ if found_value is not None:
1454
+ if raw_value is not None and raw_value != found_value:
1455
+ msg = ("Ignoring extracted value '%s' from %s for key '%s'"
1456
+ " (duplicate extraction, follow extractors order)" )
1457
+ _LOGGER.warning(
1458
+ msg,
1459
+ found_value,
1460
+ key_extractor,
1461
+ attr
1462
+ )
1463
+ continue
1464
+ raw_value = found_value
1465
+
1466
+ value = self.deserialize_data(raw_value, attr_desc['type'])
1467
+ d_attrs[attr] = value
1468
+ except (AttributeError, TypeError, KeyError) as err:
1469
+ msg = "Unable to deserialize to object: " + class_name
1470
+ raise_with_traceback(DeserializationError, msg, err)
1471
+ else:
1472
+ additional_properties = self._build_additional_properties(attributes, data)
1473
+ return self._instantiate_model(response, d_attrs, additional_properties)
1474
+
1475
+ def _build_additional_properties(self, attribute_map, data):
1476
+ if not self.additional_properties_detection:
1477
+ return None
1478
+ if "additional_properties" in attribute_map and attribute_map.get("additional_properties", {}).get("key") != '':
1479
+ # Check empty string. If it's not empty, someone has a real "additionalProperties"
1480
+ return None
1481
+ if isinstance(data, ET.Element):
1482
+ data = {el.tag: el.text for el in data}
1483
+
1484
+ known_keys = {_decode_attribute_map_key(_FLATTEN.split(desc['key'])[0])
1485
+ for desc in attribute_map.values() if desc['key'] != ''}
1486
+ present_keys = set(data.keys())
1487
+ missing_keys = present_keys - known_keys
1488
+ return {key: data[key] for key in missing_keys}
1489
+
1490
+ def _classify_target(self, target, data):
1491
+ """Check to see whether the deserialization target object can
1492
+ be classified into a subclass.
1493
+ Once classification has been determined, initialize object.
1494
+
1495
+ :param str target: The target object type to deserialize to.
1496
+ :param str/dict data: The response data to deseralize.
1497
+ """
1498
+ if target is None:
1499
+ return None, None
1500
+
1501
+ if isinstance(target, basestring):
1502
+ try:
1503
+ target = self.dependencies[target]
1504
+ except KeyError:
1505
+ return target, target
1506
+
1507
+ try:
1508
+ target = target._classify(data, self.dependencies)
1509
+ except AttributeError:
1510
+ pass # Target is not a Model, no classify
1511
+ return target, target.__class__.__name__
1512
+
1513
+ def failsafe_deserialize(self, target_obj, data, content_type=None):
1514
+ """Ignores any errors encountered in deserialization,
1515
+ and falls back to not deserializing the object. Recommended
1516
+ for use in error deserialization, as we want to return the
1517
+ HttpResponseError to users, and not have them deal with
1518
+ a deserialization error.
1519
+
1520
+ :param str target_obj: The target object type to deserialize to.
1521
+ :param str/dict data: The response data to deseralize.
1522
+ :param str content_type: Swagger "produces" if available.
1523
+ """
1524
+ try:
1525
+ return self(target_obj, data, content_type=content_type)
1526
+ except:
1527
+ _LOGGER.warning(
1528
+ "Ran into a deserialization error. Ignoring since this is failsafe deserialization",
1529
+ exc_info=True
1530
+ )
1531
+ return None
1532
+
1533
+ @staticmethod
1534
+ def _unpack_content(raw_data, content_type=None):
1535
+ """Extract the correct structure for deserialization.
1536
+
1537
+ If raw_data is a PipelineResponse, try to extract the result of RawDeserializer.
1538
+ if we can't, raise. Your Pipeline should have a RawDeserializer.
1539
+
1540
+ If not a pipeline response and raw_data is bytes or string, use content-type
1541
+ to decode it. If no content-type, try JSON.
1542
+
1543
+ If raw_data is something else, bypass all logic and return it directly.
1544
+
1545
+ :param raw_data: Data to be processed.
1546
+ :param content_type: How to parse if raw_data is a string/bytes.
1547
+ :raises JSONDecodeError: If JSON is requested and parsing is impossible.
1548
+ :raises UnicodeDecodeError: If bytes is not UTF8
1549
+ """
1550
+ # Assume this is enough to detect a Pipeline Response without importing it
1551
+ context = getattr(raw_data, "context", {})
1552
+ if context:
1553
+ if RawDeserializer.CONTEXT_NAME in context:
1554
+ return context[RawDeserializer.CONTEXT_NAME]
1555
+ raise ValueError("This pipeline didn't have the RawDeserializer policy; can't deserialize")
1556
+
1557
+ #Assume this is enough to recognize universal_http.ClientResponse without importing it
1558
+ if hasattr(raw_data, "body"):
1559
+ return RawDeserializer.deserialize_from_http_generics(
1560
+ raw_data.text(),
1561
+ raw_data.headers
1562
+ )
1563
+
1564
+ # Assume this enough to recognize requests.Response without importing it.
1565
+ if hasattr(raw_data, '_content_consumed'):
1566
+ return RawDeserializer.deserialize_from_http_generics(
1567
+ raw_data.text,
1568
+ raw_data.headers
1569
+ )
1570
+
1571
+ if isinstance(raw_data, (basestring, bytes)) or hasattr(raw_data, 'read'):
1572
+ return RawDeserializer.deserialize_from_text(raw_data, content_type)
1573
+ return raw_data
1574
+
1575
+ def _instantiate_model(self, response, attrs, additional_properties=None):
1576
+ """Instantiate a response model passing in deserialized args.
1577
+
1578
+ :param response: The response model class.
1579
+ :param d_attrs: The deserialized response attributes.
1580
+ """
1581
+ if callable(response):
1582
+ subtype = getattr(response, '_subtype_map', {})
1583
+ try:
1584
+ readonly = [k for k, v in response._validation.items()
1585
+ if v.get('readonly')]
1586
+ const = [k for k, v in response._validation.items()
1587
+ if v.get('constant')]
1588
+ kwargs = {k: v for k, v in attrs.items()
1589
+ if k not in subtype and k not in readonly + const}
1590
+ response_obj = response(**kwargs)
1591
+ for attr in readonly:
1592
+ setattr(response_obj, attr, attrs.get(attr))
1593
+ if additional_properties:
1594
+ response_obj.additional_properties = additional_properties
1595
+ return response_obj
1596
+ except TypeError as err:
1597
+ msg = "Unable to deserialize {} into model {}. ".format(
1598
+ kwargs, response)
1599
+ raise DeserializationError(msg + str(err))
1600
+ else:
1601
+ try:
1602
+ for attr, value in attrs.items():
1603
+ setattr(response, attr, value)
1604
+ return response
1605
+ except Exception as exp:
1606
+ msg = "Unable to populate response model. "
1607
+ msg += "Type: {}, Error: {}".format(type(response), exp)
1608
+ raise DeserializationError(msg)
1609
+
1610
+ def deserialize_data(self, data, data_type):
1611
+ """Process data for deserialization according to data type.
1612
+
1613
+ :param str data: The response string to be deserialized.
1614
+ :param str data_type: The type to deserialize to.
1615
+ :raises: DeserializationError if deserialization fails.
1616
+ :return: Deserialized object.
1617
+ """
1618
+ if data is None:
1619
+ return data
1620
+
1621
+ try:
1622
+ if not data_type:
1623
+ return data
1624
+ if data_type in self.basic_types.values():
1625
+ return self.deserialize_basic(data, data_type)
1626
+ if data_type in self.deserialize_type:
1627
+ if isinstance(data, self.deserialize_expected_types.get(data_type, tuple())):
1628
+ return data
1629
+
1630
+ is_a_text_parsing_type = lambda x: x not in ["object", "[]", r"{}"]
1631
+ if isinstance(data, ET.Element) and is_a_text_parsing_type(data_type) and not data.text:
1632
+ return None
1633
+ data_val = self.deserialize_type[data_type](data)
1634
+ return data_val
1635
+
1636
+ iter_type = data_type[0] + data_type[-1]
1637
+ if iter_type in self.deserialize_type:
1638
+ return self.deserialize_type[iter_type](data, data_type[1:-1])
1639
+
1640
+ obj_type = self.dependencies[data_type]
1641
+ if issubclass(obj_type, Enum):
1642
+ if isinstance(data, ET.Element):
1643
+ data = data.text
1644
+ return self.deserialize_enum(data, obj_type)
1645
+
1646
+ except (ValueError, TypeError, AttributeError) as err:
1647
+ msg = "Unable to deserialize response data."
1648
+ msg += " Data: {}, {}".format(data, data_type)
1649
+ raise_with_traceback(DeserializationError, msg, err)
1650
+ else:
1651
+ return self._deserialize(obj_type, data)
1652
+
1653
+ def deserialize_iter(self, attr, iter_type):
1654
+ """Deserialize an iterable.
1655
+
1656
+ :param list attr: Iterable to be deserialized.
1657
+ :param str iter_type: The type of object in the iterable.
1658
+ :rtype: list
1659
+ """
1660
+ if attr is None:
1661
+ return None
1662
+ if isinstance(attr, ET.Element): # If I receive an element here, get the children
1663
+ attr = list(attr)
1664
+ if not isinstance(attr, (list, set)):
1665
+ raise DeserializationError("Cannot deserialize as [{}] an object of type {}".format(
1666
+ iter_type,
1667
+ type(attr)
1668
+ ))
1669
+ return [self.deserialize_data(a, iter_type) for a in attr]
1670
+
1671
+ def deserialize_dict(self, attr, dict_type):
1672
+ """Deserialize a dictionary.
1673
+
1674
+ :param dict/list attr: Dictionary to be deserialized. Also accepts
1675
+ a list of key, value pairs.
1676
+ :param str dict_type: The object type of the items in the dictionary.
1677
+ :rtype: dict
1678
+ """
1679
+ if isinstance(attr, list):
1680
+ return {x['key']: self.deserialize_data(x['value'], dict_type) for x in attr}
1681
+
1682
+ if isinstance(attr, ET.Element):
1683
+ # Transform <Key>value</Key> into {"Key": "value"}
1684
+ attr = {el.tag: el.text for el in attr}
1685
+ return {k: self.deserialize_data(v, dict_type) for k, v in attr.items()}
1686
+
1687
+ def deserialize_object(self, attr, **kwargs):
1688
+ """Deserialize a generic object.
1689
+ This will be handled as a dictionary.
1690
+
1691
+ :param dict attr: Dictionary to be deserialized.
1692
+ :rtype: dict
1693
+ :raises: TypeError if non-builtin datatype encountered.
1694
+ """
1695
+ if attr is None:
1696
+ return None
1697
+ if isinstance(attr, ET.Element):
1698
+ # Do no recurse on XML, just return the tree as-is
1699
+ return attr
1700
+ if isinstance(attr, basestring):
1701
+ return self.deserialize_basic(attr, 'str')
1702
+ obj_type = type(attr)
1703
+ if obj_type in self.basic_types:
1704
+ return self.deserialize_basic(attr, self.basic_types[obj_type])
1705
+ if obj_type is _long_type:
1706
+ return self.deserialize_long(attr)
1707
+
1708
+ if obj_type == dict:
1709
+ deserialized = {}
1710
+ for key, value in attr.items():
1711
+ try:
1712
+ deserialized[key] = self.deserialize_object(
1713
+ value, **kwargs)
1714
+ except ValueError:
1715
+ deserialized[key] = None
1716
+ return deserialized
1717
+
1718
+ if obj_type == list:
1719
+ deserialized = []
1720
+ for obj in attr:
1721
+ try:
1722
+ deserialized.append(self.deserialize_object(
1723
+ obj, **kwargs))
1724
+ except ValueError:
1725
+ pass
1726
+ return deserialized
1727
+
1728
+ else:
1729
+ error = "Cannot deserialize generic object with type: "
1730
+ raise TypeError(error + str(obj_type))
1731
+
1732
+ def deserialize_basic(self, attr, data_type):
1733
+ """Deserialize basic builtin data type from string.
1734
+ Will attempt to convert to str, int, float and bool.
1735
+ This function will also accept '1', '0', 'true' and 'false' as
1736
+ valid bool values.
1737
+
1738
+ :param str attr: response string to be deserialized.
1739
+ :param str data_type: deserialization data type.
1740
+ :rtype: str, int, float or bool
1741
+ :raises: TypeError if string format is not valid.
1742
+ """
1743
+ # If we're here, data is supposed to be a basic type.
1744
+ # If it's still an XML node, take the text
1745
+ if isinstance(attr, ET.Element):
1746
+ attr = attr.text
1747
+ if not attr:
1748
+ if data_type == "str":
1749
+ # None or '', node <a/> is empty string.
1750
+ return ''
1751
+ else:
1752
+ # None or '', node <a/> with a strong type is None.
1753
+ # Don't try to model "empty bool" or "empty int"
1754
+ return None
1755
+
1756
+ if data_type == 'bool':
1757
+ if attr in [True, False, 1, 0]:
1758
+ return bool(attr)
1759
+ elif isinstance(attr, basestring):
1760
+ if attr.lower() in ['true', '1']:
1761
+ return True
1762
+ elif attr.lower() in ['false', '0']:
1763
+ return False
1764
+ raise TypeError("Invalid boolean value: {}".format(attr))
1765
+
1766
+ if data_type == 'str':
1767
+ return self.deserialize_unicode(attr)
1768
+ return eval(data_type)(attr) # nosec
1769
+
1770
+ @staticmethod
1771
+ def deserialize_unicode(data):
1772
+ """Preserve unicode objects in Python 2, otherwise return data
1773
+ as a string.
1774
+
1775
+ :param str data: response string to be deserialized.
1776
+ :rtype: str or unicode
1777
+ """
1778
+ # We might be here because we have an enum modeled as string,
1779
+ # and we try to deserialize a partial dict with enum inside
1780
+ if isinstance(data, Enum):
1781
+ return data
1782
+
1783
+ # Consider this is real string
1784
+ try:
1785
+ if isinstance(data, unicode):
1786
+ return data
1787
+ except NameError:
1788
+ return str(data)
1789
+ else:
1790
+ return str(data)
1791
+
1792
+ @staticmethod
1793
+ def deserialize_enum(data, enum_obj):
1794
+ """Deserialize string into enum object.
1795
+
1796
+ If the string is not a valid enum value it will be returned as-is
1797
+ and a warning will be logged.
1798
+
1799
+ :param str data: Response string to be deserialized. If this value is
1800
+ None or invalid it will be returned as-is.
1801
+ :param Enum enum_obj: Enum object to deserialize to.
1802
+ :rtype: Enum
1803
+ """
1804
+ if isinstance(data, enum_obj) or data is None:
1805
+ return data
1806
+ if isinstance(data, Enum):
1807
+ data = data.value
1808
+ if isinstance(data, int):
1809
+ # Workaround. We might consider remove it in the future.
1810
+ # https://github.com/Azure/azure-rest-api-specs/issues/141
1811
+ try:
1812
+ return list(enum_obj.__members__.values())[data]
1813
+ except IndexError:
1814
+ error = "{!r} is not a valid index for enum {!r}"
1815
+ raise DeserializationError(error.format(data, enum_obj))
1816
+ try:
1817
+ return enum_obj(str(data))
1818
+ except ValueError:
1819
+ for enum_value in enum_obj:
1820
+ if enum_value.value.lower() == str(data).lower():
1821
+ return enum_value
1822
+ # We don't fail anymore for unknown value, we deserialize as a string
1823
+ _LOGGER.warning("Deserializer is not able to find %s as valid enum in %s", data, enum_obj)
1824
+ return Deserializer.deserialize_unicode(data)
1825
+
1826
+ @staticmethod
1827
+ def deserialize_bytearray(attr):
1828
+ """Deserialize string into bytearray.
1829
+
1830
+ :param str attr: response string to be deserialized.
1831
+ :rtype: bytearray
1832
+ :raises: TypeError if string format invalid.
1833
+ """
1834
+ if isinstance(attr, ET.Element):
1835
+ attr = attr.text
1836
+ return bytearray(b64decode(attr))
1837
+
1838
+ @staticmethod
1839
+ def deserialize_base64(attr):
1840
+ """Deserialize base64 encoded string into string.
1841
+
1842
+ :param str attr: response string to be deserialized.
1843
+ :rtype: bytearray
1844
+ :raises: TypeError if string format invalid.
1845
+ """
1846
+ if isinstance(attr, ET.Element):
1847
+ attr = attr.text
1848
+ padding = '=' * (3 - (len(attr) + 3) % 4)
1849
+ attr = attr + padding
1850
+ encoded = attr.replace('-', '+').replace('_', '/')
1851
+ return b64decode(encoded)
1852
+
1853
+ @staticmethod
1854
+ def deserialize_decimal(attr):
1855
+ """Deserialize string into Decimal object.
1856
+
1857
+ :param str attr: response string to be deserialized.
1858
+ :rtype: Decimal
1859
+ :raises: DeserializationError if string format invalid.
1860
+ """
1861
+ if isinstance(attr, ET.Element):
1862
+ attr = attr.text
1863
+ try:
1864
+ return decimal.Decimal(attr)
1865
+ except decimal.DecimalException as err:
1866
+ msg = "Invalid decimal {}".format(attr)
1867
+ raise_with_traceback(DeserializationError, msg, err)
1868
+
1869
+ @staticmethod
1870
+ def deserialize_long(attr):
1871
+ """Deserialize string into long (Py2) or int (Py3).
1872
+
1873
+ :param str attr: response string to be deserialized.
1874
+ :rtype: long or int
1875
+ :raises: ValueError if string format invalid.
1876
+ """
1877
+ if isinstance(attr, ET.Element):
1878
+ attr = attr.text
1879
+ return _long_type(attr)
1880
+
1881
+ @staticmethod
1882
+ def deserialize_duration(attr):
1883
+ """Deserialize ISO-8601 formatted string into TimeDelta object.
1884
+
1885
+ :param str attr: response string to be deserialized.
1886
+ :rtype: TimeDelta
1887
+ :raises: DeserializationError if string format invalid.
1888
+ """
1889
+ if isinstance(attr, ET.Element):
1890
+ attr = attr.text
1891
+ try:
1892
+ duration = isodate.parse_duration(attr)
1893
+ except(ValueError, OverflowError, AttributeError) as err:
1894
+ msg = "Cannot deserialize duration object."
1895
+ raise_with_traceback(DeserializationError, msg, err)
1896
+ else:
1897
+ return duration
1898
+
1899
+ @staticmethod
1900
+ def deserialize_date(attr):
1901
+ """Deserialize ISO-8601 formatted string into Date object.
1902
+
1903
+ :param str attr: response string to be deserialized.
1904
+ :rtype: Date
1905
+ :raises: DeserializationError if string format invalid.
1906
+ """
1907
+ if isinstance(attr, ET.Element):
1908
+ attr = attr.text
1909
+ if re.search(r"[^\W\d_]", attr, re.I + re.U):
1910
+ raise DeserializationError("Date must have only digits and -. Received: %s" % attr)
1911
+ # This must NOT use defaultmonth/defaultday. Using None ensure this raises an exception.
1912
+ return isodate.parse_date(attr, defaultmonth=None, defaultday=None)
1913
+
1914
+ @staticmethod
1915
+ def deserialize_time(attr):
1916
+ """Deserialize ISO-8601 formatted string into time object.
1917
+
1918
+ :param str attr: response string to be deserialized.
1919
+ :rtype: datetime.time
1920
+ :raises: DeserializationError if string format invalid.
1921
+ """
1922
+ if isinstance(attr, ET.Element):
1923
+ attr = attr.text
1924
+ if re.search(r"[^\W\d_]", attr, re.I + re.U):
1925
+ raise DeserializationError("Date must have only digits and -. Received: %s" % attr)
1926
+ return isodate.parse_time(attr)
1927
+
1928
+ @staticmethod
1929
+ def deserialize_rfc(attr):
1930
+ """Deserialize RFC-1123 formatted string into Datetime object.
1931
+
1932
+ :param str attr: response string to be deserialized.
1933
+ :rtype: Datetime
1934
+ :raises: DeserializationError if string format invalid.
1935
+ """
1936
+ if isinstance(attr, ET.Element):
1937
+ attr = attr.text
1938
+ try:
1939
+ parsed_date = email.utils.parsedate_tz(attr)
1940
+ date_obj = datetime.datetime(
1941
+ *parsed_date[:6],
1942
+ tzinfo=_FixedOffset(datetime.timedelta(minutes=(parsed_date[9] or 0)/60))
1943
+ )
1944
+ if not date_obj.tzinfo:
1945
+ date_obj = date_obj.astimezone(tz=TZ_UTC)
1946
+ except ValueError as err:
1947
+ msg = "Cannot deserialize to rfc datetime object."
1948
+ raise_with_traceback(DeserializationError, msg, err)
1949
+ else:
1950
+ return date_obj
1951
+
1952
+ @staticmethod
1953
+ def deserialize_iso(attr):
1954
+ """Deserialize ISO-8601 formatted string into Datetime object.
1955
+
1956
+ :param str attr: response string to be deserialized.
1957
+ :rtype: Datetime
1958
+ :raises: DeserializationError if string format invalid.
1959
+ """
1960
+ if isinstance(attr, ET.Element):
1961
+ attr = attr.text
1962
+ try:
1963
+ attr = attr.upper()
1964
+ match = Deserializer.valid_date.match(attr)
1965
+ if not match:
1966
+ raise ValueError("Invalid datetime string: " + attr)
1967
+
1968
+ check_decimal = attr.split('.')
1969
+ if len(check_decimal) > 1:
1970
+ decimal_str = ""
1971
+ for digit in check_decimal[1]:
1972
+ if digit.isdigit():
1973
+ decimal_str += digit
1974
+ else:
1975
+ break
1976
+ if len(decimal_str) > 6:
1977
+ attr = attr.replace(decimal_str, decimal_str[0:6])
1978
+
1979
+ date_obj = isodate.parse_datetime(attr)
1980
+ test_utc = date_obj.utctimetuple()
1981
+ if test_utc.tm_year > 9999 or test_utc.tm_year < 1:
1982
+ raise OverflowError("Hit max or min date")
1983
+ except(ValueError, OverflowError, AttributeError) as err:
1984
+ msg = "Cannot deserialize datetime object."
1985
+ raise_with_traceback(DeserializationError, msg, err)
1986
+ else:
1987
+ return date_obj
1988
+
1989
+ @staticmethod
1990
+ def deserialize_unix(attr):
1991
+ """Serialize Datetime object into IntTime format.
1992
+ This is represented as seconds.
1993
+
1994
+ :param int attr: Object to be serialized.
1995
+ :rtype: Datetime
1996
+ :raises: DeserializationError if format invalid
1997
+ """
1998
+ if isinstance(attr, ET.Element):
1999
+ attr = int(attr.text)
2000
+ try:
2001
+ date_obj = datetime.datetime.fromtimestamp(attr, TZ_UTC)
2002
+ except ValueError as err:
2003
+ msg = "Cannot deserialize to unix datetime object."
2004
+ raise_with_traceback(DeserializationError, msg, err)
2005
+ else:
2006
+ return date_obj