diff --git a/src/xsdata_pydantic_basemodel/compat.py b/src/xsdata_pydantic_basemodel/compat.py index b087ea17..bc823625 100644 --- a/src/xsdata_pydantic_basemodel/compat.py +++ b/src/xsdata_pydantic_basemodel/compat.py @@ -1,3 +1,8 @@ +try: + from lxml import etree as ET +except ImportError: + import xml.etree.ElementTree as ET # type: ignore + from dataclasses import MISSING, field from typing import ( Any, @@ -11,7 +16,6 @@ TypeVar, cast, ) -from xml.etree.ElementTree import QName from pydantic import BaseModel, validators from pydantic.fields import Field, ModelField, Undefined @@ -45,6 +49,14 @@ class AnyElement(BaseModel): class Config: arbitrary_types_allowed = True + def to_etree_element(self) -> "ET._Element": + elem = ET.Element(self.qname or "", self.attributes) + elem.text = self.text + elem.tail = self.tail + for child in self.children: + elem.append(child.to_etree_element()) + return elem + class DerivedElement(BaseModel, Generic[T]): """Generic model wrapper for type substituted elements. @@ -136,7 +148,7 @@ def validator(value: Any) -> Any: (XmlTime, make_validators(XmlTime, XmlTime.from_string)), (XmlDuration, make_validators(XmlDuration, XmlDuration)), (XmlPeriod, make_validators(XmlPeriod, XmlPeriod)), - (QName, make_validators(QName, QName)), + (ET.QName, make_validators(ET.QName, ET.QName)), ] ) else: diff --git a/tests/conftest.py b/tests/conftest.py index eeda0a74..5818faac 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,6 +16,14 @@ ALL_XML = set(DATA.glob("*.ome.xml")) INVALID = {DATA / "invalid_xml_annotation.ome.xml", DATA / "bad.ome.xml"} OLD_SCHEMA = {DATA / "seq0000xy01c1.ome.xml", DATA / "2008_instrument.ome.xml"} +WITH_XML_ANNOTATIONS = { + DATA / "ome_ns.ome.xml", + DATA / "OverViewScan.ome.xml", + DATA / "spim.ome.xml", + DATA / "xmlannotation-svg.ome.xml", + DATA / "xmlannotation-multi-value.ome.xml", + DATA / "xmlannotation-body-space.ome.xml", +} def _true_stem(p: Path) -> str: @@ -39,6 +47,11 @@ def invalid_xml(request: pytest.FixtureRequest) -> Path: return request.param +@pytest.fixture(params=sorted(WITH_XML_ANNOTATIONS), ids=_true_stem) +def with_xml_annotations(request: pytest.FixtureRequest) -> Path: + return request.param + + @pytest.fixture def single_xml() -> Path: return DATA / "example.ome.xml" diff --git a/tests/test_model.py b/tests/test_model.py index 9febf81d..fe014bbc 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -227,6 +227,22 @@ def test_numpy_pixel_types() -> None: numpy.dtype(m.numpy_dtype) +def test_xml_annotations_to_etree(with_xml_annotations: Path) -> None: + from xsdata_pydantic_basemodel.compat import AnyElement + + try: + from lxml.etree import _Element as Elem + except ImportError: + from xml.etree.ElementTree import Element as Elem # type: ignore + + ome = from_xml(with_xml_annotations) + for anno in ome.structured_annotations: + if isinstance(anno, model.XMLAnnotation): + for elem in anno.value.any_elements: + assert isinstance(elem, AnyElement) + assert isinstance(elem.to_etree_element(), Elem) + + def test_update_unset(pixels: model.Pixels) -> None: """Make sure objects appended to mutable sequences are included in the xml.""" ome = model.OME()