From 0d1f62bda6958002a925c73ac5ed8527921abb23 Mon Sep 17 00:00:00 2001 From: Anders Kaseorg Date: Mon, 5 Oct 2026 21:53:00 +0000 Subject: [PATCH 1/3] Fix type errors in dom and iterators With a complete _rustlib stub, mypy and pyright reported these errors: - _ConfigNode's invalid_ordering is a tuple of any length. - BaseNode._SUBCLASS_WRAP needs a ClassVar annotation. - TreeDom.__eq__ must accept any object, as object.__eq__ does. - create_element accepts a QualName name, as the Rust constructor does. - AttrsList's (key, value) pairs are typed as such, rather than as any tuple, which could have supplied the start argument when unpacked. - AttrsList.get's default of None conflicted with its type; give it overloads. _find_by_key's default argument was never passed. - AttrsList.extend and __setitem__ reassigned variables with values of different types. - The iterators' _BASECLASS is called with a raw node, not a BaseNode. - Traverse yields EdgeTraverse rather than nodes, so it shouldn't inherit the other iterators' __next__. mypy and pyright now report no errors in the package. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Anders Kaseorg --- python/markupever/dom.py | 63 ++++++++++++++++++++++------------ python/markupever/iterators.py | 14 ++++++-- 2 files changed, 53 insertions(+), 24 deletions(-) diff --git a/python/markupever/dom.py b/python/markupever/dom.py index 928ea0b..8305b1b 100644 --- a/python/markupever/dom.py +++ b/python/markupever/dom.py @@ -66,7 +66,7 @@ def __iter__(self) -> typing.Generator["BaseNode", typing.Any, None]: for rn in _rustlib.iter.Iterator(self._raw): yield BaseNode._wrap(rn) - def __eq__(self, val: "TreeDom") -> bool: + def __eq__(self, val: object) -> bool: if not isinstance(val, TreeDom): return False @@ -108,7 +108,7 @@ def __repr__(self): class _ConfigNode: __slots__ = ("basetype", "invalid_ordering") - def __init__(self, basetype: type | None, invalid_ordering: tuple[int]): + def __init__(self, basetype: type | None, invalid_ordering: tuple[int, ...]): self.basetype = basetype self.invalid_ordering = invalid_ordering @@ -143,7 +143,7 @@ class BaseNode: __slots__ = ("_raw",) _CONFIG: _ConfigNode = _ConfigNode(None, ()) - _SUBCLASS_WRAP = {} + _SUBCLASS_WRAP: typing.ClassVar[dict[type, type["BaseNode"]]] = {} def __init__(self, node: typing.Any): if self._CONFIG.basetype is not None and not isinstance( @@ -469,7 +469,7 @@ def create_text(self, content: str, *, ordering: int = Ordering.APPEND) -> "Text def create_element( self, - name: str, + name: str | _rustlib.QualName, attrs: ( typing.Sequence[tuple[_rustlib.QualName | str, str]] | dict[_rustlib.QualName | str, str] @@ -681,16 +681,15 @@ def insert(self, index: int, key: _rustlib.QualName | str, value: str): def _find_by_key( self, key: _rustlib.QualName | str, - default: _D = None, start: int = 0, - ) -> tuple[str | _D, int]: + ) -> tuple[str | None, int]: for index, item in itertools.islice(enumerate(self.__raw.items()), start, None): k, v = item if k == key: return v, index - return default, -1 + return None, -1 def _find_by_item( self, @@ -708,7 +707,7 @@ def _find_by_item( def index( self, - key: _rustlib.QualName | str | tuple, + key: _rustlib.QualName | str | tuple[_rustlib.QualName | str, str], start: int = 0, ) -> int: """ @@ -720,7 +719,7 @@ def index( Raises `ValueError` if no matching key or key-value pair is found. """ if isinstance(key, tuple): - index = self._find_by_item(*key, start=start) + index = self._find_by_item(key[0], key[1], start=start) else: _, index = self._find_by_key(key, start=start) @@ -729,12 +728,22 @@ def index( return index + @typing.overload + def get( + self, key: _rustlib.QualName | str, default: None = None, start: int = 0 + ) -> str | None: ... + + @typing.overload + def get( + self, key: _rustlib.QualName | str, default: _D, start: int = 0 + ) -> str | _D: ... + def get( self, key: _rustlib.QualName | str, - default: _D = None, + default: _D | None = None, start: int = 0, - ) -> str | _D: + ) -> str | _D | None: """ Retrieve the value associated with a given key in the attributes list. Returns the value associated with the key if found, otherwise the default value. @@ -768,7 +777,7 @@ def pop(self, index: int = -1) -> tuple[_rustlib.QualName, str]: def remove( self, - key: _rustlib.QualName | str | tuple, + key: _rustlib.QualName | str | tuple[_rustlib.QualName | str, str], start: int = 0, ) -> None: """ @@ -786,14 +795,23 @@ def reverse(self) -> None: """Reverses the order of elements in the list.""" self.__raw.reverse() - def extend(self, m: dict | typing.Iterable[tuple]): + def extend( + self, + m: dict[_rustlib.QualName | str, str] + | typing.Iterable[tuple[_rustlib.QualName | str, str]], + ): """ Extend the attributes list by appending key-value pairs from the iterable or dictionary. """ + items: typing.Iterable[tuple[_rustlib.QualName | str, str]] if isinstance(m, dict): - m = m.items() + # A dict is also an iterable of its keys, so type checkers can't tell from + # isinstance() which of the two types m has. + items = typing.cast("dict[_rustlib.QualName | str, str]", m).items() + else: + items = m - for key, val in m: + for key, val in items: self.__raw.push(key, val) def clear(self) -> None: @@ -820,12 +838,14 @@ def __iter__(self): """Returns a generator of attribute keys.""" return self.keys() - def __contains__(self, key: _rustlib.QualName | str | tuple) -> bool: + def __contains__( + self, key: _rustlib.QualName | str | tuple[_rustlib.QualName | str, str] + ) -> bool: """ Returns `True` if the list has the specified key, else `False`. """ if isinstance(key, tuple): - index = self._find_by_item(*key) + index = self._find_by_item(key[0], key[1]) else: _, index = self._find_by_key(key) @@ -862,14 +882,15 @@ def __setitem__( - val: A value to set, either as a string or a (key, value) tuple. """ if not isinstance(index, int): - if isinstance(val, str): - val = (index, val) + key, value = (index, val) if isinstance(val, str) else val _, index = self._find_by_key(index) if index == -1: - self.__raw.push(*val) + self.__raw.push(key, value) return + val = (key, value) + self.__raw.update_item(index, val[0], val[1]) @typing.overload @@ -997,7 +1018,7 @@ def create_text( def create_element( self, - name: str, + name: str | _rustlib.QualName, attrs: ( typing.Sequence[tuple[_rustlib.QualName | str, str]] | dict[_rustlib.QualName | str, str] diff --git a/python/markupever/iterators.py b/python/markupever/iterators.py index 84c6524..fe1018d 100644 --- a/python/markupever/iterators.py +++ b/python/markupever/iterators.py @@ -4,12 +4,13 @@ if typing.TYPE_CHECKING: # pragma: no cover from . import dom + from ._rustlib import _Node class _IteratorMetaClass: """Bridge _rustlib iterators to Python""" - _BASECLASS: typing.Callable[["dom.BaseNode"], typing.Iterable] + _BASECLASS: typing.Callable[["_Node"], typing.Iterator["_Node"]] __slots__ = ("_raw",) @@ -79,10 +80,17 @@ def __repr__(self): return f"EdgeTraverse[opened]({self.node})" -class Traverse(_IteratorMetaClass): +class Traverse: """Iterator which traverses a tree.""" - _BASECLASS = _rustlib.iter.Traverse + __slots__ = ("_raw",) + + def __init__(self, value: "dom.BaseNode"): + self._raw = iter(_rustlib.iter.Traverse(value._raw)) + + def __iter__(self): + """Returns `iter(self)`""" + return self def __next__(self) -> EdgeTraverse: from .dom import BaseNode From c35567207cf9aada6ca0863538d59ee5fcfd489e Mon Sep 17 00:00:00 2001 From: Anders Kaseorg Date: Mon, 5 Oct 2026 23:20:39 +0000 Subject: [PATCH 2/3] Make BaseNode generic over its raw node type BaseNode._raw was untyped, so everything read from the raw node was Any, and type checkers couldn't catch mistakes in using it. Make BaseNode generic over the type of its raw node, with each node type a subclass of BaseNode[the corresponding _rustlib type]. Make it covariant, so that attach() and the iterators can accept any node as a BaseNode[_Node], and make _raw a read-only property so that's sound. Add a Node type alias for any node, which is what the navigation properties, the iterators, and TreeDom.__iter__ return, so that callers can narrow it with isinstance() to use a node type's attributes. Replace the registry of node classes that BaseNode._wrap() looked up, whose types couldn't be checked, with isinstance() checks. The raw nodes don't support ordering comparisons, so remove BaseNode's, which compared the raw nodes. Without them, comparing nodes raises TypeError as before, and Comment and Text, which compare their content with strings, return NotImplemented rather than deferring to them. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Anders Kaseorg --- python/markupever/dom.py | 120 ++++++++++++++++----------------- python/markupever/iterators.py | 15 +++-- 2 files changed, 69 insertions(+), 66 deletions(-) diff --git a/python/markupever/dom.py b/python/markupever/dom.py index 8305b1b..09ecab1 100644 --- a/python/markupever/dom.py +++ b/python/markupever/dom.py @@ -4,6 +4,11 @@ from . import _rustlib, iterators from ._rustlib import QualName as QualName +if typing.TYPE_CHECKING: # pragma: no cover + from ._rustlib import _Node + +_R_co = typing.TypeVar("_R_co", bound="_Node", covariant=True) + class TreeDom: __slots__ = ("_raw",) @@ -61,7 +66,7 @@ def serialize( indent=indent, is_html=is_html, include_self=include_self ) # pragma: no cover - def __iter__(self) -> typing.Generator["BaseNode", typing.Any, None]: + def __iter__(self) -> typing.Iterator["Node"]: """Iterates the nodes in insert order - don't matter which are orphan which not.""" for rn in _rustlib.iter.Iterator(self._raw): yield BaseNode._wrap(rn) @@ -126,7 +131,7 @@ class Ordering: """Means create and insert the node as the `prev_sibling`.""" -class BaseNode: +class BaseNode(typing.Generic[_R_co]): """ Base class for DOM nodes, providing core tree navigation, manipulation, and serialization methods. @@ -140,12 +145,11 @@ class BaseNode: Subclasses should define their specific node type configuration via _CONFIG. """ - __slots__ = ("_raw",) + __slots__ = ("__raw",) _CONFIG: _ConfigNode = _ConfigNode(None, ()) - _SUBCLASS_WRAP: typing.ClassVar[dict[type, type["BaseNode"]]] = {} - def __init__(self, node: typing.Any): + def __init__(self, node: _R_co): if self._CONFIG.basetype is not None and not isinstance( node, self._CONFIG.basetype ): @@ -158,18 +162,32 @@ def __init__(self, node: typing.Any): f"expected one of _rustlib nodes implementations (such as _rustlib.Element, _rustlib.Comment, ...), got {type(node).__name__}" ) - self._raw = node + self.__raw = node + + @property + def _raw(self) -> _R_co: + # A read-only property, so that BaseNode can be covariant in _R_co. + return self.__raw @classmethod - def _wrap(cls, node: typing.Any) -> "BaseNode": - try: - _type = cls._SUBCLASS_WRAP[type(node)] - except KeyError: + def _wrap(cls, node: "_Node") -> "Node": + if isinstance(node, _rustlib.Element): + return Element(node) + if isinstance(node, _rustlib.Text): + return Text(node) + if isinstance(node, _rustlib.Comment): + return Comment(node) + if isinstance(node, _rustlib.Doctype): + return Doctype(node) + if isinstance(node, _rustlib.ProcessingInstruction): + return ProcessingInstruction(node) + # Checked last, because mypy doesn't narrow isinstance() with a class whose + # __new__ returns NoReturn. + if not isinstance(node, _rustlib.Document): raise TypeError( f"the type of node is not acceptable ({type(node).__name__})." - ) from None - - return _type(node) + ) + return Document(node) def _connect_node(self, ordering: int, dom, child): if ordering in self._CONFIG.invalid_ordering: @@ -192,36 +210,32 @@ def _connect_node(self, ordering: int, dom, child): "ordering must be one of Ordering variables like Ordering.APPEND, Ordering.PREPEND, ..." ) - def __init_subclass__(cls): - assert cls._CONFIG.basetype is not None - BaseNode._SUBCLASS_WRAP[cls._CONFIG.basetype] = cls - @property - def parent(self) -> typing.Optional["BaseNode"]: + def parent(self) -> typing.Optional["Node"]: """Returns the parent of this node.""" parent = self._raw.parent() return BaseNode._wrap(parent) if parent is not None else None @property - def prev_sibling(self) -> typing.Optional["BaseNode"]: + def prev_sibling(self) -> typing.Optional["Node"]: """Returns the previous sibling of this node.""" prev_sibling = self._raw.prev_sibling() return BaseNode._wrap(prev_sibling) if prev_sibling is not None else None @property - def next_sibling(self) -> typing.Optional["BaseNode"]: + def next_sibling(self) -> typing.Optional["Node"]: """Returns the next sibling of this node.""" next_sibling = self._raw.next_sibling() return BaseNode._wrap(next_sibling) if next_sibling is not None else None @property - def first_child(self) -> typing.Optional["BaseNode"]: + def first_child(self) -> typing.Optional["Node"]: """Returns the first child of this node.""" first_child = self._raw.first_child() return BaseNode._wrap(first_child) if first_child is not None else None @property - def last_child(self) -> typing.Optional["BaseNode"]: + def last_child(self) -> typing.Optional["Node"]: """Returns the last child of this node.""" last_child = self._raw.last_child() return BaseNode._wrap(last_child) if last_child is not None else None @@ -272,7 +286,9 @@ def descendants(self) -> iterators.Descendants: """Returns an iterator which iterates over this node and its descendants.""" return iterators.Descendants(self) - def attach(self, node: "BaseNode", *, ordering: int = Ordering.APPEND) -> None: + def attach( + self, node: "BaseNode[_Node]", *, ordering: int = Ordering.APPEND + ) -> None: """ Attaches a node to the current node with a specified ordering. @@ -398,35 +414,11 @@ def __ne__(self, value): # pragma: no cover return self._raw != value - def __le__(self, value): # pragma: no cover - if isinstance(value, BaseNode): - value = value._raw - - return self._raw <= value - - def __lt__(self, value): # pragma: no cover - if isinstance(value, BaseNode): - value = value._raw - - return self._raw < value - - def __ge__(self, value): # pragma: no cover - if isinstance(value, BaseNode): - value = value._raw - - return self._raw >= value - - def __gt__(self, value): # pragma: no cover - if isinstance(value, BaseNode): - value = value._raw - - return self._raw > value - def __repr__(self) -> str: # pragma: no cover return repr(self._raw) -class Document(BaseNode): +class Document(BaseNode[_rustlib.Document]): """The root of a document.""" _CONFIG = _ConfigNode(_rustlib.Document, (Ordering.AFTER, Ordering.BEFORE)) @@ -505,7 +497,7 @@ def create_processing_instruction( return ProcessingInstruction(node) -class Doctype(BaseNode): +class Doctype(BaseNode[_rustlib.Doctype]): """ the doctype is the required preamble found at the top of all documents. Its sole purpose is to prevent a browser from switching into so-called "quirks mode" @@ -541,7 +533,7 @@ def public_id(self, value: str) -> None: self._raw.public_id = value -class Comment(BaseNode): +class Comment(BaseNode[_rustlib.Comment]): """ The Comment interface represents textual notations within markup; although it is generally not visually shown, such comments are available to be read in the source view. @@ -576,28 +568,28 @@ def __le__(self, value): # pragma: no cover if isinstance(value, str): return self._raw.content <= value - return super().__le__(value) + return NotImplemented def __lt__(self, value): # pragma: no cover if isinstance(value, str): return self._raw.content < value - return super().__lt__(value) + return NotImplemented def __ge__(self, value): # pragma: no cover if isinstance(value, str): return self._raw.content >= value - return super().__ge__(value) + return NotImplemented def __gt__(self, value): # pragma: no cover if isinstance(value, str): return self._raw.content > value - return super().__gt__(value) + return NotImplemented -class Text(BaseNode): +class Text(BaseNode[_rustlib.Text]): """A text node.""" _CONFIG = _ConfigNode(_rustlib.Text, (Ordering.APPEND, Ordering.PREPEND)) @@ -626,25 +618,25 @@ def __le__(self, value): # pragma: no cover if isinstance(value, str): return self._raw.content <= value - return super().__le__(value) + return NotImplemented def __lt__(self, value): # pragma: no cover if isinstance(value, str): return self._raw.content < value - return super().__lt__(value) + return NotImplemented def __ge__(self, value): # pragma: no cover if isinstance(value, str): return self._raw.content >= value - return super().__ge__(value) + return NotImplemented def __gt__(self, value): # pragma: no cover if isinstance(value, str): return self._raw.content > value - return super().__gt__(value) + return NotImplemented _D = typing.TypeVar("_D") @@ -914,7 +906,7 @@ def __repr__(self) -> str: return repr(self.__raw) -class Element(BaseNode): +class Element(BaseNode[_rustlib.Element]): """An element node.""" _CONFIG = _ConfigNode(_rustlib.Element, ()) @@ -1056,7 +1048,7 @@ def create_processing_instruction( return ProcessingInstruction(node) -class ProcessingInstruction(BaseNode): +class ProcessingInstruction(BaseNode[_rustlib.ProcessingInstruction]): """ The ProcessingInstruction interface represents a processing instruction; that is, a Node which embeds an instruction targeting a specific application but that can @@ -1082,3 +1074,9 @@ def data(self) -> str: @data.setter def data(self, value: str) -> None: self._raw.data = value + + +Node: typing.TypeAlias = ( + Document | Doctype | Comment | Text | Element | ProcessingInstruction +) +"""Any node.""" diff --git a/python/markupever/iterators.py b/python/markupever/iterators.py index fe1018d..d8e11fb 100644 --- a/python/markupever/iterators.py +++ b/python/markupever/iterators.py @@ -14,14 +14,14 @@ class _IteratorMetaClass: __slots__ = ("_raw",) - def __init__(self, value: "dom.BaseNode"): + def __init__(self, value: "dom.BaseNode[_Node]"): self._raw = iter(self._BASECLASS(value._raw)) def __iter__(self): """Returns `iter(self)`""" return self - def __next__(self) -> "dom.BaseNode": + def __next__(self) -> "dom.Node": """Returns `next(self)`""" from .dom import BaseNode @@ -69,7 +69,7 @@ class EdgeTraverse: __slots__ = ("closed", "node") - def __init__(self, node: "dom.BaseNode", closed: bool) -> None: + def __init__(self, node: "dom.Node", closed: bool) -> None: self.node = node self.closed = closed @@ -85,7 +85,7 @@ class Traverse: __slots__ = ("_raw",) - def __init__(self, value: "dom.BaseNode"): + def __init__(self, value: "dom.BaseNode[_Node]"): self._raw = iter(_rustlib.iter.Traverse(value._raw)) def __iter__(self): @@ -111,7 +111,12 @@ class Select: __slots__ = ("__limit", "__offset", "__raw") def __init__( - self, value: "dom.BaseNode", expr: str, *, limit: int = 0, offset: int = 0 + self, + value: "dom.BaseNode[_Node]", + expr: str, + *, + limit: int = 0, + offset: int = 0, ) -> None: self.__raw = iter(_rustlib.Select(value._raw, expr)) From 3f4df5d88b5c0ed28cfe3ac735e11b7fb6e2c43c Mon Sep 17 00:00:00 2001 From: Anders Kaseorg Date: Mon, 5 Oct 2026 23:20:57 +0000 Subject: [PATCH 3/3] Annotate the rest of the package for mypy --strict Annotate the remaining functions, and QualName's constructor in the _rustlib stub, so that the package passes mypy --strict. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Anders Kaseorg --- python/markupever/_display.py | 6 ++-- python/markupever/_rustlib/__init__.pyi | 2 +- python/markupever/dom.py | 48 +++++++++++++------------ python/markupever/iterators.py | 8 ++--- 4 files changed, 34 insertions(+), 30 deletions(-) diff --git a/python/markupever/_display.py b/python/markupever/_display.py index 7ca6028..53778d8 100644 --- a/python/markupever/_display.py +++ b/python/markupever/_display.py @@ -12,7 +12,7 @@ def __init__(self, siblings: bool): self.siblings = siblings self.children = False - def __str__(self): + def __str__(self) -> str: # match (siblings, children) # (true, true) => "│ ", # (true, false) => "├── ", @@ -42,7 +42,7 @@ def __init__(self, ignore_root: bool): self.tokens: list[_DisplayCharacterToken] = [] self.ignore_root = ignore_root - def indent(self, siblings: bool): + def indent(self, siblings: bool) -> "_Indentation": length = len(self.tokens) if length > 0: self.tokens[length - 1].children = True @@ -50,7 +50,7 @@ def indent(self, siblings: bool): self.tokens.append(_DisplayCharacterToken(siblings)) return self - def deindent(self): + def deindent(self) -> "_Indentation": self.tokens.pop() return self diff --git a/python/markupever/_rustlib/__init__.pyi b/python/markupever/_rustlib/__init__.pyi index 26a8dbe..b9652aa 100644 --- a/python/markupever/_rustlib/__init__.pyi +++ b/python/markupever/_rustlib/__init__.pyi @@ -174,7 +174,7 @@ class QualName: "html", "xml", "xhtml", "xmlns", "xlink", "svg", "mathml", "*" ] = ..., prefix: str | None = ..., - ): ... + ) -> QualName: ... @property def local(self) -> str: """The local name (e.g. `table` in `` above).""" diff --git a/python/markupever/dom.py b/python/markupever/dom.py index 09ecab1..233a0f1 100644 --- a/python/markupever/dom.py +++ b/python/markupever/dom.py @@ -106,7 +106,7 @@ def __str__(self) -> str: return res[:-1] # remove the last '\n' - def __repr__(self): + def __repr__(self) -> str: return f"TreeDom(len={len(self)}, namespaces={self.namespaces()})" @@ -189,7 +189,9 @@ def _wrap(cls, node: "_Node") -> "Node": ) return Document(node) - def _connect_node(self, ordering: int, dom, child): + def _connect_node( + self, ordering: int, dom: _rustlib.TreeDom, child: "_Node" + ) -> None: if ordering in self._CONFIG.invalid_ordering: raise ValueError("This ordering value is not acceptable for this type.") @@ -346,7 +348,7 @@ def select_one(self, expr: str, offset: int = 0) -> typing.Optional["Element"]: else: return node - def strings(self, strip: bool = False): + def strings(self, strip: bool = False) -> typing.Iterator[str]: """ Retrieve text content from descendant text nodes. @@ -402,13 +404,13 @@ def serialize( indent, is_html=is_html, include_self=include_self ).decode("utf-8") - def __eq__(self, value): + def __eq__(self, value: object) -> bool: if isinstance(value, BaseNode): value = value._raw return self._raw == value - def __ne__(self, value): # pragma: no cover + def __ne__(self, value: object) -> bool: # pragma: no cover if isinstance(value, BaseNode): value = value._raw @@ -552,37 +554,37 @@ def content(self) -> str: def content(self, value: str) -> None: self._raw.content = value - def __eq__(self, value): + def __eq__(self, value: object) -> bool: if isinstance(value, str): return self._raw.content == value return super().__eq__(value) - def __ne__(self, value): # pragma: no cover + def __ne__(self, value: object) -> bool: # pragma: no cover if isinstance(value, str): return self._raw.content != value return super().__ne__(value) - def __le__(self, value): # pragma: no cover + def __le__(self, value: object) -> bool: # pragma: no cover if isinstance(value, str): return self._raw.content <= value return NotImplemented - def __lt__(self, value): # pragma: no cover + def __lt__(self, value: object) -> bool: # pragma: no cover if isinstance(value, str): return self._raw.content < value return NotImplemented - def __ge__(self, value): # pragma: no cover + def __ge__(self, value: object) -> bool: # pragma: no cover if isinstance(value, str): return self._raw.content >= value return NotImplemented - def __gt__(self, value): # pragma: no cover + def __gt__(self, value: object) -> bool: # pragma: no cover if isinstance(value, str): return self._raw.content > value @@ -602,37 +604,37 @@ def content(self) -> str: def content(self, value: str) -> None: self._raw.content = value - def __eq__(self, value): + def __eq__(self, value: object) -> bool: if isinstance(value, str): return self._raw.content == value return super().__eq__(value) - def __ne__(self, value): # pragma: no cover + def __ne__(self, value: object) -> bool: # pragma: no cover if isinstance(value, str): return self._raw.content != value return super().__ne__(value) - def __le__(self, value): # pragma: no cover + def __le__(self, value: object) -> bool: # pragma: no cover if isinstance(value, str): return self._raw.content <= value return NotImplemented - def __lt__(self, value): # pragma: no cover + def __lt__(self, value: object) -> bool: # pragma: no cover if isinstance(value, str): return self._raw.content < value return NotImplemented - def __ge__(self, value): # pragma: no cover + def __ge__(self, value: object) -> bool: # pragma: no cover if isinstance(value, str): return self._raw.content >= value return NotImplemented - def __gt__(self, value): # pragma: no cover + def __gt__(self, value: object) -> bool: # pragma: no cover if isinstance(value, str): return self._raw.content > value @@ -658,13 +660,13 @@ class AttrsList: def __init__(self, attrs: _rustlib.AttrsList): self.__raw = attrs - def append(self, key: _rustlib.QualName | str, value: str): + def append(self, key: _rustlib.QualName | str, value: str) -> None: """ Appends a key-value pair into attributes list. """ self.__raw.push(key, value) - def insert(self, index: int, key: _rustlib.QualName | str, value: str): + def insert(self, index: int, key: _rustlib.QualName | str, value: str) -> None: """ Inserts a key-value pair at position `index` within the list, shifting all elements after it to the right. """ @@ -791,7 +793,7 @@ def extend( self, m: dict[_rustlib.QualName | str, str] | typing.Iterable[tuple[_rustlib.QualName | str, str]], - ): + ) -> None: """ Extend the attributes list by appending key-value pairs from the iterable or dictionary. """ @@ -826,7 +828,7 @@ def __len__(self) -> int: """Returns `len(self)`.""" return len(self.__raw) - def __iter__(self): + def __iter__(self) -> typing.Iterator[QualName]: """Returns a generator of attribute keys.""" return self.keys() @@ -891,7 +893,9 @@ def __getitem__(self, index: str | _rustlib.QualName) -> str: ... @typing.overload def __getitem__(self, index: int) -> tuple[_rustlib.QualName, str]: ... - def __getitem__(self, index): + def __getitem__( + self, index: int | str | _rustlib.QualName + ) -> str | tuple[_rustlib.QualName, str]: if not isinstance(index, int): _, index_i = self._find_by_key(index) if index_i == -1: diff --git a/python/markupever/iterators.py b/python/markupever/iterators.py index d8e11fb..6c88d92 100644 --- a/python/markupever/iterators.py +++ b/python/markupever/iterators.py @@ -17,7 +17,7 @@ class _IteratorMetaClass: def __init__(self, value: "dom.BaseNode[_Node]"): self._raw = iter(self._BASECLASS(value._raw)) - def __iter__(self): + def __iter__(self) -> typing.Iterator["dom.Node"]: """Returns `iter(self)`""" return self @@ -73,7 +73,7 @@ def __init__(self, node: "dom.Node", closed: bool) -> None: self.node = node self.closed = closed - def __repr__(self): + def __repr__(self) -> str: if self.closed: return f"EdgeTraverse[closed]({self.node})" @@ -88,7 +88,7 @@ class Traverse: def __init__(self, value: "dom.BaseNode[_Node]"): self._raw = iter(_rustlib.iter.Traverse(value._raw)) - def __iter__(self): + def __iter__(self) -> typing.Iterator[EdgeTraverse]: """Returns `iter(self)`""" return self @@ -123,7 +123,7 @@ def __init__( self.__limit = limit or -1 self.__offset = offset - 1 - def __iter__(self): + def __iter__(self) -> typing.Iterator["dom.Element"]: return self def __next__(self) -> "dom.Element":