From 55a83e701c78ffab4dfe209243147025dbf3ccf7 Mon Sep 17 00:00:00 2001 From: Laurent Mazuel Date: Thu, 12 Jul 2018 13:56:39 -0700 Subject: [PATCH 1/4] additionalProperties ({str}) --- msrest/serialization.py | 26 ++++++++++++--- tests/test_xml_serialization.py | 57 +++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/msrest/serialization.py b/msrest/serialization.py index e6c05ec250..f9d160bd21 100644 --- a/msrest/serialization.py +++ b/msrest/serialization.py @@ -819,6 +819,7 @@ def serialize_dict(self, attr, dict_type, **kwargs): not be None or empty. :rtype: dict """ + serialization_ctxt = kwargs.get("serialization_ctxt", {}) serialized = {} for key, value in attr.items(): try: @@ -826,6 +827,21 @@ def serialize_dict(self, attr, dict_type, **kwargs): value, dict_type, **kwargs) except ValueError: serialized[self.serialize_unicode(key)] = None + + if 'xml' in serialization_ctxt: + # XML serialization is more complicated + xml_desc = serialization_ctxt['xml'] + xml_name = xml_desc['name'] + + final_result = _create_xml_node( + xml_name, + xml_desc.get('prefix', None), + xml_desc.get('ns', None) + ) + for key, value in serialized.items(): + ET.SubElement(final_result, key).text = value + return final_result + return serialized def serialize_object(self, attr, **kwargs): @@ -1474,10 +1490,12 @@ def deserialize_dict(self, attr, dict_type): :rtype: dict """ if isinstance(attr, list): - return {x['key']: self.deserialize_data( - x['value'], dict_type) for x in attr} - return {k: self.deserialize_data( - v, dict_type) for k, v in attr.items()} + return {x['key']: self.deserialize_data(x['value'], dict_type) for x in attr} + + if isinstance(attr, ET.Element): + # Transform value into {"Key": "value"} + attr = {el.tag: el.text for el in attr} + return {k: self.deserialize_data(v, dict_type) for k, v in attr.items()} def deserialize_object(self, attr, **kwargs): """Deserialize a generic object. diff --git a/tests/test_xml_serialization.py b/tests/test_xml_serialization.py index 20117d3090..75740913e0 100644 --- a/tests/test_xml_serialization.py +++ b/tests/test_xml_serialization.py @@ -69,6 +69,32 @@ class XmlModel(Model): assert result.age == 37 assert result.country == "france" + def test_add_prop(self): + """Test addProp as a dict. + """ + basic_xml = """ + + + value1 + value2 + + """ + + class XmlModel(Model): + _attribute_map = { + 'metadata': {'key': 'Metadata', 'type': '{str}', 'xml': {'name': 'Metadata'}}, + } + _xml_map = { + 'name': 'Data' + } + + s = Deserializer({"XmlModel": XmlModel}) + result = s(XmlModel, basic_xml, "application/xml") + + assert len(result.metadata) == 2 + assert result.metadata['Key1'] == "value1" + assert result.metadata['Key2'] == "value2" + def test_object(self): basic_xml = """ @@ -399,6 +425,37 @@ class XmlModel(Model): assert_xml_equals(rawxml, basic_xml) + def test_add_prop(self): + """Test addProp as a dict. + """ + basic_xml = ET.fromstring(""" + + + value1 + value2 + + """) + + class XmlModel(Model): + _attribute_map = { + 'metadata': {'key': 'Metadata', 'type': '{str}', 'xml': {'name': 'Metadata'}}, + } + _xml_map = { + 'name': 'Data' + } + + mymodel = XmlModel( + metadata={ + 'Key1': 'value1', + 'Key2': 'value2', + } + ) + + s = Serializer({"XmlModel": XmlModel}) + rawxml = s.body(mymodel, 'XmlModel') + + assert_xml_equals(rawxml, basic_xml) + def test_object(self): """Test serialize object as is. """ From 3eae39cb213a4333e6714f9234adc6e003c5536b Mon Sep 17 00:00:00 2001 From: Laurent Mazuel Date: Thu, 12 Jul 2018 14:14:38 -0700 Subject: [PATCH 2/4] Add requests.Response test for Content-Type --- tests/test_xml_serialization.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/test_xml_serialization.py b/tests/test_xml_serialization.py index 75740913e0..24622771ef 100644 --- a/tests/test_xml_serialization.py +++ b/tests/test_xml_serialization.py @@ -26,6 +26,8 @@ import sys import xml.etree.ElementTree as ET +import requests + import pytest from msrest.serialization import Serializer, Deserializer, Model, xml_key_extractor @@ -111,6 +113,27 @@ def test_object(self): assert child.tag == "Age" assert child.text == "37" + def test_object_from_requests(self): + basic_xml = b""" + + 37 + """ + + response = requests.Response() + response.headers["content-type"] = "application/xml; charset=utf-8" + response._content = basic_xml + response._content_consumed = True + + s = Deserializer() + result = s('object', response) + + # Should be a XML tree + assert result.tag == "Data" + assert result.get("country") == "france" + for child in result: + assert child.tag == "Age" + assert child.text == "37" + def test_basic_empty(self): """Test an basic XML with an empty node.""" basic_xml = """ From 1e82501b43b246b2da305e6864cc2780c7286515 Mon Sep 17 00:00:00 2001 From: Laurent Mazuel Date: Thu, 12 Jul 2018 14:53:02 -0700 Subject: [PATCH 3/4] Improve empty text handling --- msrest/serialization.py | 14 +++++++++++--- tests/test_xml_serialization.py | 27 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/msrest/serialization.py b/msrest/serialization.py index f9d160bd21..32beeab38c 100644 --- a/msrest/serialization.py +++ b/msrest/serialization.py @@ -1441,7 +1441,9 @@ def deserialize_data(self, data, data_type): if data_type in self.deserialize_type: if isinstance(data, self.deserialize_expected_types.get(data_type, tuple())): return data - if isinstance(data, ET.Element) and not data.text: + + is_a_text_parsing_type = lambda x: x not in ["object", "[]", r"{}"] + if isinstance(data, ET.Element) and is_a_text_parsing_type(data_type) and not data.text: return None data_val = self.deserialize_type[data_type](data) return data_val @@ -1555,8 +1557,14 @@ def deserialize_basic(self, attr, data_type): # If it's still an XML node, take the text if isinstance(attr, ET.Element): attr = attr.text - if not attr: # None or '', < node is empty string. - return '' + if not attr: + if data_type == "str": + # None or '', node is empty string. + return '' + else: + # None or '', node with a strong type is None. + # Don't try to model "empty bool" or "empty int" + return None if data_type == 'bool': if attr in [True, False, 1, 0]: diff --git a/tests/test_xml_serialization.py b/tests/test_xml_serialization.py index 24622771ef..7ae434712f 100644 --- a/tests/test_xml_serialization.py +++ b/tests/test_xml_serialization.py @@ -53,12 +53,21 @@ def test_basic(self): """Test an ultra basic XML.""" basic_xml = """ + 12 + 37 + + """ class XmlModel(Model): _attribute_map = { + 'longnode': {'key': 'longnode', 'type': 'long', 'xml':{'name': 'Long'}}, + 'empty_long': {'key': 'empty_long', 'type': 'long', 'xml':{'name': 'EmptyLong'}}, 'age': {'key': 'age', 'type': 'int', 'xml':{'name': 'Age'}}, + 'empty_age': {'key': 'empty_age', 'type': 'int', 'xml':{'name': 'EmptyAge'}}, + 'empty_string': {'key': 'empty_string', 'type': 'str', 'xml':{'name': 'EmptyString'}}, + 'not_set': {'key': 'not_set', 'type': 'str', 'xml':{'name': 'NotSet'}}, 'country': {'key': 'country', 'type': 'str', 'xml':{'name': 'country', 'attr': True}}, } _xml_map = { @@ -68,8 +77,13 @@ class XmlModel(Model): s = Deserializer({"XmlModel": XmlModel}) result = s(XmlModel, basic_xml, "application/xml") + assert result.longnode == 12 + assert result.empty_long is None assert result.age == 37 + assert result.empty_age is None assert result.country == "france" + assert result.empty_string == "" + assert result.not_set is None def test_add_prop(self): """Test addProp as a dict. @@ -113,6 +127,19 @@ def test_object(self): assert child.tag == "Age" assert child.text == "37" + def test_object_no_text(self): + basic_xml = """37""" + + s = Deserializer() + result = s('object', basic_xml, "application/xml") + + # Should be a XML tree + assert result.tag == "Data" + assert result.get("country") == "france" + for child in result: + assert child.tag == "Age" + assert child.text == "37" + def test_object_from_requests(self): basic_xml = b""" From b9a4a930c0ff37f69770f9adaaaeaedbcf9000ff Mon Sep 17 00:00:00 2001 From: Laurent Mazuel Date: Thu, 12 Jul 2018 14:58:32 -0700 Subject: [PATCH 4/4] Disable addProp serialization if not at least Py3.6 --- tests/test_xml_serialization.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_xml_serialization.py b/tests/test_xml_serialization.py index 7ae434712f..d91a4bf60f 100644 --- a/tests/test_xml_serialization.py +++ b/tests/test_xml_serialization.py @@ -475,6 +475,8 @@ class XmlModel(Model): assert_xml_equals(rawxml, basic_xml) + @pytest.mark.skipif(sys.version_info < (3,6), + reason="Dict ordering not guaranted before 3.6, makes this complicated to test.") def test_add_prop(self): """Test addProp as a dict. """