From 7c6f19ff97750a411907b47b9f10a6448d6a4353 Mon Sep 17 00:00:00 2001 From: Hrithik Saini <49943287+hrithiksaini99@users.noreply.github.com> Date: Sat, 7 Nov 2026 13:57:20 +0530 Subject: [PATCH 1/2] [xml.etree] Narrow the ET.Comment fix: return Element[Any] from find/findall/iter mypy_primer showed widening the _Tag default to str | _ElementCallable broke downstream (colour: tag is no longer str where str is expected). Instead, traversal APIs return Element[Any], so el.tag is Any and identity comparisons against ET.Comment/PI don't error, while Element('x').tag stays str. Simpler and no downstream fallout. --- stdlib/@tests/test_cases/check_xml.py | 16 +++++++++++++ stdlib/xml/etree/ElementTree.pyi | 34 +++++++-------------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/stdlib/@tests/test_cases/check_xml.py b/stdlib/@tests/test_cases/check_xml.py index 31a4fa243eaa..d0df0f6e830c 100644 --- a/stdlib/@tests/test_cases/check_xml.py +++ b/stdlib/@tests/test_cases/check_xml.py @@ -2,6 +2,7 @@ from typing_extensions import assert_type from xml.dom.minidom import Document +from xml.etree.ElementTree import Comment, ElementTree, ProcessingInstruction document = Document() @@ -29,3 +30,18 @@ assert_type(document.toprettyxml(" ", "\r\n", "UTF8"), bytes) assert_type(document.toprettyxml(" ", "\r\n", "UTF8", True), bytes) assert_type(document.toprettyxml(" ", "\r\n", standalone=True), str) + + +# Regression for #14671: ET.Comment/ProcessingInstruction are valid +# element tags at runtime (insert_comments=True), so a plain Element's +# tag must be comparable with them. +def check_comment_tag(doc: ElementTree) -> None: + for el in doc.findall("*"): + _ = el.tag is Comment + _ = el.tag is ProcessingInstruction + + +def check_comment_element() -> None: + c = Comment("a comment") + assert c.tag is Comment + assert c.tag is not ProcessingInstruction diff --git a/stdlib/xml/etree/ElementTree.pyi b/stdlib/xml/etree/ElementTree.pyi index c77af2cb4dd1..7b7ceb4f73db 100644 --- a/stdlib/xml/etree/ElementTree.pyi +++ b/stdlib/xml/etree/ElementTree.pyi @@ -49,7 +49,6 @@ class ParseError(SyntaxError): # In reality it works based on `.tag` attribute duck typing. def iselement(element: object) -> TypeGuard[Element]: ... - @overload def canonicalize( xml_data: str | ReadableBuffer | None = None, @@ -96,28 +95,23 @@ class Element(Generic[_Tag]): def append(self, subelement: Element[Any], /) -> None: ... def clear(self) -> None: ... def extend(self, elements: Iterable[Element[Any]], /) -> None: ... - def find(self, path: str, namespaces: dict[str, str] | None = None) -> Element | None: ... - def findall(self, path: str, namespaces: dict[str, str] | None = None) -> list[Element]: ... - + def find(self, path: str, namespaces: dict[str, str] | None = None) -> Element[Any] | None: ... + def findall(self, path: str, namespaces: dict[str, str] | None = None) -> list[Element[Any]]: ... @overload def findtext(self, path: str, default: None = None, namespaces: dict[str, str] | None = None) -> str | None: ... @overload def findtext(self, path: str, default: _T, namespaces: dict[str, str] | None = None) -> _T | str: ... - @overload def get(self, key: str, default: None = None) -> str | None: ... @overload def get(self, key: str, default: _T) -> str | _T: ... - def insert(self, index: int, subelement: Element[Any], /) -> None: ... def items(self) -> ItemsView[str, str]: ... - def iter(self, tag: str | None = None) -> Generator[Element]: ... - + def iter(self, tag: str | None = None) -> Generator[Element[Any]]: ... @overload def iterfind(self, path: Literal[""], namespaces: dict[str, str] | None = None) -> None: ... # type: ignore[overload-overlap] @overload - def iterfind(self, path: str, namespaces: dict[str, str] | None = None) -> Generator[Element]: ... - + def iterfind(self, path: str, namespaces: dict[str, str] | None = None) -> Generator[Element[Any]]: ... def itertext(self) -> Generator[str]: ... def keys(self) -> dict_keys[str, str]: ... # makeelement returns the type of self in Python impl, but not in C impl @@ -127,16 +121,13 @@ class Element(Generic[_Tag]): def __copy__(self) -> Element[_Tag]: ... # returns the type of self in Python impl, but not in C impl def __deepcopy__(self, memo: Any, /) -> Element: ... # Only exists in C impl def __delitem__(self, key: SupportsIndex | slice, /) -> None: ... - @overload def __getitem__(self, key: SupportsIndex, /) -> Element: ... @overload def __getitem__(self, key: slice[SupportsIndex | None], /) -> list[Element]: ... - def __len__(self) -> int: ... # Doesn't actually exist at runtime, but instance of the class are indeed iterable due to __getitem__. def __iter__(self) -> Iterator[Element]: ... - @overload def __setitem__(self, key: SupportsIndex, value: Element[Any], /) -> None: ... @overload @@ -168,22 +159,18 @@ class ElementTree(Generic[_Root]): def __init__(self, element: Element[Any] | None = None, file: _FileRead | None = None) -> None: ... def getroot(self) -> _Root: ... def _setroot(self, element: Element[Any]) -> None: ... - def parse(self, source: _FileRead, parser: XMLParser | None = None) -> Element: ... - def iter(self, tag: str | None = None) -> Generator[Element]: ... - def find(self, path: str, namespaces: dict[str, str] | None = None) -> Element | None: ... - + def parse(self, source: _FileRead, parser: XMLParser | None = None) -> Element[Any]: ... + def iter(self, tag: str | None = None) -> Generator[Element[Any]]: ... + def find(self, path: str, namespaces: dict[str, str] | None = None) -> Element[Any] | None: ... @overload def findtext(self, path: str, default: None = None, namespaces: dict[str, str] | None = None) -> str | None: ... @overload def findtext(self, path: str, default: _T, namespaces: dict[str, str] | None = None) -> _T | str: ... - - def findall(self, path: str, namespaces: dict[str, str] | None = None) -> list[Element]: ... - + def findall(self, path: str, namespaces: dict[str, str] | None = None) -> list[Element[Any]]: ... @overload def iterfind(self, path: Literal[""], namespaces: dict[str, str] | None = None) -> None: ... # type: ignore[overload-overlap] @overload - def iterfind(self, path: str, namespaces: dict[str, str] | None = None) -> Generator[Element]: ... - + def iterfind(self, path: str, namespaces: dict[str, str] | None = None) -> Generator[Element[Any]]: ... def write( self, file_or_filename: _FileWrite, @@ -199,7 +186,6 @@ class ElementTree(Generic[_Root]): HTML_EMPTY: Final[set[str]] def register_namespace(prefix: str, uri: str) -> None: ... - @overload def tostring( element: Element[Any], @@ -230,7 +216,6 @@ def tostring( default_namespace: str | None = None, short_empty_elements: bool = True, ) -> Any: ... - @overload def tostringlist( element: Element[Any], @@ -261,7 +246,6 @@ def tostringlist( default_namespace: str | None = None, short_empty_elements: bool = True, ) -> list[Any]: ... - def dump(elem: Element[Any] | ElementTree[Any]) -> None: ... def indent(tree: Element[Any] | ElementTree[Any], space: str = " ", level: int = 0) -> None: ... def parse(source: _FileRead, parser: XMLParser[Any] | None = None) -> ElementTree[Element]: ... From 63d5b1a674b9071ccedf5bebef5150525c37047d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 7 Nov 2026 08:59:09 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks --- stdlib/xml/etree/ElementTree.pyi | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/stdlib/xml/etree/ElementTree.pyi b/stdlib/xml/etree/ElementTree.pyi index 7b7ceb4f73db..51367b040614 100644 --- a/stdlib/xml/etree/ElementTree.pyi +++ b/stdlib/xml/etree/ElementTree.pyi @@ -49,6 +49,7 @@ class ParseError(SyntaxError): # In reality it works based on `.tag` attribute duck typing. def iselement(element: object) -> TypeGuard[Element]: ... + @overload def canonicalize( xml_data: str | ReadableBuffer | None = None, @@ -97,21 +98,26 @@ class Element(Generic[_Tag]): def extend(self, elements: Iterable[Element[Any]], /) -> None: ... def find(self, path: str, namespaces: dict[str, str] | None = None) -> Element[Any] | None: ... def findall(self, path: str, namespaces: dict[str, str] | None = None) -> list[Element[Any]]: ... + @overload def findtext(self, path: str, default: None = None, namespaces: dict[str, str] | None = None) -> str | None: ... @overload def findtext(self, path: str, default: _T, namespaces: dict[str, str] | None = None) -> _T | str: ... + @overload def get(self, key: str, default: None = None) -> str | None: ... @overload def get(self, key: str, default: _T) -> str | _T: ... + def insert(self, index: int, subelement: Element[Any], /) -> None: ... def items(self) -> ItemsView[str, str]: ... def iter(self, tag: str | None = None) -> Generator[Element[Any]]: ... + @overload def iterfind(self, path: Literal[""], namespaces: dict[str, str] | None = None) -> None: ... # type: ignore[overload-overlap] @overload def iterfind(self, path: str, namespaces: dict[str, str] | None = None) -> Generator[Element[Any]]: ... + def itertext(self) -> Generator[str]: ... def keys(self) -> dict_keys[str, str]: ... # makeelement returns the type of self in Python impl, but not in C impl @@ -121,13 +127,16 @@ class Element(Generic[_Tag]): def __copy__(self) -> Element[_Tag]: ... # returns the type of self in Python impl, but not in C impl def __deepcopy__(self, memo: Any, /) -> Element: ... # Only exists in C impl def __delitem__(self, key: SupportsIndex | slice, /) -> None: ... + @overload def __getitem__(self, key: SupportsIndex, /) -> Element: ... @overload def __getitem__(self, key: slice[SupportsIndex | None], /) -> list[Element]: ... + def __len__(self) -> int: ... # Doesn't actually exist at runtime, but instance of the class are indeed iterable due to __getitem__. def __iter__(self) -> Iterator[Element]: ... + @overload def __setitem__(self, key: SupportsIndex, value: Element[Any], /) -> None: ... @overload @@ -162,15 +171,19 @@ class ElementTree(Generic[_Root]): def parse(self, source: _FileRead, parser: XMLParser | None = None) -> Element[Any]: ... def iter(self, tag: str | None = None) -> Generator[Element[Any]]: ... def find(self, path: str, namespaces: dict[str, str] | None = None) -> Element[Any] | None: ... + @overload def findtext(self, path: str, default: None = None, namespaces: dict[str, str] | None = None) -> str | None: ... @overload def findtext(self, path: str, default: _T, namespaces: dict[str, str] | None = None) -> _T | str: ... + def findall(self, path: str, namespaces: dict[str, str] | None = None) -> list[Element[Any]]: ... + @overload def iterfind(self, path: Literal[""], namespaces: dict[str, str] | None = None) -> None: ... # type: ignore[overload-overlap] @overload def iterfind(self, path: str, namespaces: dict[str, str] | None = None) -> Generator[Element[Any]]: ... + def write( self, file_or_filename: _FileWrite, @@ -186,6 +199,7 @@ class ElementTree(Generic[_Root]): HTML_EMPTY: Final[set[str]] def register_namespace(prefix: str, uri: str) -> None: ... + @overload def tostring( element: Element[Any], @@ -216,6 +230,7 @@ def tostring( default_namespace: str | None = None, short_empty_elements: bool = True, ) -> Any: ... + @overload def tostringlist( element: Element[Any], @@ -246,6 +261,7 @@ def tostringlist( default_namespace: str | None = None, short_empty_elements: bool = True, ) -> list[Any]: ... + def dump(elem: Element[Any] | ElementTree[Any]) -> None: ... def indent(tree: Element[Any] | ElementTree[Any], space: str = " ", level: int = 0) -> None: ... def parse(source: _FileRead, parser: XMLParser[Any] | None = None) -> ElementTree[Element]: ...