Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply xsi:lookup for xs:any elements #405

Merged
merged 1 commit into from
Feb 14, 2021
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
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ exclude: tests/fixtures|docs/examples

repos:
- repo: https://github.com/asottile/pyupgrade
rev: v2.9.0
rev: v2.10.0
hooks:
- id: pyupgrade
args: [--py37-plus]
- repo: https://github.com/asottile/reorder_python_imports
rev: v2.3.6
rev: v2.4.0
hooks:
- id: reorder-python-imports
- repo: https://github.com/ambv/black
Expand Down
3 changes: 2 additions & 1 deletion tests/fixtures/defxmlschema/chapter12.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
"other_attributes": {
"{http://example.org/oth}custom": "12"
}
}
},
"substituted": false
}
]
}
3 changes: 2 additions & 1 deletion tests/fixtures/defxmlschema/chapter17.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"quantity": 1,
"color": null,
"number": 563
}
},
"substituted": false
}
]
},
Expand Down
42 changes: 40 additions & 2 deletions tests/formats/dataclass/parsers/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,11 @@ def test_bind_choice_generic_with_derived(self):
XmlVar(element=True, name="b", qname="b", types=[float]),
],
)
data = {"qname": "a", "value": 1, "substituted": True}

self.assertEqual(
DerivedElement(qname="a", value=1),
self.parser.bind_choice({"qname": "a", "value": 1}, var),
DerivedElement(qname="a", value=1, substituted=True),
self.parser.bind_choice(data, var),
)

def test_bind_choice_generic_with_wildcard(self):
Expand Down Expand Up @@ -199,3 +200,40 @@ def test_bind_choice_generic_with_unknown_qname(self):
"XmlElements undefined choice: `compound` for qname `foo`",
str(cm.exception),
)

def test_bind_wildcard_with_any_element(self):
var = XmlVar(
wildcard=True,
name="any_element",
qname="any_element",
types=[object],
)

self.assertEqual(
AnyElement(qname="a", text="1"),
self.parser.bind_value(var, {"qname": "a", "text": 1}),
)

def test_bind_wildcard_with_derived_element(self):
var = XmlVar(
any_type=True,
name="a",
qname="a",
types=[object],
)
actual = DerivedElement(qname="a", value=Books(book=[]), substituted=True)
data = {"qname": "a", "value": {"book": []}, "substituted": True}

self.assertEqual(actual, self.parser.bind_value(var, data))

def test_bind_wildcard_with_no_matching_value(self):
var = XmlVar(
any_type=True,
name="a",
qname="a",
types=[object],
)

data = {"test_bind_wildcard_with_no_matching_value": False}
self.assertEqual(data, self.parser.bind_value(var, data))
self.assertEqual(1, self.parser.bind_value(var, 1))
151 changes: 53 additions & 98 deletions tests/formats/dataclass/parsers/test_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
from xsdata.formats.dataclass.models.generics import DerivedElement
from xsdata.formats.dataclass.parsers.config import ParserConfig
from xsdata.formats.dataclass.parsers.mixins import XmlHandler
from xsdata.formats.dataclass.parsers.nodes import AnyTypeNode
from xsdata.formats.dataclass.parsers.nodes import ElementNode
from xsdata.formats.dataclass.parsers.nodes import NodeParser
from xsdata.formats.dataclass.parsers.nodes import PrimitiveNode
Expand Down Expand Up @@ -392,29 +391,42 @@ def test_build_node_with_any_type_var_with_matching_xsi_type(self):
self.assertEqual(ns_map, actual.ns_map)
self.assertFalse(actual.mixed)

def test_build_node_with_any_type_var_with_datatype(self):
var = XmlVar(element=True, name="a", qname="a", types=[object], any_type=True)
attrs = {QNames.XSI_TYPE: "xs:hexBinary"}
ns_map = {Namespace.XS.prefix: Namespace.XS.uri}
actual = self.node.build_node(var, attrs, ns_map, 10)

self.assertIsInstance(actual, PrimitiveNode)
self.assertEqual(ns_map, actual.ns_map)
self.assertEqual([DataType.HEX_BINARY.type], actual.types)
self.assertIsNone(actual.default)
self.assertFalse(actual.tokens)
self.assertEqual(DataType.HEX_BINARY.format, actual.format)
self.assertEqual(var.derived, actual.derived)
self.assertEqual(DataType.HEX_BINARY.wrapper, actual.wrapper)

def test_build_node_with_any_type_var_with_no_matching_xsi_type(self):
var = XmlVar(element=True, name="a", qname="a", types=[object], any_type=True)
attrs = {QNames.XSI_TYPE: "noMatch"}
actual = self.node.build_node(var, attrs, {}, 10)

self.assertIsInstance(actual, AnyTypeNode)
self.assertIsInstance(actual, WildcardNode)
self.assertEqual(10, actual.position)
self.assertEqual(var, actual.var)
self.assertEqual(attrs, actual.attrs)
self.assertEqual({}, actual.ns_map)
self.assertFalse(actual.mixed)

