From 9e02477908e400e96f20453d219e7ba8cb082ff0 Mon Sep 17 00:00:00 2001 From: Zack Weinberg Date: Fri, 13 Oct 2023 11:50:05 -0400 Subject: [PATCH] Complete type stubs for xml.dom.minidom (#6886) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch completes all the type stubs for xml.dom.minidom. I did not look at the rest of the xml.dom directory except as required to make the typing of minidom complete, so it probably doesn’t completely resolve #6886. xml.dom.minidom is very poorly documented—its authors seem to have assumed that one could refer to the W3C DOM spec. But at the same time the implementation diverges significantly from the spec, so I do not have complete confidence in all the type annotations. Still, it should be an improvement on what’s there now. The following commands now pass with no errors when executed from the top of the typeshed tree: ```sh mypy --custom-typeshed-dir . --strict --warn-incomplete-stub \ -m xml.dom.minidom stubtest --custom-typeshed-dir . xml.dom.minidom ``` **However**, attempting to do the same for xml.dom.minicompat produces the following error: ```sh mypy --custom-typeshed-dir . --strict --warn-incomplete-stub \ -m xml.dom.minicompat stdlib/xml/dom/minicompat.pyi:11: error: All bases of a protocol must be protocols [misc] ``` I don’t know how to fix this, because in the implementation, EmptyNodeList is not a subclass of NodeList, therefore if I don’t make NodeList a protocol, then xml.dom.minidom.Childless fails stubtest. This patch probably should not land until this is resolved. Any advice you could give would be welcome. --- stdlib/xml/dom/__init__.pyi | 6 +- stdlib/xml/dom/minicompat.pyi | 8 +- stdlib/xml/dom/minidom.pyi | 304 +++++++++++++++++----------------- 3 files changed, 156 insertions(+), 162 deletions(-) diff --git a/stdlib/xml/dom/__init__.pyi b/stdlib/xml/dom/__init__.pyi index e5b91bf2a795..9172b425d3d2 100644 --- a/stdlib/xml/dom/__init__.pyi +++ b/stdlib/xml/dom/__init__.pyi @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, Protocol from .domreg import getDOMImplementation as getDOMImplementation, registerDOMImplementation as registerDOMImplementation @@ -56,12 +56,14 @@ class NamespaceErr(DOMException): ... class InvalidAccessErr(DOMException): ... class ValidationErr(DOMException): ... -class UserDataHandler: +class UserDataHandler(Protocol): NODE_CLONED: int NODE_IMPORTED: int NODE_DELETED: int NODE_RENAMED: int + def handle(self, operation: int, key: str, data: object, src: Node, dst: Node) -> None: ... + XML_NAMESPACE: str XMLNS_NAMESPACE: str XHTML_NAMESPACE: str diff --git a/stdlib/xml/dom/minicompat.pyi b/stdlib/xml/dom/minicompat.pyi index 4d83bef025d9..c28d15d6721b 100644 --- a/stdlib/xml/dom/minicompat.pyi +++ b/stdlib/xml/dom/minicompat.pyi @@ -1,14 +1,14 @@ -from collections.abc import Iterable -from typing import Any, TypeVar +from collections.abc import Iterable, Sequence +from typing import Any, Protocol, TypeVar from typing_extensions import Literal __all__ = ["NodeList", "EmptyNodeList", "StringTypes", "defproperty"] -_T = TypeVar("_T") +_T = TypeVar("_T", covariant=True) StringTypes: tuple[type[str]] -class NodeList(list[_T]): +class NodeList(Sequence[_T], Protocol): @property def length(self) -> int: ... def item(self, index: int) -> _T | None: ... diff --git a/stdlib/xml/dom/minidom.pyi b/stdlib/xml/dom/minidom.pyi index ec17f0a41497..b5cc66c08c01 100644 --- a/stdlib/xml/dom/minidom.pyi +++ b/stdlib/xml/dom/minidom.pyi @@ -1,9 +1,11 @@ import sys import xml.dom -from _typeshed import Incomplete, ReadableBuffer, SupportsRead, SupportsWrite +from _typeshed import ReadableBuffer, SupportsRead, SupportsWrite, Unused +from collections.abc import Iterable, Sequence from typing import NoReturn, TypeVar, overload from typing_extensions import Literal, Self -from xml.dom.minicompat import NodeList +from xml.dom import UserDataHandler +from xml.dom.minicompat import * from xml.dom.xmlbuilder import DocumentLS, DOMImplementationLS from xml.sax.xmlreader import XMLReader @@ -13,15 +15,19 @@ def parse( file: str | SupportsRead[ReadableBuffer | str], parser: XMLReader | None = None, bufsize: int | None = None ) -> Document: ... def parseString(string: str | ReadableBuffer, parser: XMLReader | None = None) -> Document: ... -def getDOMImplementation(features=None) -> DOMImplementation | None: ... +def getDOMImplementation(features: str | Iterable[tuple[str, str | None]] | None = None) -> DOMImplementation | None: ... class Node(xml.dom.Node): + parentNode: Node | None + childNodes: NodeList + nextSibling: Node | None + previousSibling: Node | None + ownerDocument: Document | None namespaceURI: str | None - parentNode: Incomplete - ownerDocument: Incomplete - nextSibling: Incomplete - previousSibling: Incomplete - prefix: Incomplete + prefix: str | None + # nodeType and attributes are part of the Node _interface_, + # but are absent from the abstract class xml.dom.minidom.Node + @property def firstChild(self) -> Node | None: ... @property @@ -95,74 +101,73 @@ class Node(xml.dom.Node): ) -> bytes: ... def hasChildNodes(self) -> bool: ... - def insertBefore(self, newChild, refChild): ... + def insertBefore(self, newChild: _N, refChild: Node) -> _N: ... def appendChild(self, node: _N) -> _N: ... - def replaceChild(self, newChild, oldChild): ... - def removeChild(self, oldChild): ... + def replaceChild(self, newChild: Node, oldChild: _N) -> _N: ... + def removeChild(self, oldChild: _N) -> _N: ... def normalize(self) -> None: ... - def cloneNode(self, deep): ... - def isSupported(self, feature, version): ... - def isSameNode(self, other): ... - def getInterface(self, feature): ... - def getUserData(self, key): ... - def setUserData(self, key, data, handler): ... - childNodes: Incomplete + def cloneNode(self, deep: bool) -> Self: ... + def isSupported(self, feature: str, version: str | None) -> bool: ... + def isSameNode(self, other: Node) -> bool: ... + def getInterface(self, feature: str) -> Self | None: ... + def getUserData(self, key: str) -> object: ... + def setUserData(self, key: str, data: object, handler: UserDataHandler) -> object | None: ... def unlink(self) -> None: ... def __enter__(self) -> Self: ... - def __exit__(self, et, ev, tb) -> None: ... + def __exit__(self, et: Unused, ev: Unused, tb: Unused) -> None: ... class DocumentFragment(Node): nodeType: int nodeName: str - nodeValue: Incomplete - attributes: Incomplete - parentNode: Incomplete - childNodes: Incomplete + nodeValue: None + attributes: None + parentNode: None + childNodes: NodeList def __init__(self) -> None: ... class Attr(Node): name: str nodeType: int - attributes: Incomplete + attributes: None specified: bool - ownerElement: Incomplete + ownerElement: Element | None namespaceURI: str | None - childNodes: Incomplete - nodeName: Incomplete - nodeValue: str + childNodes: NodeList + nodeName: str + nodeValue: str | None value: str - prefix: Incomplete + prefix: str | None def __init__( - self, qName: str, namespaceURI: str | None = None, localName: str | None = None, prefix: Incomplete | None = None + self, qName: str, namespaceURI: str | None = None, localName: str | None = None, prefix: str | None = None ) -> None: ... def unlink(self) -> None: ... @property def isId(self) -> bool: ... @property - def schemaType(self): ... + def schemaType(self) -> TypeInfo: ... class NamedNodeMap: - def __init__(self, attrs, attrsNS, ownerElement) -> None: ... - def item(self, index): ... - def items(self): ... - def itemsNS(self): ... - def __contains__(self, key): ... - def keys(self): ... - def keysNS(self): ... - def values(self): ... - def get(self, name: str, value: Incomplete | None = None): ... + def __init__(self, attrs: dict[str, Attr], attrsNS: dict[tuple[str, str], Attr], ownerElement: Node) -> None: ... + def item(self, index: int) -> Attr: ... + def items(self) -> Sequence[tuple[str, str | None]]: ... + def itemsNS(self) -> Sequence[tuple[tuple[str, str], str | None]]: ... + def __contains__(self, key: str | tuple[str, str]) -> bool: ... + def keys(self) -> Iterable[str]: ... + def keysNS(self) -> Iterable[tuple[str, str]]: ... + def values(self) -> Iterable[Attr]: ... + def get(self, name: str, value: str | None = None) -> str | None: ... def __len__(self) -> int: ... def __eq__(self, other: object) -> bool: ... def __ge__(self, other: NamedNodeMap) -> bool: ... def __gt__(self, other: NamedNodeMap) -> bool: ... def __le__(self, other: NamedNodeMap) -> bool: ... def __lt__(self, other: NamedNodeMap) -> bool: ... - def __getitem__(self, attname_or_tuple: tuple[str, str | None] | str): ... + def __getitem__(self, attname_or_tuple: tuple[str, str | None] | str) -> Attr: ... def __setitem__(self, attname: str, value: Attr | str) -> None: ... def getNamedItem(self, name: str) -> Attr | None: ... def getNamedItemNS(self, namespaceURI: str, localName: str | None) -> Attr | None: ... def removeNamedItem(self, name: str) -> Attr: ... - def removeNamedItemNS(self, namespaceURI: str, localName: str | None): ... + def removeNamedItemNS(self, namespaceURI: str, localName: str | None) -> Attr: ... def setNamedItem(self, node: Attr) -> Attr: ... def setNamedItemNS(self, node: Attr) -> Attr: ... def __delitem__(self, attname_or_tuple: tuple[str, str | None] | str) -> None: ... @@ -172,77 +177,72 @@ class NamedNodeMap: AttributeList = NamedNodeMap class TypeInfo: - namespace: Incomplete | None + namespace: str | None name: str - def __init__(self, namespace: Incomplete | None, name: str) -> None: ... + def __init__(self, namespace: str | None, name: str) -> None: ... class Element(Node): nodeType: int - nodeValue: Incomplete - schemaType: Incomplete - parentNode: Incomplete + nodeValue: None + schemaType: TypeInfo tagName: str nodeName: str - prefix: Incomplete - namespaceURI: str | None - childNodes: Incomplete - nextSibling: Incomplete + def __init__( - self, tagName, namespaceURI: str | None = None, prefix: Incomplete | None = None, localName: Incomplete | None = None + self, tagName: str, namespaceURI: str | None = None, prefix: str | None = None, localName: str | None = None ) -> None: ... def unlink(self) -> None: ... def getAttribute(self, attname: str) -> str: ... - def getAttributeNS(self, namespaceURI: str, localName): ... + def getAttributeNS(self, namespaceURI: str, localName: str) -> str: ... def setAttribute(self, attname: str, value: str) -> None: ... - def setAttributeNS(self, namespaceURI: str, qualifiedName: str, value) -> None: ... - def getAttributeNode(self, attrname: str): ... - def getAttributeNodeNS(self, namespaceURI: str, localName): ... - def setAttributeNode(self, attr): ... - setAttributeNodeNS: Incomplete + def setAttributeNS(self, namespaceURI: str, qualifiedName: str, value: str) -> None: ... + def getAttributeNode(self, attrname: str) -> Attr: ... + def getAttributeNodeNS(self, namespaceURI: str, localName: str) -> Attr: ... + def setAttributeNode(self, attr: Attr) -> Attr: ... + def setAttributeNodeNS(self, attr: Attr) -> Attr: ... def removeAttribute(self, name: str) -> None: ... - def removeAttributeNS(self, namespaceURI: str, localName) -> None: ... - def removeAttributeNode(self, node): ... - removeAttributeNodeNS: Incomplete + def removeAttributeNS(self, namespaceURI: str, localName: str) -> None: ... + def removeAttributeNode(self, node: Attr) -> Attr: ... + def removeAttributeNodeNS(self, node: Attr) -> Attr: ... def hasAttribute(self, name: str) -> bool: ... - def hasAttributeNS(self, namespaceURI: str, localName) -> bool: ... + def hasAttributeNS(self, namespaceURI: str, localName: str) -> bool: ... def getElementsByTagName(self, name: str) -> NodeList[Element]: ... def getElementsByTagNameNS(self, namespaceURI: str, localName: str) -> NodeList[Element]: ... def writexml(self, writer: SupportsWrite[str], indent: str = "", addindent: str = "", newl: str = "") -> None: ... def hasAttributes(self) -> bool: ... - def setIdAttribute(self, name) -> None: ... - def setIdAttributeNS(self, namespaceURI: str, localName) -> None: ... - def setIdAttributeNode(self, idAttr) -> None: ... + def setIdAttribute(self, name: str) -> None: ... + def setIdAttributeNS(self, namespaceURI: str, localName: str) -> None: ... + def setIdAttributeNode(self, idAttr: Attr) -> None: ... @property def attributes(self) -> NamedNodeMap: ... class Childless: - attributes: Incomplete - childNodes: Incomplete - firstChild: Incomplete - lastChild: Incomplete - def appendChild(self, node) -> NoReturn: ... - def hasChildNodes(self) -> bool: ... - def insertBefore(self, newChild, refChild) -> NoReturn: ... - def removeChild(self, oldChild) -> NoReturn: ... + attributes: None + childNodes: NodeList + firstChild: None + lastChild: None + def appendChild(self, node: Node) -> NoReturn: ... + def hasChildNodes(self) -> Literal[False]: ... + def insertBefore(self, newChild: Node, refChild: Node) -> NoReturn: ... + def removeChild(self, oldChild: Node) -> NoReturn: ... def normalize(self) -> None: ... - def replaceChild(self, newChild, oldChild) -> NoReturn: ... + def replaceChild(self, newChild: Node, oldChild: Node) -> NoReturn: ... class ProcessingInstruction(Childless, Node): nodeType: int - target: Incomplete - data: Incomplete - def __init__(self, target, data) -> None: ... - nodeValue: Incomplete - nodeName: Incomplete + target: str + data: str + def __init__(self, target: str, data: str) -> None: ... + nodeValue: str + nodeName: str def writexml(self, writer: SupportsWrite[str], indent: str = "", addindent: str = "", newl: str = "") -> None: ... class CharacterData(Childless, Node): - ownerDocument: Incomplete - previousSibling: Incomplete + data: str + nodeValue: str + def __init__(self) -> None: ... def __len__(self) -> int: ... - data: str - nodeValue: Incomplete def substringData(self, offset: int, count: int) -> str: ... def appendData(self, arg: str) -> None: ... def insertData(self, offset: int, arg: str) -> None: ... @@ -254,11 +254,10 @@ class CharacterData(Childless, Node): class Text(CharacterData): nodeType: int nodeName: str - attributes: Incomplete - data: Incomplete - def splitText(self, offset): ... + + def splitText(self, offset: int) -> Self: ... def writexml(self, writer: SupportsWrite[str], indent: str = "", addindent: str = "", newl: str = "") -> None: ... - def replaceWholeText(self, content): ... + def replaceWholeText(self, content: str) -> Self: ... @property def isWhitespaceInElementContent(self) -> bool: ... @property @@ -267,7 +266,7 @@ class Text(CharacterData): class Comment(CharacterData): nodeType: int nodeName: str - def __init__(self, data) -> None: ... + def __init__(self, data: str) -> None: ... def writexml(self, writer: SupportsWrite[str], indent: str = "", addindent: str = "", newl: str = "") -> None: ... class CDATASection(Text): @@ -276,56 +275,55 @@ class CDATASection(Text): def writexml(self, writer: SupportsWrite[str], indent: str = "", addindent: str = "", newl: str = "") -> None: ... class ReadOnlySequentialNamedNodeMap: - def __init__(self, seq=()) -> None: ... + def __init__(self, seq: Sequence[Node] = ()) -> None: ... def __len__(self) -> int: ... - def getNamedItem(self, name): ... - def getNamedItemNS(self, namespaceURI: str, localName): ... - def __getitem__(self, name_or_tuple): ... - def item(self, index): ... - def removeNamedItem(self, name) -> None: ... - def removeNamedItemNS(self, namespaceURI: str, localName) -> None: ... - def setNamedItem(self, node) -> None: ... - def setNamedItemNS(self, node) -> None: ... + def getNamedItem(self, name: str) -> Node: ... + def getNamedItemNS(self, namespaceURI: str, localName: str) -> Node: ... + def __getitem__(self, name_or_tuple: tuple[str, str] | str) -> Node: ... + def item(self, index: int) -> Node: ... + def removeNamedItem(self, name: str) -> NoReturn: ... + def removeNamedItemNS(self, namespaceURI: str, localName: str) -> NoReturn: ... + def setNamedItem(self, node: Node) -> NoReturn: ... + def setNamedItemNS(self, node: Node) -> NoReturn: ... @property def length(self) -> int: ... class Identified: - publicId: Incomplete - systemId: Incomplete + publicId: str | None + systemId: str | None class DocumentType(Identified, Childless, Node): nodeType: int - nodeValue: Incomplete - name: Incomplete - internalSubset: Incomplete - entities: Incomplete - notations: Incomplete - nodeName: Incomplete + nodeValue: None + name: str | None + internalSubset: str | None + entities: ReadOnlySequentialNamedNodeMap + notations: ReadOnlySequentialNamedNodeMap + nodeName: str def __init__(self, qualifiedName: str) -> None: ... - def cloneNode(self, deep): ... def writexml(self, writer: SupportsWrite[str], indent: str = "", addindent: str = "", newl: str = "") -> None: ... class Entity(Identified, Node): - attributes: Incomplete + attributes: None nodeType: int - nodeValue: Incomplete - actualEncoding: Incomplete - encoding: Incomplete - version: Incomplete - nodeName: Incomplete - notationName: Incomplete - childNodes: Incomplete - def __init__(self, name, publicId, systemId, notation) -> None: ... - def appendChild(self, newChild) -> NoReturn: ... - def insertBefore(self, newChild, refChild) -> NoReturn: ... - def removeChild(self, oldChild) -> NoReturn: ... - def replaceChild(self, newChild, oldChild) -> NoReturn: ... + nodeValue: None + actualEncoding: str | None + encoding: str | None + version: str | None + nodeName: str + notationName: str + + def __init__(self, name: str, publicId: str, systemId: str, notation: str) -> None: ... + def appendChild(self, newChild: Node) -> NoReturn: ... + def insertBefore(self, newChild: Node, refChild: Node) -> NoReturn: ... + def removeChild(self, oldChild: Node) -> NoReturn: ... + def replaceChild(self, newChild: Node, oldChild: Node) -> NoReturn: ... class Notation(Identified, Childless, Node): nodeType: int - nodeValue: Incomplete - nodeName: Incomplete - def __init__(self, name, publicId, systemId) -> None: ... + nodeValue: None + nodeName: str + def __init__(self, name: str, publicId: str, systemId: str) -> None: ... class DOMImplementation(DOMImplementationLS): def hasFeature(self, feature: str, version: str | None) -> bool: ... @@ -334,53 +332,52 @@ class DOMImplementation(DOMImplementationLS): def getInterface(self, feature: str) -> Self | None: ... class ElementInfo: - tagName: Incomplete - def __init__(self, name) -> None: ... - def getAttributeType(self, aname): ... - def getAttributeTypeNS(self, namespaceURI: str, localName): ... - def isElementContent(self): ... - def isEmpty(self): ... - def isId(self, aname): ... - def isIdNS(self, namespaceURI: str, localName): ... + tagName: str + def __init__(self, name: str) -> None: ... + def getAttributeType(self, aname: str) -> TypeInfo: ... + def getAttributeTypeNS(self, namespaceURI: str, localName: str) -> TypeInfo: ... + def isElementContent(self) -> bool: ... + def isEmpty(self) -> bool: ... + def isId(self, aname: str) -> bool: ... + def isIdNS(self, namespaceURI: str, localName: str) -> bool: ... class Document(Node, DocumentLS): - implementation: Incomplete + implementation: DOMImplementation nodeType: int nodeName: str - nodeValue: Incomplete - attributes: Incomplete - parentNode: Incomplete - previousSibling: Incomplete - nextSibling: Incomplete - actualEncoding: Incomplete + nodeValue: None + attributes: None + parentNode: None + previousSibling: None + nextSibling: None + actualEncoding: str | None encoding: str | None standalone: bool | None - version: Incomplete + version: str | None strictErrorChecking: bool - errorHandler: Incomplete - documentURI: Incomplete + errorHandler: None + documentURI: str | None doctype: DocumentType | None - childNodes: Incomplete + childNodes: NodeList + documentElement: Element + def __init__(self) -> None: ... - def appendChild(self, node: _N) -> _N: ... - documentElement: Incomplete - def removeChild(self, oldChild): ... def unlink(self) -> None: ... - def cloneNode(self, deep): ... + def cloneNode(self, deep: bool) -> Self: ... def createDocumentFragment(self) -> DocumentFragment: ... def createElement(self, tagName: str) -> Element: ... def createTextNode(self, data: str) -> Text: ... def createCDATASection(self, data: str) -> CDATASection: ... def createComment(self, data: str) -> Comment: ... - def createProcessingInstruction(self, target, data): ... - def createAttribute(self, qName) -> Attr: ... - def createElementNS(self, namespaceURI: str, qualifiedName: str): ... + def createProcessingInstruction(self, target: str, data: str) -> ProcessingInstruction: ... + def createAttribute(self, qName: str) -> Attr: ... + def createElementNS(self, namespaceURI: str, qualifiedName: str) -> Element: ... def createAttributeNS(self, namespaceURI: str, qualifiedName: str) -> Attr: ... def getElementById(self, id: str) -> Element | None: ... def getElementsByTagName(self, name: str) -> NodeList[Element]: ... def getElementsByTagNameNS(self, namespaceURI: str, localName: str) -> NodeList[Element]: ... def isSupported(self, feature: str, version: str | None) -> bool: ... - def importNode(self, node, deep): ... + def importNode(self, node: _N, deep: bool) -> _N: ... if sys.version_info >= (3, 9): def writexml( self, @@ -393,12 +390,7 @@ class Document(Node, DocumentLS): ) -> None: ... else: def writexml( - self, - writer: SupportsWrite[str], - indent: str = "", - addindent: str = "", - newl: str = "", - encoding: Incomplete | None = None, + self, writer: SupportsWrite[str], indent: str = "", addindent: str = "", newl: str = "", encoding: str | None = None ) -> None: ... - def renameNode(self, n, namespaceURI: str, name): ... + def renameNode(self, n: _N, namespaceURI: str, name: str) -> _N: ...