Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 33 additions & 7 deletions msrest/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -819,13 +819,29 @@ 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:
serialized[self.serialize_unicode(key)] = self.serialize_data(
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):
Expand Down Expand Up @@ -1425,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
Expand Down Expand Up @@ -1474,10 +1492,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 <Key>value</Key> 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.
Expand Down Expand Up @@ -1537,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 <a/> is empty string.
return ''
if not attr:
if data_type == "str":
# None or '', node <a/> is empty string.
return ''
else:
# None or '', node <a/> 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]:
Expand Down
109 changes: 109 additions & 0 deletions tests/test_xml_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -51,12 +53,21 @@ def test_basic(self):
"""Test an ultra basic XML."""
basic_xml = """<?xml version="1.0"?>
<Data country="france">
<Long>12</Long>
<EmptyLong/>
<Age>37</Age>
<EmptyAge/>
<EmptyString/>
</Data>"""

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 = {
Expand All @@ -66,8 +77,39 @@ 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.
"""
basic_xml = """<?xml version="1.0"?>
<Data>
<Metadata>
<Key1>value1</Key1>
<Key2>value2</Key2>
</Metadata>
</Data>"""

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 = """<?xml version="1.0"?>
Expand All @@ -85,6 +127,40 @@ def test_object(self):
assert child.tag == "Age"
assert child.text == "37"

def test_object_no_text(self):
basic_xml = """<?xml version="1.0"?><Data country="france"><Age>37</Age></Data>"""

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"""<?xml version="1.0"?>
<Data country="france">
<Age>37</Age>
</Data>"""

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 = """<?xml version="1.0"?>
Expand Down Expand Up @@ -399,6 +475,39 @@ 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.
"""
basic_xml = ET.fromstring("""<?xml version="1.0"?>
<Data>
<Metadata>
<Key1>value1</Key1>
<Key2>value2</Key2>
</Metadata>
</Data>""")

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.
"""
Expand Down