def test_build_node_with_any_type_var_with_no_xsi_type(self):
var = XmlVar(element=True, name="a", qname="a", types=[object], any_type=True)
attrs = {}
actual = self.node.build_node(var, attrs, {}, 10)

self.assertIsInstance(actual, AnyTypeNode)
self.assertIsInstance(actual, WildcardNode)
self.assertEqual(10, actual.position)
self.assertEqual(var, actual.var)
self.assertEqual(attrs, actual.attrs)
self.assertEqual({}, actual.ns_map)
self.assertFalse(actual.mixed)

def test_build_node_with_wildcard_var(self):
var = XmlVar(wildcard=True, name="a", qname="a", types=[], dataclass=False)
Expand All @@ -432,95 +444,13 @@ def test_build_node_with_primitive_var(self):
actual = self.node.build_node(var, attrs, ns_map, 10)

self.assertIsInstance(actual, PrimitiveNode)
self.assertEqual(var, actual.var)
self.assertEqual(ns_map, actual.ns_map)


class AnyTypeNodeTests(TestCase):
def setUp(self) -> None:
self.var = XmlVar(element=True, name="a", qname="a", types=[object])
self.node = AnyTypeNode(position=0, var=self.var, attrs={}, ns_map={})

def test_child(self):
self.assertFalse(self.node.has_children)

attrs = {"a": 1}
ns_map = {"ns0": "b"}
actual = self.node.child("foo", attrs, ns_map, 10)

self.assertIsInstance(actual, WildcardNode)
self.assertEqual(10, actual.position)
self.assertEqual(self.var, actual.var)
self.assertEqual(attrs, actual.attrs)
self.assertEqual(ns_map, actual.ns_map)
self.assertTrue(self.node.has_children)

def test_bind_with_children(self):
text = "\n "
tail = "bar"
generic = AnyElement(
qname="a",
text=None,
tail="bar",
attributes={},
children=[1, 2, 3],
)

objects = [("a", 1), ("b", 2), ("c", 3)]

self.node.has_children = True
self.assertTrue(self.node.bind("a", text, tail, objects))
self.assertEqual(self.var.qname, objects[-1][0])
self.assertEqual(generic, objects[-1][1])

def test_bind_with_simple_type(self):
objects = []

self.node.attrs[QNames.XSI_TYPE] = "xs:float"
self.node.ns_map["xs"] = Namespace.XS.uri

self.assertTrue(self.node.bind("a", "10", None, objects))
self.assertEqual(self.var.qname, objects[-1][0])
self.assertEqual(10.0, objects[-1][1])

def test_bind_with_simple_type_that_has_wrapper_class(self):
objects = []

self.node.attrs[QNames.XSI_TYPE] = "xs:hexBinary"
self.node.ns_map["xs"] = Namespace.XS.uri

self.assertTrue(self.node.bind("a", "4368726973", None, objects))
self.assertEqual(self.var.qname, objects[-1][0])
self.assertEqual(b"Chris", objects[-1][1])
self.assertIsInstance(objects[-1][1], XmlHexBinary)

def test_bind_with_simple_type_derived(self):
objects = []

self.node.var = XmlVar(
element=True, name="a", qname="a", types=[object], derived=True
)
self.node.attrs[QNames.XSI_TYPE] = str(DataType.FLOAT)

self.assertTrue(self.node.bind("a", "10", None, objects))
self.assertEqual(self.var.qname, objects[-1][0])
self.assertEqual(DerivedElement(qname="a", value=10.0), objects[-1][1])

def test_bind_with_simple_type_with_mixed_content(self):
objects = []

self.node.mixed = True
self.node.attrs[QNames.XSI_TYPE] = str(DataType.FLOAT)

self.assertTrue(self.node.bind("a", "10", "pieces", objects))
self.assertEqual(self.var.qname, objects[-2][0])
self.assertEqual(10.0, objects[-2][1])
self.assertIsNone(objects[-1][0])
self.assertEqual("pieces", objects[-1][1])

self.assertTrue(self.node.bind("a", "10", "\n", objects))
self.assertEqual(self.var.qname, objects[-1][0])
self.assertEqual(10.0, objects[-1][1])
self.assertEqual(actual.types, var.types)
self.assertEqual(actual.tokens, var.tokens)
self.assertEqual(actual.format, var.format)
self.assertEqual(actual.derived, var.derived)
self.assertEqual(actual.default, var.default)
self.assertIsNone(actual.wrapper)


class WildcardNodeTests(TestCase):
Expand All @@ -546,6 +476,21 @@ def test_bind(self):
self.assertEqual(var.qname, objects[-1][0])
self.assertEqual(generic, objects[-1][1])

# Preserve whitespace if no children
node.position = 1
node.bind("foo", text, tail, objects)
generic.text = text
generic.children = []
self.assertEqual(generic, objects[-1][1])

# Not a wildcard, no tail/attrs/children skip wrapper
tail = None
text = "1"
node.attrs = {}
node.position = 2
node.bind("a", text, tail, objects)
self.assertEqual("1", objects[-1][1])

