From b0a8a64faf900edc69c37dc5238bb025c9ff6473 Mon Sep 17 00:00:00 2001 From: Kira K Date: Tue, 17 Jun 2025 13:42:18 -0400 Subject: [PATCH 1/5] Tenuous first fix Need to switch machines to test on Linux --- Lib/xml/etree/ElementTree.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index 44ab5d18624e73..64639bc61cdb5b 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -528,6 +528,9 @@ class ElementTree: """ def __init__(self, element=None, file=None): # assert element is None or iselement(element) + if element is not None and not iselement(element): + raise TypeError(f"element must be etree.Element, " + f"not {type(element).__name__}") self._root = element # first node if file: self.parse(file) @@ -544,6 +547,8 @@ def _setroot(self, element): """ # assert iselement(element) + if not iselement(element): + raise TypeError self._root = element def parse(self, source, parser=None): From f0356c8c8ff58bc414f65ebb803029e0a983cc65 Mon Sep 17 00:00:00 2001 From: Kira Kaviani Date: Tue, 17 Jun 2025 14:48:11 -0400 Subject: [PATCH 2/5] Remove commented assertions --- Lib/xml/etree/ElementTree.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index 64639bc61cdb5b..9a2d638fa15c37 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -527,7 +527,6 @@ class ElementTree: """ def __init__(self, element=None, file=None): - # assert element is None or iselement(element) if element is not None and not iselement(element): raise TypeError(f"element must be etree.Element, " f"not {type(element).__name__}") @@ -546,7 +545,6 @@ def _setroot(self, element): with the given element. Use with care! """ - # assert iselement(element) if not iselement(element): raise TypeError self._root = element From 06eaff5e193aab8c8021207b9a4e83696fdfacc5 Mon Sep 17 00:00:00 2001 From: Kira Kaviani Date: Tue, 17 Jun 2025 15:24:22 -0400 Subject: [PATCH 3/5] Add tests for ElementTree constructor Add tests verifying the new type checking behavior --- Lib/test/test_xml_etree.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index 38be2cd437f200..d1161c0b4a2b44 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -247,6 +247,13 @@ def check_element(element): self.assertRegex(repr(element), r"^$") element = ET.Element("tag", key="value") + # Verify type checking for ElementTree constructor + + with self.assertRaises(TypeError): + tree = ET.ElementTree("") + with self.assertRaises(TypeError): + tree = ET.ElementTree(ET.ElementTree()) + # Make sure all standard element methods exist. def check_method(method): From 6d4d65f335f57b00110668ac9088f8dce39dab20 Mon Sep 17 00:00:00 2001 From: Kira K Date: Tue, 17 Jun 2025 16:26:14 -0400 Subject: [PATCH 4/5] Add appropriate text to an exception --- Lib/xml/etree/ElementTree.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index 9a2d638fa15c37..4dabb211d0b863 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -546,7 +546,8 @@ def _setroot(self, element): """ if not iselement(element): - raise TypeError + raise TypeError(f"element must be etree.Element, " + f"not {type(element).__name__}") self._root = element def parse(self, source, parser=None): From 84b22d4aa60eab31e28ca5a94be12aabb6a2face Mon Sep 17 00:00:00 2001 From: Kira K Date: Sat, 21 Jun 2025 23:47:56 -0400 Subject: [PATCH 5/5] Make iselement stricter + incorporate feedback Adjusts iselement() based off feedback from picnixz. Since ElementTree needs to be compatible with element-like objects, the function now checks for all attributes that are necessary to instantiate an ElementTree object and successfully write it to a file (preventing issue #135640). This commit also incorporates other feedback requesting moving the tests and adding more tests, and adds a news entry. --- Lib/test/test_xml_etree.py | 43 ++++++++++++++++--- Lib/xml/etree/ElementTree.py | 9 ++-- ...-06-22-02-16-17.gh-issue-135640.FXyFL6.rst | 3 ++ 3 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-06-22-02-16-17.gh-issue-135640.FXyFL6.rst diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index d1161c0b4a2b44..4d0acc7fc04bb6 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -218,6 +218,42 @@ class ElementTreeTest(unittest.TestCase): def serialize_check(self, elem, expected): self.assertEqual(serialize(elem), expected) + def test_constructor(self): + # Test constructor behavior. + + with self.assertRaises(TypeError): + tree = ET.ElementTree("") + with self.assertRaises(TypeError): + tree = ET.ElementTree(ET.ElementTree()) + + # Test _setroot as well, since it also sets the _root object. + + tree = ET.ElementTree() + with self.assertRaises(TypeError): + tree._setroot("") + with self.assertRaises(TypeError): + tree._setroot(ET.ElementTree()) + + # Make sure it accepts an Element-like object. + + class ElementLike: + def __init__(self): + self.tag = "tag" + self.text = None + self.tail = None + def iter(self): + pass + def items(self): + pass + def __len__(self): + pass + + element_like = ElementLike() + try: + tree = ET.ElementTree(element_like) + except Exception as err: + self.fail(err) + def test_interface(self): # Test element tree interface. @@ -247,13 +283,6 @@ def check_element(element): self.assertRegex(repr(element), r"^$") element = ET.Element("tag", key="value") - # Verify type checking for ElementTree constructor - - with self.assertRaises(TypeError): - tree = ET.ElementTree("") - with self.assertRaises(TypeError): - tree = ET.ElementTree(ET.ElementTree()) - # Make sure all standard element methods exist. def check_method(method): diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py index 4dabb211d0b863..4bef7d5710b7c0 100644 --- a/Lib/xml/etree/ElementTree.py +++ b/Lib/xml/etree/ElementTree.py @@ -120,8 +120,9 @@ class ParseError(SyntaxError): def iselement(element): """Return True if *element* appears to be an Element.""" - return hasattr(element, 'tag') - + return (hasattr(element, 'tag') and hasattr(element, 'text') and + hasattr(element, 'tail') and callable(element.iter) and + callable(element.items) and callable(element.__len__)) class Element: """An XML element. @@ -528,7 +529,7 @@ class ElementTree: """ def __init__(self, element=None, file=None): if element is not None and not iselement(element): - raise TypeError(f"element must be etree.Element, " + raise TypeError(f"element must be xml.etree.Element, " f"not {type(element).__name__}") self._root = element # first node if file: @@ -546,7 +547,7 @@ def _setroot(self, element): """ if not iselement(element): - raise TypeError(f"element must be etree.Element, " + raise TypeError(f"element must be xml.etree.Element, " f"not {type(element).__name__}") self._root = element diff --git a/Misc/NEWS.d/next/Library/2025-06-22-02-16-17.gh-issue-135640.FXyFL6.rst b/Misc/NEWS.d/next/Library/2025-06-22-02-16-17.gh-issue-135640.FXyFL6.rst new file mode 100644 index 00000000000000..49d1c5fead8843 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-06-22-02-16-17.gh-issue-135640.FXyFL6.rst @@ -0,0 +1,3 @@ +Address bug where calling :func:`xml.etree.ElementTree.ElementTree.write` on +an ElementTree object with an invalid root element would blank the file +passed to ``write`` if it already existed.