From 25c79eda2bef2dd7b6e720ffe72e54ce1c8e31b0 Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Mon, 10 Jul 2023 17:31:01 -0400 Subject: [PATCH 1/3] feat: to_etree_element --- src/xsdata_pydantic_basemodel/compat.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/xsdata_pydantic_basemodel/compat.py b/src/xsdata_pydantic_basemodel/compat.py index b087ea17..b176e7ef 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: From 37c67ac38efd05525edd82e8b0515b119712c103 Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Wed, 12 Jul 2023 09:56:32 -0400 Subject: [PATCH 2/3] add test --- tests/conftest.py | 13 +++++++++++++ tests/test_model.py | 16 ++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 8f4d6d94..5854e66d 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 7801573b..f26a28d6 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -225,3 +225,19 @@ def test_numpy_pixel_types() -> None: for m in model.PixelType: 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) From 4c5fc2fe0623f442372be8e1d76541e77e8ba15c Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Wed, 12 Jul 2023 10:03:42 -0400 Subject: [PATCH 3/3] quote the type hint --- src/xsdata_pydantic_basemodel/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xsdata_pydantic_basemodel/compat.py b/src/xsdata_pydantic_basemodel/compat.py index b176e7ef..bc823625 100644 --- a/src/xsdata_pydantic_basemodel/compat.py +++ b/src/xsdata_pydantic_basemodel/compat.py @@ -49,7 +49,7 @@ class AnyElement(BaseModel): class Config: arbitrary_types_allowed = True - def to_etree_element(self) -> ET._Element: + def to_etree_element(self) -> "ET._Element": elem = ET.Element(self.qname or "", self.attributes) elem.text = self.text elem.tail = self.tail