Skip to content

Commit

Permalink
feat: Add class and field info in parsing validation warnings (tefra#…
Browse files Browse the repository at this point in the history
  • Loading branch information
tefra authored and skinkie committed Jun 18, 2024
1 parent 47fe6b4 commit c701e58
Show file tree
Hide file tree
Showing 17 changed files with 192 additions and 103 deletions.
2 changes: 1 addition & 1 deletion tests/formats/dataclass/parsers/nodes/test_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,5 +538,5 @@ def test_build_node_with_primitive_var(self):

self.assertIsInstance(actual, PrimitiveNode)
self.assertEqual(ns_map, actual.ns_map)
self.assertEqual(self.meta, actual.meta)
self.assertEqual(var, actual.var)
self.assertEqual(self.node.meta.mixed_content, actual.mixed)
36 changes: 19 additions & 17 deletions tests/formats/dataclass/parsers/nodes/test_primitive.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,41 @@
from unittest import TestCase, mock

from tests.fixtures.artists import Artist
from xsdata.exceptions import XmlContextError
from xsdata.formats.dataclass.models.elements import XmlType
from xsdata.formats.dataclass.parsers.nodes import PrimitiveNode
from xsdata.formats.dataclass.parsers.utils import ParserUtils
from xsdata.utils.testing import XmlVarFactory
from xsdata.utils.testing import XmlMetaFactory, XmlVarFactory


class PrimitiveNodeTests(TestCase):
@mock.patch.object(ParserUtils, "parse_value")
def test_bind(self, mock_parse_value):
mock_parse_value.return_value = 13
def setUp(self):
super().setUp()
self.meta = XmlMetaFactory.create(clazz=Artist)

@mock.patch.object(ParserUtils, "parse_var")
def test_bind(self, mock_parse_var):
mock_parse_var.return_value = 13
var = XmlVarFactory.create(
xml_type=XmlType.TEXT, name="foo", types=(int,), format="Nope"
)
ns_map = {"foo": "bar"}
node = PrimitiveNode(var, ns_map, False)
node = PrimitiveNode(self.meta, var, ns_map)
objects = []

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

mock_parse_value.assert_called_once_with(
value="13",
types=var.types,
default=var.default,
ns_map=ns_map,
tokens_factory=var.tokens_factory,
format=var.format,
mock_parse_var.assert_called_once_with(
meta=self.meta, var=var, value="13", ns_map=ns_map
)

def test_bind_nillable_content(self):
var = XmlVarFactory.create(
xml_type=XmlType.TEXT, name="foo", types=(str,), nillable=False
)
ns_map = {"foo": "bar"}
node = PrimitiveNode(var, ns_map, False)
node = PrimitiveNode(self.meta, var, ns_map)
objects = []

self.assertTrue(node.bind("foo", None, None, objects))
Expand All @@ -53,7 +53,7 @@ def test_bind_nillable_bytes_content(self):
nillable=False,
)
ns_map = {"foo": "bar"}
node = PrimitiveNode(var, ns_map, False)
node = PrimitiveNode(self.meta, var, ns_map)
objects = []

self.assertTrue(node.bind("foo", None, None, objects))
Expand All @@ -64,25 +64,27 @@ def test_bind_nillable_bytes_content(self):
self.assertIsNone(objects[-1][1])

def test_bind_mixed_with_tail_content(self):
self.meta.mixed_content = True
var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo", types=(int,))
node = PrimitiveNode(var, {}, True)
node = PrimitiveNode(self.meta, var, {})
objects = []

self.assertTrue(node.bind("foo", "13", "tail", objects))
self.assertEqual((None, "tail"), objects[-1])
self.assertEqual(13, objects[-2][1])

def test_bind_mixed_without_tail_content(self):
self.meta.mixed_content = True
var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo", types=(int,))
node = PrimitiveNode(var, {}, True)
node = PrimitiveNode(self.meta, var, {})
objects = []

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

def test_child(self):
var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo")
node = PrimitiveNode(var, {}, False)
node = PrimitiveNode(self.meta, var, {})

with self.assertRaises(XmlContextError):
node.child("foo", {}, {}, 0)
26 changes: 17 additions & 9 deletions tests/formats/dataclass/parsers/nodes/test_standard.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,46 @@
from unittest import TestCase

from tests.fixtures.artists import Artist
from xsdata.exceptions import XmlContextError
from xsdata.formats.dataclass.models.generics import DerivedElement
from xsdata.formats.dataclass.parsers.nodes import StandardNode
from xsdata.models.enums import DataType
from xsdata.utils.testing import XmlMetaFactory, XmlVarFactory


class StandardNodeTests(TestCase):
def setUp(self):
super().setUp()
self.meta = XmlMetaFactory.create(clazz=Artist)
self.var = XmlVarFactory.create()

def test_bind_simple(self):
var = DataType.INT
node = StandardNode(var, {}, False, False)
datatype = DataType.INT
node = StandardNode(self.meta, self.var, datatype, {}, False, False)
objects = []

self.assertTrue(node.bind("a", "13", None, objects))
self.assertEqual(("a", 13), objects[-1])

def test_bind_derived(self):
var = DataType.INT
node = StandardNode(var, {}, False, DerivedElement)
datatype = DataType.INT
node = StandardNode(self.meta, self.var, datatype, {}, False, DerivedElement)
objects = []

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

def test_bind_wrapper_type(self):
var = DataType.HEX_BINARY
node = StandardNode(var, {}, False, DerivedElement)
datatype = DataType.HEX_BINARY
node = StandardNode(self.meta, self.var, datatype, {}, False, DerivedElement)
objects = []

self.assertTrue(node.bind("a", "13", None, objects))
self.assertEqual(("a", DerivedElement(qname="a", value=b"\x13")), objects[-1])

def test_bind_nillable(self):
var = DataType.STRING
node = StandardNode(var, {}, True, None)
datatype = DataType.STRING
node = StandardNode(self.meta, self.var, datatype, {}, True, None)
objects = []

self.assertTrue(node.bind("a", None, None, objects))
Expand All @@ -44,7 +51,8 @@ def test_bind_nillable(self):
self.assertEqual(("a", ""), objects[-1])

def test_child(self):
node = StandardNode(DataType.STRING, {}, False, False)
datatype = DataType.STRING
node = StandardNode(self.meta, self.var, datatype, {}, False, False)

with self.assertRaises(XmlContextError):
node.child("foo", {}, {}, 0)
17 changes: 12 additions & 5 deletions tests/formats/dataclass/parsers/nodes/test_union.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
from typing import Union
from unittest import TestCase

from tests.fixtures.artists import Artist
from tests.fixtures.models import UnionType
from xsdata.exceptions import ParserError
from xsdata.formats.dataclass.context import XmlContext
from xsdata.formats.dataclass.models.elements import XmlType
from xsdata.formats.dataclass.parsers.config import ParserConfig
from xsdata.formats.dataclass.parsers.nodes import UnionNode
from xsdata.models.mixins import attribute
from xsdata.utils.testing import XmlVarFactory
from xsdata.utils.testing import XmlMetaFactory, XmlVarFactory


class UnionNodeTests(TestCase):
Expand All @@ -22,10 +23,12 @@ def setUp(self) -> None:
def test_child(self):
attrs = {"id": "1"}
ns_map = {"ns0": "xsdata"}
meta = XmlMetaFactory.create(clazz=Artist)
var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo")
node = UnionNode(
position=0,
meta=meta,
var=var,
position=0,
config=self.config,
context=self.context,
attrs={},
Expand All @@ -38,10 +41,12 @@ def test_child(self):
self.assertIsNot(attrs, node.events[0][2])

def test_bind_appends_end_event_when_level_not_zero(self):
meta = XmlMetaFactory.create(clazz=Artist)
var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo")
node = UnionNode(
position=0,
meta=meta,
var=var,
position=0,
config=self.config,
context=self.context,
attrs={},
Expand All @@ -67,8 +72,9 @@ def test_bind_returns_best_matching_object(self):
attrs = {"a": "1", "b": 2}
ns_map = {}
node = UnionNode(
position=0,
meta=meta,
var=var,
position=0,
config=self.config,
context=self.context,
attrs=attrs,
Expand Down Expand Up @@ -102,8 +108,9 @@ def test_bind_raises_parser_error_on_failure(self):
var = next(meta.find_children("element"))

node = UnionNode(
position=0,
meta=meta,
var=var,
position=0,
config=self.config,
context=self.context,
attrs={},
Expand Down
2 changes: 1 addition & 1 deletion tests/formats/dataclass/parsers/test_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def test_decode_with_fail_on_converter_warnings(self):
self.decoder.decode(json_str, TypeA)

self.assertEqual(
"Failed to convert value `foo` to one of (<class 'int'>,)",
"Failed to convert value for `TypeA.x`\n `foo` is not a valid `int`",
str(cm.exception),
)

Expand Down
2 changes: 1 addition & 1 deletion tests/formats/dataclass/parsers/test_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def test_parse_with_fail_on_converter_warnings(self):
parser.from_string(xml, TypeA)

self.assertEqual(
"Failed to convert value `foo` to one of (<class 'int'>,)",
"Failed to convert value for `TypeA.x`\n `foo` is not a valid `int`",
str(cm.exception),
)

Expand Down
16 changes: 16 additions & 0 deletions tests/formats/dataclass/parsers/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import warnings
from unittest import mock

from tests.fixtures.models import TypeA
Expand Down Expand Up @@ -114,3 +115,18 @@ def test_validate_fixed_value(self):

var = XmlVarFactory.create("fixed", default=lambda: float("nan"))
ParserUtils.validate_fixed_value(meta, var, float("nan"))

def test_parse_var_with_warnings(self):
meta = XmlMetaFactory.create(clazz=TypeA, qname="foo")
var = XmlVarFactory.create("fixed", default="a")

with warnings.catch_warnings(record=True) as w:
result = ParserUtils.parse_var(meta, var, "a", types=[int, float])

expected = (
"Failed to convert value for `TypeA.fixed`\n"
" `a` is not a valid `int | float`"
)
self.assertEqual("a", result)

self.assertEqual(expected, str(w[-1].message))
6 changes: 4 additions & 2 deletions tests/formats/dataclass/parsers/test_xml.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from unittest import mock

from tests.fixtures.artists import Artist
from tests.fixtures.books import Books
from xsdata.formats.dataclass.models.elements import XmlType
from xsdata.formats.dataclass.parsers.nodes import PrimitiveNode, SkipNode
from xsdata.formats.dataclass.parsers.xml import UserXmlParser
from xsdata.models.enums import EventType
from xsdata.utils.testing import FactoryTestCase, XmlVarFactory
from xsdata.utils.testing import FactoryTestCase, XmlMetaFactory, XmlVarFactory


class UserXmlParserTests(FactoryTestCase):
Expand All @@ -30,8 +31,9 @@ def test_start(self, mock_emit_event):
def test_end(self, mock_emit_event):
objects = []
queue = []
meta = XmlMetaFactory.create(clazz=Artist)
var = XmlVarFactory.create(xml_type=XmlType.TEXT, name="foo", types=(bool,))
queue.append(PrimitiveNode(var, {}, False))
queue.append(PrimitiveNode(meta, var, {}))

result = self.parser.end(queue, objects, "enabled", "true", None)
self.assertTrue(result)
Expand Down
20 changes: 8 additions & 12 deletions tests/formats/test_converter.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import sys
import warnings
from datetime import date, datetime, time
from decimal import Decimal
from enum import Enum
Expand All @@ -18,12 +17,10 @@

class ConverterFactoryTests(TestCase):
def test_deserialize(self):
with warnings.catch_warnings(record=True) as w:
self.assertEqual("a", converter.deserialize("a", [int]))
with self.assertRaises(ConverterError) as cm:
converter.deserialize("a", [int])

self.assertEqual(
"Failed to convert value `a` to one of [<class 'int'>]", str(w[-1].message)
)
self.assertEqual("`a` is not a valid `int`", str(cm.exception))

self.assertFalse(converter.deserialize("false", [int, bool]))
self.assertEqual(1, converter.deserialize("1", [int, bool]))
Expand Down Expand Up @@ -73,13 +70,12 @@ def test_unknown_converter(self):
class A:
pass

class B(A):
pass

with warnings.catch_warnings(record=True) as w:
converter.serialize(B())
with self.assertRaises(ConverterError) as cm:
converter.serialize(A())

self.assertEqual(f"No converter registered for `{B}`", str(w[-1].message))
self.assertEqual(
f"No converter registered for `{A.__qualname__}`", str(cm.exception)
)

def test_register_converter(self):
class MinusOneInt(int):
Expand Down
Loading

0 comments on commit c701e58

Please sign in to comment.