class XmlContextTests(TestCase): def setUp(self): self.ctx = XmlContext() super().setUp() @mock.patch.object(XmlContext, "find_subclass") @mock.patch.object(XmlContext, "build") def test_fetch(self, mock_build, mock_find_subclass): meta = XmlMeta( name="ItemsType", clazz=ItemsType, qname=QName("ItemsType"), source_qname=QName("ItemsType"), nillable=False, ) mock_build.return_value = meta actual = self.ctx.fetch(ItemsType, "foo") self.assertEqual(meta, actual) self.assertEqual(0, mock_find_subclass.call_count) mock_build.assert_called_once_with(ItemsType, "foo") @mock.patch.object(XmlContext, "find_subclass") @mock.patch.object(XmlContext, "build") def test_fetch_with_xsi_type_and_subclass_not_found( self, mock_build, mock_find_subclass ): meta = XmlMeta( name="ItemsType", clazz=ItemsType, qname=QName("ItemsType"), source_qname=QName("ItemsType"), nillable=False, ) mock_build.return_value = meta mock_find_subclass.return_value = None actual = self.ctx.fetch(ItemsType, xsi_type="foo") self.assertEqual(meta, actual) mock_find_subclass.assert_called_once_with(ItemsType, "foo") @mock.patch.object(XmlContext, "find_subclass") @mock.patch.object(XmlContext, "build") def test_fetch_with_xsi_type_and_subclass_found( self, mock_build, mock_find_subclass ): meta = XmlMeta( name="ItemsType", clazz=ItemsType, qname=QName("ItemsType"), source_qname=QName("ItemsType"), nillable=False, ) xsi_meta = replace(meta, name="XsiType") mock_build.side_effect = [meta, xsi_meta] mock_find_subclass.return_value = xsi_meta actual = self.ctx.fetch(ItemsType, xsi_type="foo") self.assertEqual(xsi_meta, actual) mock_find_subclass.assert_called_once_with(ItemsType, "foo") def test_find_subclass(self): a = make_dataclass("A", fields=[]) b = make_dataclass("B", fields=[], bases=(a,)) c = make_dataclass("C", fields=[], bases=(a,)) self.assertEqual(b, self.ctx.find_subclass(a, "B")) self.assertEqual(b, self.ctx.find_subclass(c, "B")) self.assertEqual(a, self.ctx.find_subclass(b, "A")) self.assertEqual(a, self.ctx.find_subclass(c, "A")) self.assertIsNone(self.ctx.find_subclass(c, "What")) def test_match_class_name(self): qname_foo = QName("qname_foo") qname_items = QName("ItemsType") qname_product = QName("http://datypic.com/prod", "product") qname_object = QName("object") qname_int = QName("int") # no meta name self.assertFalse(self.ctx.match_class_source_qname(ItemsType, qname_foo)) self.assertTrue(self.ctx.match_class_source_qname(ItemsType, qname_items)) # with meta name self.assertFalse(self.ctx.match_class_source_qname(Product, qname_items)) self.assertTrue(self.ctx.match_class_source_qname(Product, qname_product)) # not dataclass self.assertFalse(self.ctx.match_class_source_qname(object, qname_object)) self.assertFalse(self.ctx.match_class_source_qname(int, qname_int)) @mock.patch.object(XmlContext, "get_type_hints") def test_build_build_vars(self, mock_get_type_hints): var = XmlElement(name="foo", qname=QName("foo", "bar"), types=[int]) mock_get_type_hints.return_value = [var] result = self.ctx.build(ItemsType, None) expected = XmlMeta( name="ItemsType", clazz=ItemsType, qname=QName("ItemsType"), source_qname=QName("ItemsType"), nillable=False, vars=[var], ) self.assertEqual(expected, result) mock_get_type_hints.assert_called_once_with(ItemsType, None) @mock.patch.object(XmlContext, "get_type_hints", return_value=dict()) def test_build_with_meta_namespace(self, mock_get_type_hints): namespace = Product.Meta.namespace result = self.ctx.build(Product, None) self.assertEqual(QName(namespace, "product"), result.qname) self.assertEqual(QName(namespace, "product"), result.source_qname) mock_get_type_hints.assert_called_once_with(Product, namespace) @mock.patch.object(XmlContext, "get_type_hints", return_value=dict()) def test_build_with_parent_ns(self, mock_get_type_hints): result = self.ctx.build(ProductType, "http://xsdata") self.assertEqual(QName("http://xsdata", "ProductType"), str(result.qname)) mock_get_type_hints.assert_called_once_with(ProductType, "http://xsdata") @mock.patch.object(XmlContext, "get_type_hints", return_value=dict()) def test_build_with_no_meta_name_and_name_generator(self, *args): inspect = XmlContext(name_generator=lambda x: text.snake_case(x)) result = inspect.build(ItemsType) self.assertEqual(QName("items_type"), str(result.qname)) def test_build_with_no_meta_not_inherit_from_parent(self): @dataclass class Bar: class Meta: name = "bar" @dataclass class Foo(Bar): pass result = self.ctx.build(Foo) self.assertEqual("Foo", result.name) self.assertIsNone(result.qname.namespace) @mock.patch.object(XmlContext, "get_type_hints", return_value=dict()) def test_build_with_no_dataclass_raises_exception(self, *args): with self.assertRaises(XmlContextError) as cm: self.ctx.build(int) self.assertEqual(f"Object {int} is not a dataclass.", str(cm.exception)) def test_get_type_hints(self): result = self.ctx.get_type_hints(BookForm, None) self.assertIsInstance(result, Iterator) expected = [ XmlElement(name="author", qname=QName("author"), types=[str],), XmlElement(name="title", qname=QName("title"), types=[str]), XmlElement(name="genre", qname=QName("genre"), types=[str]), XmlElement(name="price", qname=QName("price"), types=[float],), XmlElement(name="pub_date", qname=QName("pub_date"), types=[str],), XmlElement(name="review", qname=QName("review"), types=[str],), XmlAttribute(name="id", qname=QName("id"), types=[str]), XmlAttribute( name="lang", qname=QName("lang"), types=[str], init=False, default="en", ), ] result = list(result) self.assertEqual(expected, result) for var in result: self.assertFalse(var.dataclass) self.assertIsNone(var.clazz) def test_get_type_hints_with_dataclass_list(self): result = list(self.ctx.get_type_hints(Books, None)) expected = XmlElement( name="book", qname=QName("book"), types=[BookForm], dataclass=True, default=list, ) self.assertTrue(expected.is_list) self.assertEqual(1, len(result)) self.assertEqual(expected, result[0]) self.assertTrue(result[0].dataclass) self.assertEqual(BookForm, result[0].clazz) def test_get_type_hints_with_wildcard_element(self): result = list(self.ctx.get_type_hints(TextType, None)) expected = XmlWildcard( name="any_element", qname=QName(None, "any_element"), types=[object], init=True, nillable=False, dataclass=False, default=list, namespaces=["##any"], ) self.assertEqual(2, len(result)) self.assertEqual(expected, result[0]) def test_get_type_hints_with_no_dataclass(self): with self.assertRaises(TypeError): list(self.ctx.get_type_hints(self.__class__, None)) def test_resolve_namespaces(self): self.assertEqual( ["foo"], self.ctx.resolve_namespaces(XmlType.ELEMENT, "foo", "bar") ) self.assertEqual([], self.ctx.resolve_namespaces(XmlType.ELEMENT, "", "bar")) self.assertEqual( ["bar"], self.ctx.resolve_namespaces(XmlType.ELEMENT, None, "bar") ) self.assertEqual( [], self.ctx.resolve_namespaces(XmlType.ATTRIBUTE, None, "bar") ) self.assertEqual( ["p"], self.ctx.resolve_namespaces(XmlType.WILDCARD, None, "p") ) self.assertEqual( ["##any"], self.ctx.resolve_namespaces(XmlType.WILDCARD, "##any", "p") ) self.assertEqual( ["##any"], self.ctx.resolve_namespaces(XmlType.WILDCARD, "##targetNamespace", ""), ) self.assertEqual( ["##any"], self.ctx.resolve_namespaces(XmlType.WILDCARD, "##targetNamespace", None), ) self.assertEqual( ["p"], self.ctx.resolve_namespaces(XmlType.WILDCARD, "##targetNamespace", "p"), ) self.assertEqual( [""], self.ctx.resolve_namespaces(XmlType.WILDCARD, "##local", "p") ) self.assertEqual( ["!p"], self.ctx.resolve_namespaces(XmlType.WILDCARD, "##other", "p") ) self.assertEqual( ["", "!p"], sorted( self.ctx.resolve_namespaces(XmlType.WILDCARD, "##other ##local", "p") ), ) self.assertEqual( ["foo", "p"], sorted( self.ctx.resolve_namespaces( XmlType.WILDCARD, "##targetNamespace foo", "p" ) ), ) def test_is_derived(self): a = make_dataclass("A", fields=[]) b = make_dataclass("B", fields=[], bases=(a,)) c = make_dataclass("C", fields=[], bases=(a,)) d = make_dataclass("D", fields=[]) self.assertTrue(self.ctx.is_derived(c(), b)) self.assertTrue(self.ctx.is_derived(b(), c)) self.assertTrue(self.ctx.is_derived(a(), b)) self.assertTrue(self.ctx.is_derived(a(), c)) self.assertTrue(self.ctx.is_derived(a(), a)) self.assertFalse(self.ctx.is_derived(a(), d))
class XmlContextTests(TestCase): def setUp(self): self.ctx = XmlContext() super().setUp() @mock.patch.object(XmlContext, "find_subclass") @mock.patch.object(XmlContext, "build") def test_fetch(self, mock_build, mock_find_subclass): meta = XmlMeta( clazz=ItemsType, qname="ItemsType", source_qname="ItemsType", nillable=False, ) mock_build.return_value = meta actual = self.ctx.fetch(ItemsType, "foo") self.assertEqual(meta, actual) self.assertEqual(0, mock_find_subclass.call_count) mock_build.assert_called_once_with(ItemsType, "foo") @mock.patch.object(XmlContext, "find_subclass") @mock.patch.object(XmlContext, "build") def test_fetch_with_xsi_type_and_subclass_not_found( self, mock_build, mock_find_subclass): meta = XmlMeta( clazz=ItemsType, qname="ItemsType", source_qname="ItemsType", nillable=False, ) mock_build.return_value = meta mock_find_subclass.return_value = None actual = self.ctx.fetch(ItemsType, xsi_type="foo") self.assertEqual(meta, actual) mock_find_subclass.assert_called_once_with(ItemsType, "foo") @mock.patch.object(XmlContext, "find_subclass") @mock.patch.object(XmlContext, "build") def test_fetch_with_xsi_type_and_subclass_found(self, mock_build, mock_find_subclass): meta = XmlMeta( clazz=ItemsType, qname="ItemsType", source_qname="ItemsType", nillable=False, ) xsi_meta = replace(meta, qname="XsiType") mock_build.side_effect = [meta, xsi_meta] mock_find_subclass.return_value = xsi_meta actual = self.ctx.fetch(ItemsType, xsi_type="foo") self.assertEqual(xsi_meta, actual) mock_find_subclass.assert_called_once_with(ItemsType, "foo") def test_find(self): self.assertIsNone(self.ctx.find_type(str(DataType.FLOAT))) self.assertEqual(BookForm, self.ctx.find_type("{urn:books}BookForm")) self.ctx.xsi_cache["{urn:books}BookForm"].append(BooksForm) self.assertEqual(BooksForm, self.ctx.find_type("{urn:books}BookForm")) def test_find_type_by_fields(self): field_names = {f.name for f in fields(BookForm)} self.assertEqual(BookForm, self.ctx.find_type_by_fields(field_names)) self.assertIsNone( self.ctx.find_type_by_fields({"please", "dont", "exist"})) def test_find_subclass(self): a = make_dataclass("A", fields=[]) b = make_dataclass("B", fields=[], bases=(a, )) c = make_dataclass("C", fields=[], bases=(a, )) other = make_dataclass("Other", fields=[]) self.assertEqual(b, self.ctx.find_subclass(a, "B")) self.assertEqual(b, self.ctx.find_subclass(c, "B")) self.assertEqual(a, self.ctx.find_subclass(b, "A")) self.assertEqual(a, self.ctx.find_subclass(c, "A")) self.assertIsNone(self.ctx.find_subclass(c, "Unknown")) self.assertIsNone(self.ctx.find_subclass(c, "Other")) @mock.patch.object(XmlContext, "get_type_hints") def test_build_build_vars(self, mock_get_type_hints): var = XmlVar(element=True, name="foo", qname="{foo}bar", types=[int]) mock_get_type_hints.return_value = [var] result = self.ctx.build(ItemsType, None) expected = XmlMeta( clazz=ItemsType, qname="ItemsType", source_qname="ItemsType", nillable=False, vars=[var], ) self.assertEqual(expected, result) mock_get_type_hints.assert_called_once_with(ItemsType, None) @mock.patch.object(XmlContext, "get_type_hints", return_value={}) def test_build_with_meta_namespace(self, mock_get_type_hints): namespace = Product.Meta.namespace result = self.ctx.build(Product, None) self.assertEqual(build_qname(namespace, "product"), result.qname) self.assertEqual(build_qname(namespace, "product"), result.source_qname) mock_get_type_hints.assert_called_once_with(Product, namespace) @mock.patch.object(XmlContext, "get_type_hints", return_value={}) def test_build_with_parent_ns(self, mock_get_type_hints): result = self.ctx.build(ProductType, "http://xsdata") self.assertEqual(build_qname("http://xsdata", "ProductType"), str(result.qname)) mock_get_type_hints.assert_called_once_with(ProductType, "http://xsdata") @mock.patch.object(XmlContext, "get_type_hints", return_value={}) def test_build_with_no_meta_name_and_name_generator(self, *args): inspect = XmlContext(element_name=lambda x: text.snake_case(x)) result = inspect.build(ItemsType) self.assertEqual("items_type", result.qname) def test_build_with_no_meta_not_inherit_from_parent(self): @dataclass class Bar: class Meta: name = "bar" @dataclass class Foo(Bar): pass @dataclass class Thug(Bar): class Meta: name = "thug" result = self.ctx.build(Foo) self.assertEqual("Foo", result.qname) result = self.ctx.build(Thug) self.assertEqual("thug", result.qname) @mock.patch.object(XmlContext, "get_type_hints", return_value={}) def test_build_with_no_dataclass_raises_exception(self, *args): with self.assertRaises(XmlContextError) as cm: self.ctx.build(int) self.assertEqual(f"Object {int} is not a dataclass.", str(cm.exception)) def test_get_type_hints(self): result = self.ctx.get_type_hints(BookForm, None) self.assertIsInstance(result, Iterator) expected = [ XmlVar(element=True, name="author", qname="author", types=[str]), XmlVar(element=True, name="title", qname="title", types=[str]), XmlVar(element=True, name="genre", qname="genre", types=[str]), XmlVar(element=True, name="price", qname="price", types=[float]), XmlVar(element=True, name="pub_date", qname="pub_date", types=[str]), XmlVar(element=True, name="review", qname="review", types=[str]), XmlVar(attribute=True, name="id", qname="id", types=[str]), XmlVar( attribute=True, name="lang", qname="lang", types=[str], init=False, default="en", ), ] result = list(result) self.assertEqual(expected, result) for var in result: self.assertFalse(var.dataclass) self.assertIsNone(var.clazz) def test_get_type_hints_with_union_types(self): @dataclass class Example: bool: bool int: int union: Union[int, bool] result = list(self.ctx.get_type_hints(Example, None)) expected = [ XmlVar(element=True, name="bool", qname="bool", types=[bool]), XmlVar(element=True, name="int", qname="int", types=[int]), XmlVar(element=True, name="union", qname="union", types=[bool, int]), ] if sys.version_info < (3, 7): expected[2].types.remove(bool) self.assertEqual(expected, result) def test_get_type_hints_with_dataclass_list(self): result = list(self.ctx.get_type_hints(Books, None)) expected = XmlVar( element=True, name="book", qname="book", types=[BookForm], dataclass=True, default=list, list_element=True, ) self.assertTrue(expected.list_element) self.assertEqual(1, len(result)) self.assertEqual(expected, result[0]) self.assertTrue(result[0].dataclass) self.assertEqual(BookForm, result[0].clazz) def test_get_type_hints_with_wildcard_element(self): result = list(self.ctx.get_type_hints(Umbrella, None)) expected = XmlVar( wildcard=True, name="any_element", qname="any_element", types=[object], default=None, namespaces=["##any"], ) self.assertEqual(1, len(result)) self.assertEqual(expected, result[0]) def test_get_type_hints_with_undefined_types(self): @dataclass class Currency: id: int = field(metadata=dict(type="Attribute", name="ID")) iso_code: str = field(metadata=dict(name="CharCode")) nominal: int = field(metadata=dict(name="Nominal")) @dataclass class Currencies: name: str = field(metadata=dict(type="Attribute")) values: List[Currency] = field(default_factory=list) expected = [ XmlVar(attribute=True, name="id", qname="ID", types=[int]), XmlVar(element=True, name="iso_code", qname="CharCode", types=[str]), XmlVar(element=True, name="nominal", qname="Nominal", types=[int]), ] self.assertEqual(expected, list(self.ctx.get_type_hints(Currency, None))) expected = [ XmlVar(attribute=True, name="name", qname="name", types=[str]), XmlVar( element=True, name="values", qname="values", dataclass=True, list_element=True, default=list, types=[Currency], ), ] self.assertEqual(expected, list(self.ctx.get_type_hints(Currencies, None))) def test_get_type_hints_with_choices(self): actual = self.ctx.get_type_hints(Node, "bar") self.assertIsInstance(actual, Generator) expected = XmlVar( elements=True, name="compound", qname="compound", list_element=True, any_type=True, default=list, choices=[ XmlVar( element=True, name="compound", qname="{foo}node", dataclass=True, types=[Node], namespaces=["foo"], derived=False, ), XmlVar( element=True, name="compound", qname="{bar}x", tokens=True, types=[str], namespaces=["bar"], derived=False, default=return_true, ), XmlVar( element=True, name="compound", qname="{bar}y", nillable=True, types=[int], namespaces=["bar"], derived=False, ), XmlVar( element=True, name="compound", qname="{bar}z", nillable=False, types=[int], namespaces=["bar"], derived=True, ), XmlVar( element=True, name="compound", qname="{bar}o", nillable=False, types=[object], namespaces=["bar"], derived=True, any_type=True, ), XmlVar( element=True, name="compound", qname="{bar}p", types=[float], namespaces=["bar"], default=1.1, ), XmlVar( wildcard=True, name="compound", qname="{http://www.w3.org/1999/xhtml}any", types=[object], namespaces=["http://www.w3.org/1999/xhtml"], derived=True, any_type=False, ), ], types=[object], ) self.assertEqual(expected, list(actual)[0]) def test_get_type_hints_with_no_dataclass(self): with self.assertRaises(TypeError): list(self.ctx.get_type_hints(self.__class__, None)) def test_resolve_namespaces(self): self.assertEqual(["foo"], self.ctx.resolve_namespaces(XmlType.ELEMENT, "foo", "bar")) self.assertEqual([], self.ctx.resolve_namespaces(XmlType.ELEMENT, "", "bar")) self.assertEqual(["bar"], self.ctx.resolve_namespaces(XmlType.ELEMENT, None, "bar")) self.assertEqual([], self.ctx.resolve_namespaces(XmlType.ATTRIBUTE, None, "bar")) self.assertEqual(["p"], self.ctx.resolve_namespaces(XmlType.WILDCARD, None, "p")) self.assertEqual(["##any"], self.ctx.resolve_namespaces(XmlType.WILDCARD, "##any", "p")) self.assertEqual( ["##any"], self.ctx.resolve_namespaces(XmlType.WILDCARD, "##targetNamespace", ""), ) self.assertEqual( ["##any"], self.ctx.resolve_namespaces(XmlType.WILDCARD, "##targetNamespace", None), ) self.assertEqual( ["p"], self.ctx.resolve_namespaces(XmlType.WILDCARD, "##targetNamespace", "p"), ) self.assertEqual([""], self.ctx.resolve_namespaces(XmlType.WILDCARD, "##local", "p")) self.assertEqual(["!p"], self.ctx.resolve_namespaces(XmlType.WILDCARD, "##other", "p")) self.assertEqual( ["", "!p"], sorted( self.ctx.resolve_namespaces(XmlType.WILDCARD, "##other ##local", "p")), ) self.assertEqual( ["foo", "p"], sorted( self.ctx.resolve_namespaces(XmlType.WILDCARD, "##targetNamespace foo", "p")), ) def test_is_derived(self): a = make_dataclass("A", fields=[]) b = make_dataclass("B", fields=[], bases=(a, )) c = make_dataclass("C", fields=[], bases=(a, )) d = make_dataclass("D", fields=[]) self.assertTrue(self.ctx.is_derived(c(), b)) self.assertTrue(self.ctx.is_derived(b(), c)) self.assertTrue(self.ctx.is_derived(a(), b)) self.assertTrue(self.ctx.is_derived(a(), c)) self.assertTrue(self.ctx.is_derived(a(), a)) self.assertFalse(self.ctx.is_derived(a(), d)) self.assertFalse(self.ctx.is_derived(None, d)) def test_is_element_list(self): @dataclass class Fixture: list_int: List[int] list_list_int: List[List[int]] type_hints = get_type_hints(Fixture) self.assertTrue(self.ctx.is_element_list(type_hints["list_int"], False)) self.assertFalse(self.ctx.is_element_list(type_hints["list_int"], True)) self.assertTrue( self.ctx.is_element_list(type_hints["list_list_int"], False)) self.assertTrue( self.ctx.is_element_list(type_hints["list_list_int"], True)) def test_default_xml_type(self): cls = make_dataclass("a", [("x", int)]) self.assertEqual(XmlType.TEXT, self.ctx.default_xml_type(cls)) cls = make_dataclass("b", [("x", int), ("y", int)]) self.assertEqual(XmlType.ELEMENT, self.ctx.default_xml_type(cls)) cls = make_dataclass("c", [("x", int), ("y", int, field(metadata=dict(type="Text")))]) self.assertEqual(XmlType.ELEMENT, self.ctx.default_xml_type(cls)) cls = make_dataclass( "d", [("x", int), ("y", int, field(metadata=dict(type="Element")))]) self.assertEqual(XmlType.TEXT, self.ctx.default_xml_type(cls)) with self.assertRaises(XmlContextError) as cm: cls = make_dataclass( "e", [ ("x", int, field(metadata=dict(type="Text"))), ("y", int, field(metadata=dict(type="Text"))), ], ) self.ctx.default_xml_type(cls) self.assertEqual("Dataclass `e` includes more than one text node!", str(cm.exception))