From 26233756567beafb1113f5a2f6bee2789c81c47d Mon Sep 17 00:00:00 2001 From: Dmitry Pershin Date: Sat, 20 Jan 2024 16:46:50 +0500 Subject: [PATCH 1/2] snapshot sourceline bug fixed. sourceline was not being copied to snapshot which led to incorrect error message. --- pydantic_xml/element/element.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pydantic_xml/element/element.py b/pydantic_xml/element/element.py index 4a99e30..d46113d 100644 --- a/pydantic_xml/element/element.py +++ b/pydantic_xml/element/element.py @@ -305,6 +305,7 @@ def create_snapshot(self) -> 'XmlElement[NativeElement]': attributes=dict(self._state.attrib) if self._state.attrib is not None else None, elements=[element.create_snapshot() for element in self._state.elements], nsmap=dict(self._nsmap) if self._nsmap is not None else None, + sourceline=self._sourceline, ) element._state.next_element_idx = self._state.next_element_idx From 9fce737b537c7a8f61041e8ef8a63fdd88b8ddb9 Mon Sep 17 00:00:00 2001 From: Dmitry Pershin Date: Sat, 20 Jan 2024 16:54:30 +0500 Subject: [PATCH 2/2] Union collection deserialization bug fixed. Deserialization proces gets stuck for List[Union] typed field because element index is not incremented if a validation error raised. --- pydantic_xml/element/element.py | 9 ++++ pydantic_xml/serializers/factories/union.py | 1 + tests/test_errors.py | 47 +++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/pydantic_xml/element/element.py b/pydantic_xml/element/element.py index d46113d..6e82093 100644 --- a/pydantic_xml/element/element.py +++ b/pydantic_xml/element/element.py @@ -116,6 +116,12 @@ def apply_snapshot(self, snapshot: 'XmlElement[Any]') -> None: Applies a snapshot to the current element. """ + @abc.abstractmethod + def step_forward(self) -> None: + """ + Increment the current element index. + """ + @abc.abstractmethod def to_native(self) -> Any: """ @@ -320,6 +326,9 @@ def apply_snapshot(self, snapshot: 'XmlElement[NativeElement]') -> None: self._state.elements = snapshot._state.elements self._state.next_element_idx = snapshot._state.next_element_idx + def step_forward(self) -> None: + self._state.next_element_idx += 1 + def is_empty(self) -> bool: if not self._state.text and not self._state.tail and not self._state.attrib and len(self._state.elements) == 0: return True diff --git a/pydantic_xml/serializers/factories/union.py b/pydantic_xml/serializers/factories/union.py index d1e4136..58e94e0 100644 --- a/pydantic_xml/serializers/factories/union.py +++ b/pydantic_xml/serializers/factories/union.py @@ -117,6 +117,7 @@ def deserialize( last_error = e if last_error is not None: + element.step_forward() raise last_error return result diff --git a/tests/test_errors.py b/tests/test_errors.py index a72fdc3..d23dd82 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -170,3 +170,50 @@ class TestModel(BaseXmlModel, tag='model'): }, }, ] + + +def test_models_union_errors(): + class TestSubModel1(BaseXmlModel, tag='submodel1'): + data: int + + class TestSubModel2(BaseXmlModel, tag='submodel2'): + data: float + + class TestModel(BaseXmlModel, tag='model'): + submodel: List[Union[TestSubModel1, TestSubModel2]] + + xml = ''' + + a + b + + ''' + + with pytest.raises(pd.ValidationError) as exc: + TestModel.from_xml(xml) + + err = exc.value + assert err.title == 'TestModel' + assert err.error_count() == 2 + assert err.errors() == [ + { + 'input': 'a', + 'loc': ('submodel', 0, 'data'), + 'msg': f'[line {fmt_sourceline(3)}]: Input should be a valid number, unable to parse string as a number', + 'type': 'float_parsing', + 'ctx': { + 'orig': 'Input should be a valid number, unable to parse string as a number', + 'sourceline': fmt_sourceline(3), + }, + }, + { + 'input': 'b', + 'loc': ('submodel', 1, 'data'), + 'msg': f'[line {fmt_sourceline(4)}]: Input should be a valid integer, unable to parse string as an integer', + 'type': 'int_parsing', + 'ctx': { + 'orig': 'Input should be a valid integer, unable to parse string as an integer', + 'sourceline': fmt_sourceline(4), + }, + }, + ]