Complete type stubs for xml.dom.minidom (#6886)#10879
Complete type stubs for xml.dom.minidom (#6886)#10879zackw wants to merge 1 commit intopython:mainfrom
Conversation
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 python#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.
|
According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉 |
|
It looks like all the failures are due to the xml.dom.minicompat issue I mentioned up top. For clarity, this is one of those places where the implementation diverges from the spec. All implementations of the It ought to be perfectly fine for users of xml.dom.minidom to treat the type of if I make xml.dom.minicompat.NodeList be a Protocol in the type stubs. But but then every user of the childNodes property would have to know about EmptyNodeList, which the authors of the stdlib intended to be a pure implementation detail. The entire minicompat module is undocumented in the stdlib. So I don't like that option either. Again, any advice you could give would be welcome. |
| StringTypes: tuple[type[str]] | ||
|
|
||
| class NodeList(list[_T]): | ||
| class NodeList(Sequence[_T], Protocol): |
There was a problem hiding this comment.
This doesn't look right. At runtime NodeList inherits from list and it is indeed a list and its append method is used elsewhere in the code. Why was it changed?
|
|
||
| def hasChildNodes(self) -> bool: ... | ||
| def insertBefore(self, newChild, refChild): ... | ||
| def insertBefore(self, newChild: _N, refChild: Node) -> _N: ... |
There was a problem hiding this comment.
There is a if refChild is None check at runtime
| def insertBefore(self, newChild: _N, refChild: Node) -> _N: ... | |
| def insertBefore(self, newChild: _N, refChild: Node | None) -> _N: ... |
| def getUserData(self, key: str) -> object: ... | ||
| def setUserData(self, key: str, data: object, handler: UserDataHandler) -> object | None: ... |
There was a problem hiding this comment.
Returning object would require every use of the return value to be guarded by isinstance checks. Any is preferred for return values.
| def getUserData(self, key: str) -> object: ... | |
| def setUserData(self, key: str, data: object, handler: UserDataHandler) -> object | None: ... | |
| def getUserData(self, key: str) -> Any: ... | |
| def setUserData(self, key: str, data: object, handler: UserDataHandler) -> Any: ... |
|
|
||
| class Node(xml.dom.Node): | ||
| parentNode: Node | None | ||
| childNodes: NodeList |
There was a problem hiding this comment.
To silence pyright. Need to import Incomplete from _typeshed.
| childNodes: NodeList | |
| childNodes: NodeList[Incomplete] |
(same for the NodeList annotations below)
| def insertBefore(self, newChild, refChild) -> NoReturn: ... | ||
| def removeChild(self, oldChild) -> NoReturn: ... | ||
| attributes: None | ||
| childNodes: NodeList |
There was a problem hiding this comment.
This is an EmptyNodeList. If the type checker complains you can add a type: ignore comment
| childNodes: NodeList | |
| childNodes: EmptyNodeList |
| 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: ... |
There was a problem hiding this comment.
This returns None if content is falsy.
| def removeNamedItemNS(self, namespaceURI: str, localName) -> None: ... | ||
| def setNamedItem(self, node) -> None: ... | ||
| def setNamedItemNS(self, node) -> None: ... | ||
| def getNamedItem(self, name: str) -> Node: ... |
There was a problem hiding this comment.
This returns None if the node is not found in the sequence
| def getNamedItem(self, name: str) -> Node: ... | |
| def getNamedItem(self, name: str) -> Node | None: ... |
(same below)
| 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: ... |
There was a problem hiding this comment.
This explicitly returns None. It is similar in behavior to dict.get()
| def item(self, index: int) -> Node: ... | |
| def item(self, index: int) -> Node | None: ... |
|
Thanks for contributing! I'm closing this PR for now, because it still fails some tests and has unresolved review feedback after three months of inactivity. If you are still interested, please feel free to open a new PR (or ping us to reopen this one). |
|
Also thanks to @hamdanal for the useful review! |
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:
However, attempting to do the same for xml.dom.minicompat produces the following error:
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.