Skip to content
This repository was archived by the owner on Feb 28, 2026. It is now read-only.
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
12 changes: 12 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ To install:
Release History
---------------

2020-XX-XX Version 0.6.16
+++++++++++++++++++++++++

**Bugfixes**

- Fix XML parsing with namespaces and attributes #209

**Features**

- Add py.typed for mypy support


2020-06-04 Version 0.6.15
+++++++++++++++++++++++++

Expand Down
39 changes: 19 additions & 20 deletions msrest/serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,12 @@ def _serialize(self, target_obj, data_type=None, **kwargs):
if is_xml_model_serialization:
xml_desc = attr_desc.get('xml', {})
xml_name = xml_desc.get('name', attr_desc['key'])
xml_prefix = xml_desc.get('prefix', None)
xml_ns = xml_desc.get('ns', None)
if xml_desc.get("attr", False):
if xml_ns:
ET.register_namespace(xml_prefix, xml_ns)
xml_name = "{{{}}}{}".format(xml_ns, xml_name)
serialized.set(xml_name, new_attr)
continue
if isinstance(new_attr, list):
Expand All @@ -537,8 +542,8 @@ def _serialize(self, target_obj, data_type=None, **kwargs):
# Integrate namespace if necessary
local_node = _create_xml_node(
xml_name,
xml_desc.get('prefix', None),
xml_desc.get('ns', None)
xml_prefix,
xml_ns
)
local_node.text = unicode_str(new_attr)
serialized.append(local_node)
Expand Down Expand Up @@ -1205,11 +1210,8 @@ def _extract_name_from_internal_type(internal_type):
xml_name = internal_type_xml_map.get('name', internal_type.__name__)
xml_ns = internal_type_xml_map.get("ns", None)
if xml_ns:
ns = {'prefix': xml_ns}
xml_name = "prefix:"+xml_name
else:
ns = {} # And keep same xml_name
return xml_name, ns
xml_name = "{{{}}}{}".format(xml_ns, xml_name)
return xml_name


def xml_key_extractor(attr, attr_desc, data):
Expand All @@ -1223,10 +1225,6 @@ def xml_key_extractor(attr, attr_desc, data):
xml_desc = attr_desc.get('xml', {})
xml_name = xml_desc.get('name', attr_desc['key'])

# If it's an attribute, that's simple
if xml_desc.get("attr", False):
return data.get(xml_name)

# Look for a children
is_iter_type = attr_desc['type'].startswith("[")
is_wrapped = xml_desc.get("wrapped", False)
Expand All @@ -1236,28 +1234,29 @@ def xml_key_extractor(attr, attr_desc, data):
# Integrate namespace if necessary
xml_ns = xml_desc.get('ns', internal_type_xml_map.get("ns", None))
if xml_ns:
ns = {'prefix': xml_ns}
xml_name = "prefix:"+xml_name
else:
ns = {} # And keep same xml_name
xml_name = "{{{}}}{}".format(xml_ns, xml_name)

# If it's an attribute, that's simple
if xml_desc.get("attr", False):
return data.get(xml_name)

# Scenario where I take the local name:
# - Wrapped node
# - Internal type is an enum (considered basic types)
# - Internal type has no XML/Name node
if is_wrapped or (internal_type and (issubclass(internal_type, Enum) or 'name' not in internal_type_xml_map)):
children = data.findall(xml_name, ns)
children = data.findall(xml_name)
# If internal type has a local name and it's not a list, I use that name
elif not is_iter_type and internal_type and 'name' in internal_type_xml_map:
xml_name, ns = _extract_name_from_internal_type(internal_type)
children = data.findall(xml_name, ns)
xml_name = _extract_name_from_internal_type(internal_type)
children = data.findall(xml_name)
# That's an array
else:
if internal_type: # Complex type, ignore itemsName and use the complex type name
items_name, ns = _extract_name_from_internal_type(internal_type)
items_name = _extract_name_from_internal_type(internal_type)
else:
items_name = xml_desc.get("itemsName", xml_name)
children = data.findall(items_name, ns)
children = data.findall(items_name)

if len(children) == 0:
if is_iter_type:
Expand Down
81 changes: 78 additions & 3 deletions tests/test_xml_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,21 +474,25 @@ class XmlModel(Model):
def test_complex_namespace(self):
"""Test recursive namespace."""
basic_xml = """<?xml version="1.0"?>
<entry xmlns="http://www.w3.org/2005/Atom">
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<author>
<name>lmazuel</name>
</author>
<AuthorizationRules xmlns="http://schemas.microsoft.com/netservices/2010/10/servicebus/connect">
<AuthorizationRule>
<AuthorizationRule i:type="SharedAccessAuthorizationRule">
<KeyName>testpolicy</KeyName>
</AuthorizationRule>
</AuthorizationRules>
<CountDetails xmlns="http://schemas.microsoft.com/netservices/2010/10/servicebus/connect">
<d2p1:ActiveMessageCount xmlns:d2p1="http://schemas.microsoft.com/netservices/2011/06/servicebus">12</d2p1:ActiveMessageCount>
</CountDetails>
</entry>"""

