3838import re
3939import sys
4040import 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+ )
4257
4358try :
4459 from urllib import quote # type: ignore
4863
4964import isodate # type: ignore
5065
51- from typing import Dict , Any , cast
52-
5366from azure .core .exceptions import DeserializationError , SerializationError , raise_with_traceback
67+ from azure .core .serialization import NULL as AzureCoreNull
5468
5569_BOM = codecs .BOM_UTF8 .decode (encoding = "utf-8" )
5670
71+ ModelType = TypeVar ("ModelType" , bound = "Model" )
72+ JSON = MutableMapping [str , Any ]
73+
5774
5875class RawDeserializer :
5976
@@ -277,8 +294,8 @@ class Model(object):
277294 _attribute_map : Dict [str , Dict [str , Any ]] = {}
278295 _validation : Dict [str , Dict [str , Any ]] = {}
279296
280- def __init__ (self , ** kwargs ) :
281- self .additional_properties = {}
297+ def __init__ (self , ** kwargs : Any ) -> None :
298+ self .additional_properties : Dict [ str , Any ] = {}
282299 for k in kwargs :
283300 if k not in self ._attribute_map :
284301 _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):
287304 else :
288305 setattr (self , k , kwargs [k ])
289306
290- def __eq__ (self , other ) :
307+ def __eq__ (self , other : Any ) -> bool :
291308 """Compare objects by comparing all attributes."""
292309 if isinstance (other , self .__class__ ):
293310 return self .__dict__ == other .__dict__
294311 return False
295312
296- def __ne__ (self , other ) :
313+ def __ne__ (self , other : Any ) -> bool :
297314 """Compare objects by comparing all attributes."""
298315 return not self .__eq__ (other )
299316
300- def __str__ (self ):
317+ def __str__ (self ) -> str :
301318 return str (self .__dict__ )
302319
303320 @classmethod
304- def enable_additional_properties_sending (cls ):
321+ def enable_additional_properties_sending (cls ) -> None :
305322 cls ._attribute_map ["additional_properties" ] = {"key" : "" , "type" : "{object}" }
306323
307324 @classmethod
308- def is_xml_model (cls ):
325+ def is_xml_model (cls ) -> bool :
309326 try :
310327 cls ._xml_map # type: ignore
311328 except AttributeError :
@@ -322,7 +339,7 @@ def _create_xml_node(cls):
322339
323340 return _create_xml_node (xml_map .get ("name" , cls .__name__ ), xml_map .get ("prefix" , None ), xml_map .get ("ns" , None ))
324341
325- def serialize (self , keep_readonly = False , ** kwargs ) :
342+ def serialize (self , keep_readonly : bool = False , ** kwargs : Any ) -> JSON :
326343 """Return the JSON that would be sent to azure from this model.
327344
328345 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):
336353 serializer = Serializer (self ._infer_class_models ())
337354 return serializer ._serialize (self , keep_readonly = keep_readonly , ** kwargs )
338355
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.
341363
342364 Advanced usage might optionally use a callback as parameter:
343365
@@ -384,7 +406,7 @@ def _infer_class_models(cls):
384406 return client_models
385407
386408 @classmethod
387- def deserialize (cls , data , content_type = None ):
409+ def deserialize (cls : Type [ ModelType ] , data : Any , content_type : Optional [ str ] = None ) -> ModelType :
388410 """Parse a str using the RestAPI syntax and return a model.
389411
390412 :param str data: A str using RestAPI structure. JSON by default.
@@ -396,7 +418,12 @@ def deserialize(cls, data, content_type=None):
396418 return deserializer (cls .__name__ , data , content_type = content_type )
397419
398420 @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 :
400427 """Parse a dict using given key extractor return a model.
401428
402429 By default consider key
@@ -409,8 +436,8 @@ def from_dict(cls, data, key_extractors=None, content_type=None):
409436 :raises: DeserializationError if something went wrong
410437 """
411438 deserializer = Deserializer (cls ._infer_class_models ())
412- deserializer .key_extractors = (
413- [
439+ deserializer .key_extractors = ( # type: ignore
440+ [ # type: ignore
414441 attribute_key_case_insensitive_extractor ,
415442 rest_key_case_insensitive_extractor ,
416443 last_rest_key_case_insensitive_extractor ,
@@ -518,7 +545,7 @@ class Serializer(object):
518545 "multiple" : lambda x , y : x % y != 0 ,
519546 }
520547
521- def __init__ (self , classes = None ):
548+ def __init__ (self , classes : Optional [ Mapping [ str , Type [ ModelType ]]] = None ):
522549 self .serialize_type = {
523550 "iso-8601" : Serializer .serialize_iso ,
524551 "rfc-1123" : Serializer .serialize_rfc ,
@@ -534,7 +561,7 @@ def __init__(self, classes=None):
534561 "[]" : self .serialize_iter ,
535562 "{}" : self .serialize_dict ,
536563 }
537- self .dependencies = dict (classes ) if classes else {}
564+ self .dependencies : Dict [ str , Type [ ModelType ]] = dict (classes ) if classes else {}
538565 self .key_transformer = full_restapi_key_transformer
539566 self .client_side_validation = True
540567
@@ -602,7 +629,7 @@ def _serialize(self, target_obj, data_type=None, **kwargs):
602629 if xml_desc .get ("attr" , False ):
603630 if xml_ns :
604631 ET .register_namespace (xml_prefix , xml_ns )
605- xml_name = "{}{}" .format (xml_ns , xml_name )
632+ xml_name = "{{{}} }{}" .format (xml_ns , xml_name )
606633 serialized .set (xml_name , new_attr ) # type: ignore
607634 continue
608635 if xml_desc .get ("text" , False ):
@@ -626,8 +653,7 @@ def _serialize(self, target_obj, data_type=None, **kwargs):
626653 serialized .append (local_node ) # type: ignore
627654 else : # JSON
628655 for k in reversed (keys ): # type: ignore
629- unflattened = {k : new_attr }
630- new_attr = unflattened
656+ new_attr = {k : new_attr }
631657
632658 _new_attr = new_attr
633659 _serialized = serialized
@@ -636,8 +662,9 @@ def _serialize(self, target_obj, data_type=None, **kwargs):
636662 _serialized .update (_new_attr ) # type: ignore
637663 _new_attr = _new_attr [k ] # type: ignore
638664 _serialized = _serialized [k ]
639- except ValueError :
640- continue
665+ except ValueError as err :
666+ if isinstance (err , SerializationError ):
667+ raise
641668
642669 except (AttributeError , KeyError , TypeError ) as err :
643670 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):
656683 """
657684
658685 # 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 )
661688 try :
662689 is_xml_model_serialization = kwargs ["is_xml" ]
663690 except KeyError :
@@ -715,6 +742,8 @@ def query(self, name, data, data_type, **kwargs):
715742
716743 :param data: The data to be serialized.
717744 :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.
718747 :rtype: str
719748 :raises: TypeError if serialization fails.
720749 :raises: ValueError if data is None
@@ -723,10 +752,8 @@ def query(self, name, data, data_type, **kwargs):
723752 # Treat the list aside, since we don't want to encode the div separator
724753 if data_type .startswith ("[" ):
725754 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 ))
730757
731758 # Not a list, regular serialization
732759 output = self .serialize_data (data , data_type , ** kwargs )
@@ -777,6 +804,8 @@ def serialize_data(self, data, data_type, **kwargs):
777804 raise ValueError ("No value for given attribute" )
778805
779806 try :
807+ if data is AzureCoreNull :
808+ return None
780809 if data_type in self .basic_types .values ():
781810 return self .serialize_basic (data , data_type , ** kwargs )
782811
@@ -863,6 +892,8 @@ def serialize_iter(self, data, iter_type, div=None, **kwargs):
863892 not be None or empty.
864893 :param str div: If set, this str will be used to combine the elements
865894 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.
866897 :rtype: list, str
867898 """
868899 if isinstance (data , str ):
@@ -875,9 +906,14 @@ def serialize_iter(self, data, iter_type, div=None, **kwargs):
875906 for d in data :
876907 try :
877908 serialized .append (self .serialize_data (d , iter_type , ** kwargs ))
878- except ValueError :
909+ except ValueError as err :
910+ if isinstance (err , SerializationError ):
911+ raise
879912 serialized .append (None )
880913
914+ if kwargs .get ("do_quote" , False ):
915+ serialized = ["" if s is None else quote (str (s ), safe = "" ) for s in serialized ]
916+
881917 if div :
882918 serialized = ["" if s is None else str (s ) for s in serialized ]
883919 serialized = div .join (serialized )
@@ -922,7 +958,9 @@ def serialize_dict(self, attr, dict_type, **kwargs):
922958 for key , value in attr .items ():
923959 try :
924960 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
926964 serialized [self .serialize_unicode (key )] = None
927965
928966 if "xml" in serialization_ctxt :
@@ -1161,7 +1199,8 @@ def rest_key_extractor(attr, attr_desc, data):
11611199 working_data = data
11621200
11631201 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 ))
11651204 if len (dict_keys ) == 1 :
11661205 key = _decode_attribute_map_key (dict_keys [0 ])
11671206 break
@@ -1242,7 +1281,7 @@ def _extract_name_from_internal_type(internal_type):
12421281 xml_name = internal_type_xml_map .get ("name" , internal_type .__name__ )
12431282 xml_ns = internal_type_xml_map .get ("ns" , None )
12441283 if xml_ns :
1245- xml_name = "{}{}" .format (xml_ns , xml_name )
1284+ xml_name = "{{{}} }{}" .format (xml_ns , xml_name )
12461285 return xml_name
12471286
12481287
@@ -1266,7 +1305,7 @@ def xml_key_extractor(attr, attr_desc, data):
12661305 # Integrate namespace if necessary
12671306 xml_ns = xml_desc .get ("ns" , internal_type_xml_map .get ("ns" , None ))
12681307 if xml_ns :
1269- xml_name = "{}{}" .format (xml_ns , xml_name )
1308+ xml_name = "{{{}} }{}" .format (xml_ns , xml_name )
12701309
12711310 # If it's an attribute, that's simple
12721311 if xml_desc .get ("attr" , False ):
@@ -1332,7 +1371,7 @@ class Deserializer(object):
13321371
13331372 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}]?" )
13341373
1335- def __init__ (self , classes = None ):
1374+ def __init__ (self , classes : Optional [ Mapping [ str , Type [ ModelType ]]] = None ):
13361375 self .deserialize_type = {
13371376 "iso-8601" : Deserializer .deserialize_iso ,
13381377 "rfc-1123" : Deserializer .deserialize_rfc ,
@@ -1352,7 +1391,7 @@ def __init__(self, classes=None):
13521391 "duration" : (isodate .Duration , datetime .timedelta ),
13531392 "iso-8601" : (datetime .datetime ),
13541393 }
1355- self .dependencies = dict (classes ) if classes else {}
1394+ self .dependencies : Dict [ str , Type [ ModelType ]] = dict (classes ) if classes else {}
13561395 self .key_extractors = [rest_key_extractor , xml_key_extractor ]
13571396 # Additional properties only works if the "rest_key_extractor" is used to
13581397 # extract the keys. Making it to work whatever the key extractor is too much
@@ -1471,7 +1510,7 @@ def _classify_target(self, target, data):
14711510 Once classification has been determined, initialize object.
14721511
14731512 :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 .
14751514 """
14761515 if target is None :
14771516 return None , None
@@ -1486,7 +1525,7 @@ def _classify_target(self, target, data):
14861525 target = target ._classify (data , self .dependencies )
14871526 except AttributeError :
14881527 pass # Target is not a Model, no classify
1489- return target , target .__class__ .__name__
1528+ return target , target .__class__ .__name__ # type: ignore
14901529
14911530 def failsafe_deserialize (self , target_obj , data , content_type = None ):
14921531 """Ignores any errors encountered in deserialization,
@@ -1496,7 +1535,7 @@ def failsafe_deserialize(self, target_obj, data, content_type=None):
14961535 a deserialization error.
14971536
14981537 :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 .
15001539 :param str content_type: Swagger "produces" if available.
15011540 """
15021541 try :
0 commit comments