def test_child(self):
attrs = {"id": "1"}
ns_map = {"ns0": "xsdata"}
Expand Down Expand Up @@ -648,7 +593,7 @@ def test_bind(self, mock_parse_value):
mock_parse_value.return_value = 13
var = XmlVar(text=True, name="foo", qname="foo", types=[int], format="Nope")
ns_map = {"foo": "bar"}
node = PrimitiveNode(var=var, ns_map=ns_map)
node = PrimitiveNode.from_var(var, ns_map)
objects = []

self.assertTrue(node.bind("foo", "13", "Impossible", objects))
Expand All @@ -658,17 +603,27 @@ def test_bind(self, mock_parse_value):
"13", var.types, var.default, ns_map, var.tokens, var.format
)

def test_bind_derived_var(self):
def test_bind_derived_mode(self):
var = XmlVar(text=True, name="foo", qname="foo", types=[int], derived=True)
ns_map = {"foo": "bar"}
node = PrimitiveNode(var=var, ns_map=ns_map)
node = PrimitiveNode.from_var(var, ns_map)
objects = []

self.assertTrue(node.bind("foo", "13", "Impossible", objects))
self.assertEqual(DerivedElement("foo", 13), objects[-1][1])

def test_bind_wrapper_mode(self):
datatype = DataType.HEX_BINARY
ns_map = {"foo": "bar"}
node = PrimitiveNode.from_datatype(datatype, True, ns_map)
objects = []

self.assertTrue(node.bind("foo", "13", "Impossible", objects))
self.assertEqual(DerivedElement("foo", XmlHexBinary(b"\x13")), objects[-1][1])

def test_child(self):
node = PrimitiveNode(var=XmlVar(text=True, name="foo", qname="foo"), ns_map={})
var = XmlVar(text=True, name="foo", qname="foo")
node = PrimitiveNode.from_var(var, {})

with self.assertRaises(XmlContextError):
node.child("foo", {}, {}, 0)
Expand Down Expand Up @@ -809,7 +764,7 @@ def test_end(self, mock_assemble):
objects = [("q", "result")]
queue = []
var = XmlVar(text=True, name="foo", qname="foo")
queue.append(PrimitiveNode(var=var, ns_map={}))
queue.append(PrimitiveNode.from_var(var, ns_map={}))

result = parser.end(queue, objects, "author", "foobar", None)
self.assertEqual("result", result)
Expand Down
2 changes: 1 addition & 1 deletion tests/formats/dataclass/parsers/test_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def test_end(self, mock_emit_event):
objects = []
queue = []
var = XmlVar(text=True, name="foo", qname="foo", types=[bool])
queue.append(PrimitiveNode(var=var, ns_map={}))
queue.append(PrimitiveNode.from_var(var, {}))

result = self.parser.end(queue, objects, "enabled", "true", None)
self.assertTrue(result)
Expand Down
33 changes: 32 additions & 1 deletion tests/formats/dataclass/serializers/test_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def test_write_any_type_with_primitive_element(self):
self.assertIsInstance(result, Generator)
self.assertEqual(expected, list(result))

def test_write_any_type_with_generic_object(self):
def test_write_any_type_with_any_element(self):
var = XmlVar(wildcard=True, qname="a", name="a")
value = AnyElement(
qname="a",
Expand All @@ -218,6 +218,37 @@ def test_write_any_type_with_generic_object(self):
self.assertIsInstance(result, Generator)
self.assertEqual(expected, list(result))

def test_write_any_type_with_derived_element_primitive(self):
var = XmlVar(wildcard=True, qname="a", name="a")
value = DerivedElement(qname="a", value=1)
expected = [
(XmlWriterEvent.START, "a"),
(XmlWriterEvent.ATTR, QNames.XSI_TYPE, QName(str(DataType.SHORT))),
(XmlWriterEvent.DATA, 1),
(XmlWriterEvent.END, "a"),
]

result = self.serializer.write_value(value, var, "xsdata")
self.assertIsInstance(result, Generator)
self.assertEqual(expected, list(result))

def test_write_any_type_with_derived_element_dataclass(self):
var = XmlVar(wildcard=True, qname="a", name="a")
value = DerivedElement(qname="a", value=BookForm(title="def"), substituted=True)
expected = [
(XmlWriterEvent.START, "a"),
(XmlWriterEvent.ATTR, "lang", "en"),
(XmlWriterEvent.ATTR, QNames.XSI_TYPE, QName("{urn:books}BookForm")),
(XmlWriterEvent.START, "title"),
(XmlWriterEvent.DATA, "def"),
(XmlWriterEvent.END, "title"),
(XmlWriterEvent.END, "a"),
]

result = self.serializer.write_value(value, var, "xsdata")
self.assertIsInstance(result, Generator)
self.assertEqual(expected, list(result))

def test_write_xsi_type(self):
var = XmlVar(
element=True, qname="a", name="a", dataclass=True, types=[BookForm]
Expand Down
Loading