class XmlRoot(Model):
_attribute_map = {
'author': {'key': 'author', 'type': 'QueueDescriptionResponseAuthor'},
'authorization_rules': {'key': 'AuthorizationRules', 'type': '[AuthorizationRule]', 'xml': {'ns': 'http://schemas.microsoft.com/netservices/2010/10/servicebus/connect', 'wrapped': True, 'itemsNs': 'http://schemas.microsoft.com/netservices/2010/10/servicebus/connect'}},
'message_count_details': {'key': 'MessageCountDetails', 'type': 'MessageCountDetails'},
}
_xml_map = {
'name': 'entry', 'ns': 'http://www.w3.org/2005/Atom'
Expand All @@ -504,22 +508,34 @@ class QueueDescriptionResponseAuthor(Model):

class AuthorizationRule(Model):
_attribute_map = {
'type': {'key': 'type', 'type': 'str', 'xml': {'attr': True, 'prefix': 'i', 'ns': 'http://www.w3.org/2001/XMLSchema-instance'}},
'key_name': {'key': 'KeyName', 'type': 'str', 'xml': {'ns': 'http://schemas.microsoft.com/netservices/2010/10/servicebus/connect'}},
}
_xml_map = {
'ns': 'http://schemas.microsoft.com/netservices/2010/10/servicebus/connect'
}

class MessageCountDetails(Model):
_attribute_map = {
'active_message_count': {'key': 'ActiveMessageCount', 'type': 'int', 'xml': {'prefix': 'd2p1', 'ns': 'http://schemas.microsoft.com/netservices/2011/06/servicebus'}},
}
_xml_map = {
'name': 'CountDetails', 'ns': 'http://schemas.microsoft.com/netservices/2010/10/servicebus/connect'
}


s = Deserializer({
"XmlRoot": XmlRoot,
"QueueDescriptionResponseAuthor": QueueDescriptionResponseAuthor,
"AuthorizationRule": AuthorizationRule,
"MessageCountDetails": MessageCountDetails,
})
result = s(XmlRoot, basic_xml, "application/xml")

assert result.author.name == "lmazuel"
assert result.authorization_rules[0].key_name == "testpolicy"
assert result.authorization_rules[0].type == "SharedAccessAuthorizationRule"
assert result.message_count_details.active_message_count == 12


class TestXmlSerialization:
Expand Down Expand Up @@ -1409,4 +1425,63 @@ class XmlModel(Model):
s = Serializer({"XmlModel": XmlModel})
rawxml = s.body(mymodel, 'XmlModel', is_xml=True)

assert_xml_equals(rawxml, basic_xml)
assert_xml_equals(rawxml, basic_xml)

@pytest.mark.skipif(sys.version_info < (3,6),
reason="Unstable before python3.6 for some reasons")
def test_complex_namespace(self):
"""Test recursive namespace."""
basic_xml = ET.fromstring("""<?xml version="1.0"?>
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<author>
<name>lmazuel</name>
</author>
<AuthorizationRules xmlns="http://schemas.microsoft.com/netservices/2010/10/servicebus/connect">
<AuthorizationRule i:type="SharedAccessAuthorizationRule">
<KeyName>testpolicy</KeyName>
</AuthorizationRule>
</AuthorizationRules>
</entry>""")

class XmlRoot(Model):
_attribute_map = {
'author': {'key': 'author', 'type': 'QueueDescriptionResponseAuthor'},
'authorization_rules': {'key': 'AuthorizationRules', 'type': '[AuthorizationRule]', 'xml': {'ns': 'http://schemas.microsoft.com/netservices/2010/10/servicebus/connect', 'wrapped': True, 'itemsNs': 'http://schemas.microsoft.com/netservices/2010/10/servicebus/connect'}},
}
_xml_map = {
'name': 'entry', 'ns': 'http://www.w3.org/2005/Atom'
}

class QueueDescriptionResponseAuthor(Model):
_attribute_map = {
'name': {'key': 'name', 'type': 'str', 'xml': {'ns': 'http://www.w3.org/2005/Atom'}},
}
_xml_map = {
'ns': 'http://www.w3.org/2005/Atom'
}

class AuthorizationRule(Model):
_attribute_map = {
'type': {'key': 'type', 'type': 'str', 'xml': {'attr': True, 'prefix': 'i', 'ns': 'http://www.w3.org/2001/XMLSchema-instance'}},
'key_name': {'key': 'KeyName', 'type': 'str', 'xml': {'ns': 'http://schemas.microsoft.com/netservices/2010/10/servicebus/connect'}},
}
_xml_map = {
'ns': 'http://schemas.microsoft.com/netservices/2010/10/servicebus/connect'
}

mymodel = XmlRoot(
author = QueueDescriptionResponseAuthor(name = "lmazuel"),
authorization_rules = [AuthorizationRule(
type="SharedAccessAuthorizationRule",
key_name="testpolicy"
)]
)

s = Serializer({
"XmlRoot": XmlRoot,
"QueueDescriptionResponseAuthor": QueueDescriptionResponseAuthor,
"AuthorizationRule": AuthorizationRule,
})
rawxml = s.body(mymodel, 'XmlModel')

assert_xml_equals(rawxml, basic_xml)