38
38
import re
39
39
import sys
40
40
import codecs
41
- from typing import Optional , Union , AnyStr , IO , Mapping
41
+ from typing import (
42
+ Dict ,
43
+ Any ,
44
+ cast ,
45
+ Optional ,
46
+ Union ,
47
+ AnyStr ,
48
+ IO ,
49
+ Mapping ,
50
+ Callable ,
51
+ TypeVar ,
52
+ MutableMapping ,
53
+ Type ,
54
+ List ,
55
+ Mapping ,
56
+ )
42
57
43
58
try :
44
59
from urllib import quote # type: ignore
48
63
49
64
import isodate # type: ignore
50
65
51
- from typing import Dict , Any , cast
52
-
53
66
from azure .core .exceptions import DeserializationError , SerializationError , raise_with_traceback
67
+ from azure .core .serialization import NULL as AzureCoreNull
54
68
55
69
_BOM = codecs .BOM_UTF8 .decode (encoding = "utf-8" )
56
70
71
+ ModelType = TypeVar ("ModelType" , bound = "Model" )
72
+ JSON = MutableMapping [str , Any ]
73
+
57
74
58
75
class RawDeserializer :
59
76
@@ -277,8 +294,8 @@ class Model(object):
277
294
_attribute_map : Dict [str , Dict [str , Any ]] = {}
278
295
_validation : Dict [str , Dict [str , Any ]] = {}
279
296
280
- def __init__ (self , ** kwargs ) :
281
- self .additional_properties = {}
297
+ def __init__ (self , ** kwargs : Any ) -> None :
298
+ self .additional_properties : Dict [ str , Any ] = {}
282
299
for k in kwargs :
283
300
if k not in self ._attribute_map :
284
301
_LOGGER .warning ("%s is not a known attribute of class %s and will be ignored" , k , self .__class__ )
@@ -287,25 +304,25 @@ def __init__(self, **kwargs):
287
304
else :
288
305
setattr (self , k , kwargs [k ])
289
306
290
- def __eq__ (self , other ) :
307
+ def __eq__ (self , other : Any ) -> bool :
291
308
"""Compare objects by comparing all attributes."""
292
309
if isinstance (other , self .__class__ ):
293
310
return self .__dict__ == other .__dict__
294
311
return False
295
312
296
- def __ne__ (self , other ) :
313
+ def __ne__ (self , other : Any ) -> bool :
297
314
"""Compare objects by comparing all attributes."""
298
315
return not self .__eq__ (other )
299
316
300
- def __str__ (self ):
317
+ def __str__ (self ) -> str :
301
318
return str (self .__dict__ )
302
319
303
320
@classmethod
304
- def enable_additional_properties_sending (cls ):
321
+ def enable_additional_properties_sending (cls ) -> None :
305
322
cls ._attribute_map ["additional_properties" ] = {"key" : "" , "type" : "{object}" }
306
323
307
324
@classmethod
308
- def is_xml_model (cls ):
325
+ def is_xml_model (cls ) -> bool :
309
326
try :
310
327
cls ._xml_map # type: ignore
311
328
except AttributeError :
@@ -322,7 +339,7 @@ def _create_xml_node(cls):
322
339
323
340
return _create_xml_node (xml_map .get ("name" , cls .__name__ ), xml_map .get ("prefix" , None ), xml_map .get ("ns" , None ))
324
341
325
- def serialize (self , keep_readonly = False , ** kwargs ) :
342
+ def serialize (self , keep_readonly : bool = False , ** kwargs : Any ) -> JSON :
326
343
"""Return the JSON that would be sent to azure from this model.
327
344
328
345
This is an alias to `as_dict(full_restapi_key_transformer, keep_readonly=False)`.
@@ -336,8 +353,13 @@ def serialize(self, keep_readonly=False, **kwargs):
336
353
serializer = Serializer (self ._infer_class_models ())
337
354
return serializer ._serialize (self , keep_readonly = keep_readonly , ** kwargs )
338
355
339
- def as_dict (self , keep_readonly = True , key_transformer = attribute_transformer , ** kwargs ):
340
- """Return a dict that can be JSONify using json.dump.
356
+ def as_dict (
357
+ self ,
358
+ keep_readonly : bool = True ,
359
+ key_transformer : Callable [[str , Dict [str , Any ], Any ], Any ] = attribute_transformer ,
360
+ ** kwargs : Any
361
+ ) -> JSON :
362
+ """Return a dict that can be serialized using json.dump.
341
363
342
364
Advanced usage might optionally use a callback as parameter:
343
365
@@ -384,7 +406,7 @@ def _infer_class_models(cls):
384
406
return client_models
385
407
386
408
@classmethod
387
- def deserialize (cls , data , content_type = None ):
409
+ def deserialize (cls : Type [ ModelType ] , data : Any , content_type : Optional [ str ] = None ) -> ModelType :
388
410
"""Parse a str using the RestAPI syntax and return a model.
389
411
390
412
:param str data: A str using RestAPI structure. JSON by default.
@@ -396,7 +418,12 @@ def deserialize(cls, data, content_type=None):
396
418
return deserializer (cls .__name__ , data , content_type = content_type )
397
419
398
420
@classmethod
399
- def from_dict (cls , data , key_extractors = None , content_type = None ):
421
+ def from_dict (
422
+ cls : Type [ModelType ],
423
+ data : Any ,
424
+ key_extractors : Optional [Callable [[str , Dict [str , Any ], Any ], Any ]] = None ,
425
+ content_type : Optional [str ] = None ,
426
+ ) -> ModelType :
400
427
"""Parse a dict using given key extractor return a model.
401
428
402
429
By default consider key
@@ -409,8 +436,8 @@ def from_dict(cls, data, key_extractors=None, content_type=None):
409
436
:raises: DeserializationError if something went wrong
410
437
"""
411
438
deserializer = Deserializer (cls ._infer_class_models ())
412
- deserializer .key_extractors = (
413
- [
439
+ deserializer .key_extractors = ( # type: ignore
440
+ [ # type: ignore
414
441
attribute_key_case_insensitive_extractor ,
415
442
rest_key_case_insensitive_extractor ,
416
443
last_rest_key_case_insensitive_extractor ,
@@ -518,7 +545,7 @@ class Serializer(object):
518
545
"multiple" : lambda x , y : x % y != 0 ,
519
546
}
520
547
521
- def __init__ (self , classes = None ):
548
+ def __init__ (self , classes : Optional [ Mapping [ str , Type [ ModelType ]]] = None ):
522
549
self .serialize_type = {
523
550
"iso-8601" : Serializer .serialize_iso ,
524
551
"rfc-1123" : Serializer .serialize_rfc ,
@@ -534,7 +561,7 @@ def __init__(self, classes=None):
534
561
"[]" : self .serialize_iter ,
535
562
"{}" : self .serialize_dict ,
536
563
}
537
- self .dependencies = dict (classes ) if classes else {}
564
+ self .dependencies : Dict [ str , Type [ ModelType ]] = dict (classes ) if classes else {}
538
565
self .key_transformer = full_restapi_key_transformer
539
566
self .client_side_validation = True
540
567
@@ -602,7 +629,7 @@ def _serialize(self, target_obj, data_type=None, **kwargs):
602
629
if xml_desc .get ("attr" , False ):
603
630
if xml_ns :
604
631
ET .register_namespace (xml_prefix , xml_ns )
605
- xml_name = "{}{}" .format (xml_ns , xml_name )
632
+ xml_name = "{{{}} }{}" .format (xml_ns , xml_name )
606
633
serialized .set (xml_name , new_attr ) # type: ignore
607
634
continue
608
635
if xml_desc .get ("text" , False ):
@@ -626,8 +653,7 @@ def _serialize(self, target_obj, data_type=None, **kwargs):
626
653
serialized .append (local_node ) # type: ignore
627
654
else : # JSON
628
655
for k in reversed (keys ): # type: ignore
629
- unflattened = {k : new_attr }
630
- new_attr = unflattened
656
+ new_attr = {k : new_attr }
631
657
632
658
_new_attr = new_attr
633
659
_serialized = serialized
@@ -636,8 +662,9 @@ def _serialize(self, target_obj, data_type=None, **kwargs):
636
662
_serialized .update (_new_attr ) # type: ignore
637
663
_new_attr = _new_attr [k ] # type: ignore
638
664
_serialized = _serialized [k ]
639
- except ValueError :
640
- continue
665
+ except ValueError as err :
666
+ if isinstance (err , SerializationError ):
667
+ raise
641
668
642
669
except (AttributeError , KeyError , TypeError ) as err :
643
670
msg = "Attribute {} in object {} cannot be serialized.\n {}" .format (attr_name , class_name , str (target_obj ))
@@ -656,8 +683,8 @@ def body(self, data, data_type, **kwargs):
656
683
"""
657
684
658
685
# Just in case this is a dict
659
- internal_data_type = data_type .strip ("[]{}" )
660
- internal_data_type = self .dependencies .get (internal_data_type , None )
686
+ internal_data_type_str = data_type .strip ("[]{}" )
687
+ internal_data_type = self .dependencies .get (internal_data_type_str , None )
661
688
try :
662
689
is_xml_model_serialization = kwargs ["is_xml" ]
663
690
except KeyError :
@@ -715,6 +742,8 @@ def query(self, name, data, data_type, **kwargs):
715
742
716
743
:param data: The data to be serialized.
717
744
:param str data_type: The type to be serialized from.
745
+ :keyword bool skip_quote: Whether to skip quote the serialized result.
746
+ Defaults to False.
718
747
:rtype: str
719
748
:raises: TypeError if serialization fails.
720
749
:raises: ValueError if data is None
@@ -723,10 +752,8 @@ def query(self, name, data, data_type, **kwargs):
723
752
# Treat the list aside, since we don't want to encode the div separator
724
753
if data_type .startswith ("[" ):
725
754
internal_data_type = data_type [1 :- 1 ]
726
- data = [self .serialize_data (d , internal_data_type , ** kwargs ) if d is not None else "" for d in data ]
727
- if not kwargs .get ("skip_quote" , False ):
728
- data = [quote (str (d ), safe = "" ) for d in data ]
729
- return str (self .serialize_iter (data , internal_data_type , ** kwargs ))
755
+ do_quote = not kwargs .get ("skip_quote" , False )
756
+ return str (self .serialize_iter (data , internal_data_type , do_quote = do_quote , ** kwargs ))
730
757
731
758
# Not a list, regular serialization
732
759
output = self .serialize_data (data , data_type , ** kwargs )
@@ -777,6 +804,8 @@ def serialize_data(self, data, data_type, **kwargs):
777
804
raise ValueError ("No value for given attribute" )
778
805
779
806
try :
807
+ if data is AzureCoreNull :
808
+ return None
780
809
if data_type in self .basic_types .values ():
781
810
return self .serialize_basic (data , data_type , ** kwargs )
782
811
@@ -863,6 +892,8 @@ def serialize_iter(self, data, iter_type, div=None, **kwargs):
863
892
not be None or empty.
864
893
:param str div: If set, this str will be used to combine the elements
865
894
in the iterable into a combined string. Default is 'None'.
895
+ :keyword bool do_quote: Whether to quote the serialized result of each iterable element.
896
+ Defaults to False.
866
897
:rtype: list, str
867
898
"""
868
899
if isinstance (data , str ):
@@ -875,9 +906,14 @@ def serialize_iter(self, data, iter_type, div=None, **kwargs):
875
906
for d in data :
876
907
try :
877
908
serialized .append (self .serialize_data (d , iter_type , ** kwargs ))
878
- except ValueError :
909
+ except ValueError as err :
910
+ if isinstance (err , SerializationError ):
911
+ raise
879
912
serialized .append (None )
880
913
914
+ if kwargs .get ("do_quote" , False ):
915
+ serialized = ["" if s is None else quote (str (s ), safe = "" ) for s in serialized ]
916
+
881
917
if div :
882
918
serialized = ["" if s is None else str (s ) for s in serialized ]
883
919
serialized = div .join (serialized )
@@ -922,7 +958,9 @@ def serialize_dict(self, attr, dict_type, **kwargs):
922
958
for key , value in attr .items ():
923
959
try :
924
960
serialized [self .serialize_unicode (key )] = self .serialize_data (value , dict_type , ** kwargs )
925
- except ValueError :
961
+ except ValueError as err :
962
+ if isinstance (err , SerializationError ):
963
+ raise
926
964
serialized [self .serialize_unicode (key )] = None
927
965
928
966
if "xml" in serialization_ctxt :
@@ -1161,7 +1199,8 @@ def rest_key_extractor(attr, attr_desc, data):
1161
1199
working_data = data
1162
1200
1163
1201
while "." in key :
1164
- dict_keys = _FLATTEN .split (key )
1202
+ # Need the cast, as for some reasons "split" is typed as list[str | Any]
1203
+ dict_keys = cast (List [str ], _FLATTEN .split (key ))
1165
1204
if len (dict_keys ) == 1 :
1166
1205
key = _decode_attribute_map_key (dict_keys [0 ])
1167
1206
break
@@ -1242,7 +1281,7 @@ def _extract_name_from_internal_type(internal_type):
1242
1281
xml_name = internal_type_xml_map .get ("name" , internal_type .__name__ )
1243
1282
xml_ns = internal_type_xml_map .get ("ns" , None )
1244
1283
if xml_ns :
1245
- xml_name = "{}{}" .format (xml_ns , xml_name )
1284
+ xml_name = "{{{}} }{}" .format (xml_ns , xml_name )
1246
1285
return xml_name
1247
1286
1248
1287
@@ -1266,7 +1305,7 @@ def xml_key_extractor(attr, attr_desc, data):
1266
1305
# Integrate namespace if necessary
1267
1306
xml_ns = xml_desc .get ("ns" , internal_type_xml_map .get ("ns" , None ))
1268
1307
if xml_ns :
1269
- xml_name = "{}{}" .format (xml_ns , xml_name )
1308
+ xml_name = "{{{}} }{}" .format (xml_ns , xml_name )
1270
1309
1271
1310
# If it's an attribute, that's simple
1272
1311
if xml_desc .get ("attr" , False ):
@@ -1332,7 +1371,7 @@ class Deserializer(object):
1332
1371
1333
1372
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}]?" )
1334
1373
1335
- def __init__ (self , classes = None ):
1374
+ def __init__ (self , classes : Optional [ Mapping [ str , Type [ ModelType ]]] = None ):
1336
1375
self .deserialize_type = {
1337
1376
"iso-8601" : Deserializer .deserialize_iso ,
1338
1377
"rfc-1123" : Deserializer .deserialize_rfc ,
@@ -1352,7 +1391,7 @@ def __init__(self, classes=None):
1352
1391
"duration" : (isodate .Duration , datetime .timedelta ),
1353
1392
"iso-8601" : (datetime .datetime ),
1354
1393
}
1355
- self .dependencies = dict (classes ) if classes else {}
1394
+ self .dependencies : Dict [ str , Type [ ModelType ]] = dict (classes ) if classes else {}
1356
1395
self .key_extractors = [rest_key_extractor , xml_key_extractor ]
1357
1396
# Additional properties only works if the "rest_key_extractor" is used to
1358
1397
# extract the keys. Making it to work whatever the key extractor is too much
@@ -1471,7 +1510,7 @@ def _classify_target(self, target, data):
1471
1510
Once classification has been determined, initialize object.
1472
1511
1473
1512
:param str target: The target object type to deserialize to.
1474
- :param str/dict data: The response data to deseralize .
1513
+ :param str/dict data: The response data to deserialize .
1475
1514
"""
1476
1515
if target is None :
1477
1516
return None , None
@@ -1486,7 +1525,7 @@ def _classify_target(self, target, data):
1486
1525
target = target ._classify (data , self .dependencies )
1487
1526
except AttributeError :
1488
1527
pass # Target is not a Model, no classify
1489
- return target , target .__class__ .__name__
1528
+ return target , target .__class__ .__name__ # type: ignore
1490
1529
1491
1530
def failsafe_deserialize (self , target_obj , data , content_type = None ):
1492
1531
"""Ignores any errors encountered in deserialization,
@@ -1496,7 +1535,7 @@ def failsafe_deserialize(self, target_obj, data, content_type=None):
1496
1535
a deserialization error.
1497
1536
1498
1537
:param str target_obj: The target object type to deserialize to.
1499
- :param str/dict data: The response data to deseralize .
1538
+ :param str/dict data: The response data to deserialize .
1500
1539
:param str content_type: Swagger "produces" if available.
1501
1540
"""
1502
1541
try :
0 commit comments