From c96bf18ea29818a78346a925ef9a3f65b6d59ab8 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Mon, 2 Jun 2025 10:45:28 +0200 Subject: [PATCH 1/7] Fix return type of ElementTree.iterparse() The type of the second element of the tuple returned from the iterator depends on the event. --- stdlib/xml/etree/ElementTree.pyi | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/stdlib/xml/etree/ElementTree.pyi b/stdlib/xml/etree/ElementTree.pyi index 4c55a1a7452e..2728f1f0b502 100644 --- a/stdlib/xml/etree/ElementTree.pyi +++ b/stdlib/xml/etree/ElementTree.pyi @@ -249,16 +249,31 @@ def dump(elem: Element | ElementTree[Any]) -> None: ... def indent(tree: Element | ElementTree[Any], space: str = " ", level: int = 0) -> None: ... def parse(source: _FileRead, parser: XMLParser[Any] | None = None) -> ElementTree[Element]: ... -# This class is defined inside the body of iterparse +# The possible element types depend on events passed to iterparse. +# * start, end: Element[str] +# * comment, pi: Element[_ElementCallable] +# * start-ns: tuple[str, str] (prefix, uri) +# * end-ns: None +_EventT_co = TypeVar("_EventT_co", bound=Element[str] | Element[_ElementCallable] | tuple[str, str] | None, covariant=True) +_EventType: TypeAlias = Literal["start", "end", "comment", "pi", "start-ns", "end-ns"] + +# This class is defined inside the body of iterparse. @type_check_only -class _IterParseIterator(Iterator[tuple[str, Element]], Protocol): - def __next__(self) -> tuple[str, Element]: ... +class _IterParseIterator(Iterator[tuple[_EventType, _EventT_co]], Protocol[_EventT_co]): if sys.version_info >= (3, 13): def close(self) -> None: ... if sys.version_info >= (3, 11): def __del__(self) -> None: ... -def iterparse(source: _FileRead, events: Sequence[str] | None = None, parser: XMLParser | None = None) -> _IterParseIterator: ... +# See the comment for _EventT_co above for possible iterator types. +@overload +def iterparse( + source: _FileRead, + events: Sequence[_EventType], + parser: XMLParser | None = None +) -> _IterParseIterator[Any]: ... +@overload +def iterparse(source: _FileRead, events: None = None, parser: XMLParser | None = None) -> _IterParseIterator[Element[str]]: ... _EventQueue: TypeAlias = tuple[str] | tuple[str, tuple[str, str]] | tuple[str, None] From 1bc42ca5a5d614f58124a95296ce8d21ed68e7e5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 08:47:25 +0000 Subject: [PATCH 2/7] [pre-commit.ci] auto fixes from pre-commit.com hooks --- stdlib/xml/etree/ElementTree.pyi | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/stdlib/xml/etree/ElementTree.pyi b/stdlib/xml/etree/ElementTree.pyi index 2728f1f0b502..3e46e7580309 100644 --- a/stdlib/xml/etree/ElementTree.pyi +++ b/stdlib/xml/etree/ElementTree.pyi @@ -267,11 +267,7 @@ class _IterParseIterator(Iterator[tuple[_EventType, _EventT_co]], Protocol[_Even # See the comment for _EventT_co above for possible iterator types. @overload -def iterparse( - source: _FileRead, - events: Sequence[_EventType], - parser: XMLParser | None = None -) -> _IterParseIterator[Any]: ... +def iterparse(source: _FileRead, events: Sequence[_EventType], parser: XMLParser | None = None) -> _IterParseIterator[Any]: ... @overload def iterparse(source: _FileRead, events: None = None, parser: XMLParser | None = None) -> _IterParseIterator[Element[str]]: ... From ee643f15ff40208711eea3750a3acb871c56af0c Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Mon, 2 Jun 2025 10:47:26 +0200 Subject: [PATCH 3/7] Improve comment --- stdlib/xml/etree/ElementTree.pyi | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/stdlib/xml/etree/ElementTree.pyi b/stdlib/xml/etree/ElementTree.pyi index 3e46e7580309..c192dc40be13 100644 --- a/stdlib/xml/etree/ElementTree.pyi +++ b/stdlib/xml/etree/ElementTree.pyi @@ -249,11 +249,12 @@ def dump(elem: Element | ElementTree[Any]) -> None: ... def indent(tree: Element | ElementTree[Any], space: str = " ", level: int = 0) -> None: ... def parse(source: _FileRead, parser: XMLParser[Any] | None = None) -> ElementTree[Element]: ... -# The possible element types depend on events passed to iterparse. -# * start, end: Element[str] -# * comment, pi: Element[_ElementCallable] -# * start-ns: tuple[str, str] (prefix, uri) -# * end-ns: None +# The type of the second element of the tuple yielded by iterparse depends +# on the event type in the first element of the tuple: +# * start, end: Element[str] +# * comment, pi: Element[_ElementCallable] +# * start-ns: tuple[str, str] (prefix, uri) +# * end-ns: None _EventT_co = TypeVar("_EventT_co", bound=Element[str] | Element[_ElementCallable] | tuple[str, str] | None, covariant=True) _EventType: TypeAlias = Literal["start", "end", "comment", "pi", "start-ns", "end-ns"] From d1883adee3f6316d8d53997847105cf9ff89340b Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Mon, 2 Jun 2025 10:50:12 +0200 Subject: [PATCH 4/7] Update a few more types --- stdlib/xml/etree/ElementTree.pyi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stdlib/xml/etree/ElementTree.pyi b/stdlib/xml/etree/ElementTree.pyi index c192dc40be13..8c0540ad314b 100644 --- a/stdlib/xml/etree/ElementTree.pyi +++ b/stdlib/xml/etree/ElementTree.pyi @@ -268,17 +268,17 @@ class _IterParseIterator(Iterator[tuple[_EventType, _EventT_co]], Protocol[_Even # See the comment for _EventT_co above for possible iterator types. @overload -def iterparse(source: _FileRead, events: Sequence[_EventType], parser: XMLParser | None = None) -> _IterParseIterator[Any]: ... +def iterparse(source: _FileRead, events: Iterable[_EventType], parser: XMLParser | None = None) -> _IterParseIterator[Any]: ... @overload def iterparse(source: _FileRead, events: None = None, parser: XMLParser | None = None) -> _IterParseIterator[Element[str]]: ... _EventQueue: TypeAlias = tuple[str] | tuple[str, tuple[str, str]] | tuple[str, None] -class XMLPullParser(Generic[_E]): - def __init__(self, events: Sequence[str] | None = None, *, _parser: XMLParser[_E] | None = None) -> None: ... +class XMLPullParser(Generic[_EventT_co]): + def __init__(self, events: Iterable[_EventType] | None = None, *, _parser: XMLParser[_EventT_co] | None = None) -> None: ... def feed(self, data: str | ReadableBuffer) -> None: ... def close(self) -> None: ... - def read_events(self) -> Iterator[_EventQueue | tuple[str, _E]]: ... + def read_events(self) -> Iterator[_EventQueue | tuple[_EventType, _EventT_co]]: ... def flush(self) -> None: ... def XML(text: str | ReadableBuffer, parser: XMLParser | None = None) -> Element: ... From 2fb080ecc3debaddf419af02e6c15fe1726f18b2 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Mon, 2 Jun 2025 10:56:31 +0200 Subject: [PATCH 5/7] Passing a parser is deprecated and in that case all bets are off --- stdlib/xml/etree/ElementTree.pyi | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/stdlib/xml/etree/ElementTree.pyi b/stdlib/xml/etree/ElementTree.pyi index 8c0540ad314b..8fc9aa5bfb0c 100644 --- a/stdlib/xml/etree/ElementTree.pyi +++ b/stdlib/xml/etree/ElementTree.pyi @@ -268,9 +268,14 @@ class _IterParseIterator(Iterator[tuple[_EventType, _EventT_co]], Protocol[_Even # See the comment for _EventT_co above for possible iterator types. @overload -def iterparse(source: _FileRead, events: Iterable[_EventType], parser: XMLParser | None = None) -> _IterParseIterator[Any]: ... +def iterparse(source: _FileRead, events: Iterable[_EventType]) -> _IterParseIterator[Any]: ... +@overload +def iterparse(source: _FileRead, events: None = None) -> _IterParseIterator[Element[str]]: ... +# In case a custom parser is passed, the type of the second element of the tuple +# yielded by iterparse depends on the parser. @overload -def iterparse(source: _FileRead, events: None = None, parser: XMLParser | None = None) -> _IterParseIterator[Element[str]]: ... +@deprecated("The *parser* argument is deprecated.") +def iterparse(source: _FileRead, events: Iterable[_EventType], parser: XMLParser | None = None) -> _IterParseIterator[Any]: ... _EventQueue: TypeAlias = tuple[str] | tuple[str, tuple[str, str]] | tuple[str, None] From 501e25cde18847064f495f314585cc3343430af6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 08:59:00 +0000 Subject: [PATCH 6/7] [pre-commit.ci] auto fixes from pre-commit.com hooks --- stdlib/xml/etree/ElementTree.pyi | 1 + 1 file changed, 1 insertion(+) diff --git a/stdlib/xml/etree/ElementTree.pyi b/stdlib/xml/etree/ElementTree.pyi index 8fc9aa5bfb0c..17eb74f6f675 100644 --- a/stdlib/xml/etree/ElementTree.pyi +++ b/stdlib/xml/etree/ElementTree.pyi @@ -271,6 +271,7 @@ class _IterParseIterator(Iterator[tuple[_EventType, _EventT_co]], Protocol[_Even def iterparse(source: _FileRead, events: Iterable[_EventType]) -> _IterParseIterator[Any]: ... @overload def iterparse(source: _FileRead, events: None = None) -> _IterParseIterator[Element[str]]: ... + # In case a custom parser is passed, the type of the second element of the tuple # yielded by iterparse depends on the parser. @overload From 00e39537382c64b6cd702484d96fe11b19f7b625 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Sat, 26 Jul 2025 17:03:57 +0200 Subject: [PATCH 7/7] Update defusedxml --- stubs/defusedxml/defusedxml/ElementTree.pyi | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/stubs/defusedxml/defusedxml/ElementTree.pyi b/stubs/defusedxml/defusedxml/ElementTree.pyi index 3b10271665af..638db7e47a16 100644 --- a/stubs/defusedxml/defusedxml/ElementTree.pyi +++ b/stubs/defusedxml/defusedxml/ElementTree.pyi @@ -1,6 +1,6 @@ from _typeshed import ReadableBuffer from collections.abc import Sequence -from typing import Final +from typing import Any, Final from xml.etree.ElementTree import ( Element, ElementTree, @@ -49,7 +49,7 @@ XMLTreeBuilder = DefusedXMLParser XMLParse = DefusedXMLParser XMLParser = DefusedXMLParser -# wrapper to xml.etree.ElementTree.parse +# Wrapper to xml.etree.ElementTree.parse def parse( source: _FileRead, parser: XMLParser | None = None, @@ -58,7 +58,9 @@ def parse( forbid_external: bool = True, ) -> ElementTree: ... -# wrapper to xml.etree.ElementTree.iterparse +# Wrapper to xml.etree.ElementTree.iterparse +# +# See there for possible return types. def iterparse( source: _FileRead, events: Sequence[str] | None = None, @@ -66,7 +68,7 @@ def iterparse( forbid_dtd: bool = False, forbid_entities: bool = True, forbid_external: bool = True, -) -> _IterParseIterator: ... +) -> _IterParseIterator[Any]: ... def fromstring( text: str | ReadableBuffer, forbid_dtd: bool = False, forbid_entities: bool = True, forbid_external: bool = True ) -> Element: ...