SF.net SVN: docutils:[9813 ] trunk/docutils/docutils/n odes.py

aa-turner--- via Docutils-checkins <[email protected]>
Newsgroups gmane.text.docutils.cvs
Message-ID <[email protected]>
Revision: 9813
          http://sourceforge.net/p/docutils/code/9813
Author:   aa-turner
Date:     2024-08-01 13:57:33 +0000 (Thu, 01 Aug 2024)
Log Message:
-----------
Add type hints to ``docutils.nodes``

A significant number of parameters and attributes are typed as
expecting or yielding ``Element | Text`` rather than ``Node``,
which is the more obvious choice. This union type is used because
``Node`` is de facto an abstract base class --- it should not
be used or instantiated. Notably, ``Node.children`` is not defined,
``Node.attributes`` does not exist, etc. The two direct subclasses
fill in many of these missing pieces, making static typing both
more precise and easier.

Modified Paths:
--------------
    trunk/docutils/docutils/nodes.py

Modified: trunk/docutils/docutils/nodes.py
===================================================================
--- trunk/docutils/docutils/nodes.py	2024-08-01 10:46:38 UTC (rev 9812)
+++ trunk/docutils/docutils/nodes.py	2024-08-01 13:57:33 UTC (rev 9813)
@@ -20,13 +20,53 @@
 .. _DTD: https://docutils.sourceforge.io/docs/ref/docutils.dtd
 """
 
+from __future__ import annotations
+
 __docformat__ = 'reStructuredText'
 
-from collections import Counter
+import os
 import re
 import sys
 import unicodedata
 import warnings
+from collections import Counter
+from typing import TYPE_CHECKING, overload
+
+if TYPE_CHECKING:
+    from collections.abc import (
+        Callable,
+        Iterable,
+        Iterator,
+        Literal,
+        Mapping,
+        Sequence,
+    )
+    from typing import (
+        Any,
+        ClassVar,
+        Final,
+        Self,
+        SupportsIndex,
+        TypeAlias,
+        TypeVar,
+    )
+    from xml.dom import minidom
+
+    from docutils.frontend import Values
+    from docutils.transforms import Transformer, Transform
+    from docutils.utils import Reporter
+
+    _NodeT = TypeVar('_NodeT', bound='Node')
+    _DefaultT = TypeVar('_DefaultT')
+
+    _ContentModelCategory: TypeAlias = tuple['Element' | tuple['Element', ...]]
+    _ContentModelQuantifier = Literal['.', '?', '+', '*']
+    _ContentModelItem: TypeAlias = tuple[
+        _ContentModelCategory, _ContentModelQuantifier,
+    ]
+    _ContentModelTuple: TypeAlias = tuple[_ContentModelItem, ...]
+
+
 # import xml.dom.minidom as dom # -> conditional import in Node.asdom()
 #                                    and document.asdom()
 
@@ -40,19 +80,25 @@
 class Node:
     """Abstract base class of nodes in a document tree."""
 
-    parent = None
+    parent: Element = None
     """Back-reference to the Node immediately containing this Node."""
 
-    source = None
+    children: Sequence[Element | Text]  # defined in subclasses
+    """List of child nodes (Elements or Text)."""
+
+    source: str | os.PathLike[str] | None = None
     """Path or description of the input source which generated this Node."""
 
-    line = None
+    line: int | None = None
     """The line number (1-based) of the beginning of this Node in `source`."""
 
-    _document = None
+    tagname: str  # defined in subclasses
+    """The element generic identifier."""
 
+    _document: document | None = None
+
     @property
-    def document(self):
+    def document(self) -> document | None:
         """Return the `document` root node of the tree containing this Node.
         """
         try:
@@ -61,10 +107,10 @@
             return None
 
     @document.setter
-    def document(self, value) -> None:
+    def document(self, value: document) -> None:
         self._document = value
 
-    def __bool__(self) -> bool:
+    def __bool__(self) -> Literal[True]:
         """
         Node instances are always true, even if they're empty.  A node is more
         than a simple container.  Its boolean "truth" does not depend on
@@ -74,7 +120,9 @@
         """
         return True
 
-    def asdom(self, dom=None):
+    def asdom(
+        self, dom: None = None,
+    ) -> minidom.Document | minidom.Element | minidom.Text:
         """Return a DOM **fragment** representation of this Node."""
         if dom is None:
             import xml.dom.minidom as dom
@@ -81,7 +129,7 @@
         domroot = dom.Document()
         return self._dom_node(domroot)
 
-    def pformat(self, indent='    ', level=0):
+    def pformat(self, indent: str = '    ', level: int = 0) -> str:
         """
         Return an indented pseudo-XML representation, for test purposes.
 
@@ -89,19 +137,19 @@
         """
         raise NotImplementedError
 
-    def copy(self):
+    def copy(self) -> Self:
         """Return a copy of self."""
         raise NotImplementedError
 
-    def deepcopy(self):
+    def deepcopy(self) -> Self:
         """Return a deep copy of self (also copying children)."""
         raise NotImplementedError
 
-    def astext(self):
+    def astext(self) -> str:
         """Return a string representation of this Node."""
         raise NotImplementedError
 
-    def setup_child(self, child) -> None:
+    def setup_child(self, child: Element | Text) -> None:
         child.parent = self
         if self.document:
             child.document = self.document
@@ -110,7 +158,7 @@
             if child.line is None:
                 child.line = self.document.current_line
 
-    def walk(self, visitor):
+    def walk(self, visitor: NodeVisitor) -> bool:
         """
         Traverse a tree of `Node` objects, calling the
         `dispatch_visit()` method of `visitor` when entering each
@@ -156,7 +204,7 @@
             stop = True
         return stop
 
-    def walkabout(self, visitor):
+    def walkabout(self, visitor: NodeVisitor) -> bool:
         """
         Perform a tree traversal similarly to `Node.walk()` (which
         see), except also call the `dispatch_departure()` method
@@ -199,7 +247,7 @@
             visitor.dispatch_departure(self)
         return stop
 
-    def _fast_findall(self, cls):
+    def _fast_findall(self, cls: type[_NodeT]) -> Iterator[_NodeT]:
         """Return iterator that only supports instance checks."""
         if isinstance(self, cls):
             yield self
@@ -206,7 +254,7 @@
         for child in self.children:
             yield from child._fast_findall(cls)
 
-    def _superfast_findall(self):
+    def _superfast_findall(self) -> Iterator[Element | Text]:
         """Return iterator that doesn't check for a condition."""
         # This is different from ``iter(self)`` implemented via
         # __getitem__() and __len__() in the Element subclass,
@@ -215,8 +263,14 @@
         for child in self.children:
             yield from child._superfast_findall()
 
-    def findall(self, condition=None, include_self=True, descend=True,
-                siblings=False, ascend=False):
+    def findall(
+        self,
+        condition: type[_NodeT] | Callable[[Node], bool] | None = None,
+        include_self: bool = True,
+        descend: bool = True,
+        siblings: bool = False,
+        ascend: bool = False,
+    ) -> Iterator[Element | Text]:
         """
         Return an iterator yielding nodes following `self`:
 
@@ -298,8 +352,14 @@
                 else:
                     node = node.parent
 
-    def traverse(self, condition=None, include_self=True, descend=True,
-                 siblings=False, ascend=False):
+    def traverse(
+        self,
+        condition: type[_NodeT] | Callable[[Node], bool] | None = None,
+        include_self: bool = True,
+        descend: bool = True,
+        siblings: bool = False,
+        ascend: bool = False,
+    ) -> list[Element | Text]:
         """Return list of nodes following `self`.
 
         For looping, Node.findall() is faster and more memory efficient.
@@ -310,8 +370,14 @@
         return list(self.findall(condition, include_self, descend,
                                  siblings, ascend))
 
-    def next_node(self, condition=None, include_self=False, descend=True,
-                  siblings=False, ascend=False):
+    def next_node(
+        self,
+        condition: type[_NodeT] | Callable[[Node], bool] | None = None,
+        include_self: bool = False,
+        descend: bool = True,
+        siblings: bool = False,
+        ascend: bool = False,
+    ) -> Element | Text | None:
         """
         Return the first node in the iterator returned by findall(),
         or None if the iterable is empty.
@@ -335,12 +401,12 @@
     and unescaped text with ``<instance>.astext()``.
     """
 
-    tagname = '#text'
+    tagname: Final = '#text'
 
-    children = ()
+    children: Final = ()
     """Text nodes have no children, and cannot have children."""
 
-    def __new__(cls, data, rawsource=None):
+    def __new__(cls, data: str, rawsource: None = None) -> Self:
         """Assert that `data` is not an array of bytes
         and warn if the deprecated `rawsource` argument is used.
         """
@@ -352,7 +418,7 @@
                           DeprecationWarning, stacklevel=2)
         return str.__new__(cls, data)
 
-    def shortrepr(self, maxlen=18) -> str:
+    def shortrepr(self, maxlen: int = 18) -> str:
         data = self
         if len(data) > maxlen:
             data = data[:maxlen-4] + ' ...'
@@ -361,19 +427,19 @@
     def __repr__(self) -> str:
         return self.shortrepr(maxlen=68)
 
-    def astext(self):
+    def astext(self) -> str:
         return str(unescape(self))
 
-    def _dom_node(self, domroot):
+    def _dom_node(self, domroot: minidom.Document) -> minidom.Text:
         return domroot.createTextNode(str(self))
 
-    def copy(self):
+    def copy(self) -> Self:
         return self.__class__(str(self))
 
-    def deepcopy(self):
+    def deepcopy(self) -> Self:
         return self.copy()
 
-    def pformat(self, indent='    ', level=0):
+    def pformat(self, indent: str = '    ', level: int = 0) -> str:
         try:
             if self.document.settings.detailed:
                 tag = '%s%s' % (indent*level, '<#text>')
@@ -392,13 +458,13 @@
     # they are expected to return a Text instance, this was formerly
     # taken care of by UserString.
 
-    def rstrip(self, chars=None):
+    def rstrip(self, chars: str | None = None) -> Self:
         return self.__class__(str.rstrip(self, chars))
 
-    def lstrip(self, chars=None):
+    def lstrip(self, chars: str | None = None) -> Self:
         return self.__class__(str.lstrip(self, chars))
 
-    def validate(self, recursive=True) -> None:
+    def validate(self, recursive: bool = True) -> None:
         """Validate Docutils Document Tree element ("doctree")."""
         # Text nodes have no attributes and no children.
 
@@ -455,7 +521,7 @@
     This is equivalent to ``element.extend([node1, node2])``.
     """
 
-    list_attributes = ('ids', 'classes', 'names', 'dupnames')
+    list_attributes: Final = ('ids', 'classes', 'names', 'dupnames')
     """Tuple of attributes that are initialized to empty lists.
 
     NOTE: Derived classes should update this value when supporting
@@ -462,7 +528,7 @@
           additional list attributes.
     """
 
-    valid_attributes = list_attributes + ('source',)
+    valid_attributes: Final = list_attributes + ('source',)
     """Tuple of attributes that are valid for elements of this class.
 
     NOTE: Derived classes should update this value when supporting
@@ -469,22 +535,22 @@
           additional attributes.
     """
 
-    common_attributes = valid_attributes
+    common_attributes: Final = valid_attributes
     """Tuple of `common attributes`__  known to all Doctree Element classes.
 
     __ https://docutils.sourceforge.io/docs/ref/doctree.html#common-attributes
     """
 
-    known_attributes = common_attributes
+    known_attributes: Final = common_attributes
     """Alias for `common_attributes`. Will be removed in Docutils 2.0."""
 
-    basic_attributes = list_attributes
+    basic_attributes: Final = list_attributes
     """Common list attributes. Deprecated. Will be removed in Docutils 2.0."""
 
-    local_attributes = ('backrefs',)
+    local_attributes: Final = ('backrefs',)
     """Obsolete. Will be removed in Docutils 2.0."""
 
-    content_model = tuple()
+    content_model: ClassVar[_ContentModelTuple] = ()
     """Python representation of the element's content model (cf. docutils.dtd).
 
     A tuple of ``(category, quantifier)`` tuples with
@@ -502,16 +568,21 @@
     Provisional.
     """
 
-    tagname = None
+    tagname: str | None = None
     """The element generic identifier.
 
     If None, it is set as an instance attribute to the name of the class.
     """
 
-    child_text_separator = '\n\n'
+    child_text_separator: Final = '\n\n'
     """Separator for child nodes, used by `astext()` method."""
 
-    def __init__(self, rawsource='', *children, **attributes) -> None:
+    def __init__(
+        self,
+        rawsource: str = '',
+        *children: Element | Text,
+        **attributes: Any,
+    ) -> None:
         self.rawsource = rawsource
         """The raw text from which this element was constructed.
 
@@ -522,12 +593,12 @@
         if isinstance(rawsource, Element):
             raise TypeError('First argument "rawsource" must be a string.')
 
-        self.children = []
+        self.children: list[Element | Text] = []
         """List of child nodes (elements and/or `Text`)."""
 
         self.extend(children)           # maintain parent info
 
-        self.attributes = {}
+        self.attributes: dict[str, Any] = {}
         """Dictionary of attribute {name: value}."""
 
         # Initialize list attributes.
@@ -543,9 +614,9 @@
                 self.attributes[att] = value
 
         if self.tagname is None:
-            self.tagname = self.__class__.__name__
+            self.tagname: str = self.__class__.__name__
 
-    def _dom_node(self, domroot):
+    def _dom_node(self, domroot: minidom.Document) -> minidom.Element:
         element = domroot.createElement(self.tagname)
         for attribute, value in self.attlist():
             if isinstance(value, list):
@@ -583,7 +654,7 @@
         else:
             return self.emptytag()
 
-    def starttag(self, quoteattr=None) -> str:
+    def starttag(self, quoteattr: Callable[[str], str] | None = None) -> str:
         # the optional arg is used by the docutils_xml writer
         if quoteattr is None:
             quoteattr = pseudo_quoteattr
@@ -613,13 +684,27 @@
     def __len__(self) -> int:
         return len(self.children)
 
-    def __contains__(self, key) -> bool:
+    def __contains__(self, key: str | Element | Text) -> bool:
         # Test for both, children and attributes with operator ``in``.
         if isinstance(key, str):
             return key in self.attributes
         return key in self.children
 
-    def __getitem__(self, key):
+    @overload
+    def __getitem__(self, key: str) -> Any:
+        ...
+
+    @overload
+    def __getitem__(self, key: int) -> Element | Text:
+        ...
+
+    @overload
+    def __getitem__(self, key: slice) -> list[Element | Text]:
+        ...
+
+    def __getitem__(
+        self, key: str | int | slice,
+    ) -> Element | Text | list[Element | Text] | Any:
         if isinstance(key, str):
             return self.attributes[key]
         elif isinstance(key, int):
@@ -631,6 +716,18 @@
             raise TypeError('element index must be an integer, a slice, or '
                             'an attribute name string')
 
+    @overload
+    def __setitem__(self, key: str, item: Any) -> None:
+        ...
+
+    @overload
+    def __setitem__(self, key: int, item: Element | Text) -> None:
+        ...
+
+    @overload
+    def __setitem__(self, key: slice, item: Iterable[Element | Text]) -> None:
+        ...
+
     def __setitem__(self, key, item) -> None:
         if isinstance(key, str):
             self.attributes[str(key)] = item
@@ -646,7 +743,7 @@
             raise TypeError('element index must be an integer, a slice, or '
                             'an attribute name string')
 
-    def __delitem__(self, key) -> None:
+    def __delitem__(self, key: str | int | slice) -> None:
         if isinstance(key, str):
             del self.attributes[key]
         elif isinstance(key, int):
@@ -658,13 +755,15 @@
             raise TypeError('element index must be an integer, a simple '
                             'slice, or an attribute name string')
 
-    def __add__(self, other):
+    def __add__(self, other: list[Element | Text]) -> list[Element | Text]:
         return self.children + other
 
-    def __radd__(self, other):
+    def __radd__(self, other: list[Element | Text]) -> list[Element | Text]:
         return other + self.children
 
-    def __iadd__(self, other):
+    def __iadd__(
+        self, other: Element | Text | Iterable[Element | Text],
+    ) -> Self:
         """Append a node or a list of nodes to `self.children`."""
         if isinstance(other, Node):
             self.append(other)
@@ -672,11 +771,11 @@
             self.extend(other)
         return self
 
-    def astext(self):
+    def astext(self) -> str:
         return self.child_text_separator.join(
                    [child.astext() for child in self.children])
 
-    def non_default_attributes(self):
+    def non_default_attributes(self) -> dict[str, Any]:
         atts = {}
         for key, value in self.attributes.items():
             if self.is_not_default(key):
@@ -683,25 +782,41 @@
                 atts[key] = value
         return atts
 
-    def attlist(self):
+    def attlist(self) -> list[tuple[str, Any]]:
         return sorted(self.non_default_attributes().items())
 
-    def get(self, key, failobj=None):
+    @overload
+    def get(self, key: str) -> Any:
+        ...
+
+    @overload
+    def get(self, key: str, failobj: _DefaultT) -> Any | _DefaultT:
+        ...
+
+    def get(self, key: str, failobj: Any | None = None) -> Any:
         return self.attributes.get(key, failobj)
 
-    def hasattr(self, attr) -> bool:
+    def hasattr(self, attr: str) -> bool:
         return attr in self.attributes
 
-    def delattr(self, attr) -> None:
+    def delattr(self, attr: str) -> None:
         if attr in self.attributes:
             del self.attributes[attr]
 
-    def setdefault(self, key, failobj=None):
+    @overload
+    def setdefault(self, key: str) -> Any:
+        ...
+
+    @overload
+    def setdefault(self, key: str, failobj: _DefaultT) -> Any | _DefaultT:
+        ...
+
+    def setdefault(self, key: str, failobj: Any | None = None) -> Any:
         return self.attributes.setdefault(key, failobj)
 
     has_key = hasattr
 
-    def get_language_code(self, fallback=''):
+    def get_language_code(self, fallback: str = '') -> str:
         """Return node's language tag.
 
         Look iteratively in self and parents for a class argument
@@ -716,15 +831,19 @@
         except AttributeError:
             return fallback
 
-    def append(self, item) -> None:
+    def append(self, item: Element | Text) -> None:
         self.setup_child(item)
         self.children.append(item)
 
-    def extend(self, item) -> None:
+    def extend(self, item: Iterable[Element | Text]) -> None:
         for node in item:
             self.append(node)
 
-    def insert(self, index, item) -> None:
+    def insert(
+        self,
+        index: SupportsIndex,
+        item: Element | Text | Iterable[Element | Text],
+    ) -> None:
         if isinstance(item, Node):
             self.setup_child(item)
             self.children.insert(index, item)
@@ -731,16 +850,18 @@
         elif item is not None:
             self[index:index] = item
 
-    def pop(self, i=-1):
+    def pop(self, i: int = -1) -> Element | Text:
         return self.children.pop(i)
 
-    def remove(self, item) -> None:
+    def remove(self, item: Element | Text) -> None:
         self.children.remove(item)
 
-    def index(self, item, start=0, stop=sys.maxsize):
+    def index(
+        self, item: Element | Text, start: int = 0, stop: int = sys.maxsize,
+    ) -> int:
         return self.children.index(item, start, stop)
 
-    def previous_sibling(self):
+    def previous_sibling(self) -> Element | Text | None:
         """Return preceding sibling node or ``None``."""
         try:
             i = self.parent.index(self)
@@ -748,13 +869,13 @@
             return None
         return self.parent[i-1] if i > 0 else None
 
-    def is_not_default(self, key) -> int:
+    def is_not_default(self, key: str) -> bool:
         if self[key] == [] and key in self.list_attributes:
-            return 0
+            return False
         else:
-            return 1
+            return True
 
-    def update_basic_atts(self, dict_) -> None:
+    def update_basic_atts(self, dict_: Mapping[str, Any] | Element) -> None:
         """
         Update basic attributes ('ids', 'names', 'classes',
         'dupnames', but not 'source') from node or dictionary `dict_`.
@@ -766,7 +887,7 @@
         for att in self.basic_attributes:
             self.append_attr_list(att, dict_.get(att, []))
 
-    def append_attr_list(self, attr, values) -> None:
+    def append_attr_list(self, attr: str, values: Iterable[Any]) -> None:
         """
         For each element in values, if it does not exist in self[attr], append
         it.
@@ -779,7 +900,9 @@
             if value not in self[attr]:
                 self[attr].append(value)
 
-    def coerce_append_attr_list(self, attr, value) -> None:
+    def coerce_append_attr_list(
+        self, attr: str, value: list[Any] | Any,
+    ) -> None:
         """
         First, convert both self[attr] and value to a non-string sequence
         type; if either is not already a sequence, convert it to a list of one
@@ -794,7 +917,7 @@
             value = [value]
         self.append_attr_list(attr, value)
 
-    def replace_attr(self, attr, value, force=True) -> None:
+    def replace_attr(self, attr: str, value: Any, force: bool = True) -> None:
         """
         If self[attr] does not exist or force is True or omitted, set
         self[attr] to value, otherwise do nothing.
@@ -803,7 +926,9 @@
         if force or self.get(attr) is None:
             self[attr] = value
 
-    def copy_attr_convert(self, attr, value, replace=True) -> None:
+    def copy_attr_convert(
+        self, attr: str, value: Any, replace: bool = True,
+    ) -> None:
         """
         If attr is an attribute of self, set self[attr] to
         [self[attr], value], otherwise set self[attr] to value.
@@ -814,7 +939,7 @@
         if self.get(attr) is not value:
             self.coerce_append_attr_list(attr, value)
 
-    def copy_attr_coerce(self, attr, value, replace) -> None:
+    def copy_attr_coerce(self, attr: str, value: Any, replace: bool) -> None:
         """
         If attr is an attribute of self and either self[attr] or value is a
         list, convert all non-sequence values to a sequence of 1 element and
@@ -830,7 +955,9 @@
             else:
                 self.replace_attr(attr, value, replace)
 
-    def copy_attr_concatenate(self, attr, value, replace) -> None:
+    def copy_attr_concatenate(
+        self, attr: str, value: Any, replace: bool,
+    ) -> None:
         """
         If attr is an attribute of self and both self[attr] and value are
         lists, concatenate the two sequences, setting the result to
@@ -845,7 +972,9 @@
             else:
                 self.replace_attr(attr, value, replace)
 
-    def copy_attr_consistent(self, attr, value, replace) -> None:
+    def copy_attr_consistent(
+        self, attr: str, value: Any, replace: bool,
+    ) -> None:
         """
         If replace is True or self[attr] is None, replace self[attr] with
         value.  Otherwise, do nothing.
@@ -853,8 +982,13 @@
         if self.get(attr) is not value:
             self.replace_attr(attr, value, replace)
 
-    def update_all_atts(self, dict_, update_fun=copy_attr_consistent,
-                        replace=True, and_source=False) -> None:
+    def update_all_atts(
+        self,
+        dict_: Mapping[str, Any] | Element,
+        update_fun: Callable[[str, Any, bool], None] = copy_attr_consistent,
+        replace: bool = True,
+        and_source: bool = False,
+    ) -> None:
         """
         Updates all attributes from node or dictionary `dict_`.
 
@@ -891,8 +1025,12 @@
         for att in filter(filter_fun, dict_):
             update_fun(self, att, dict_[att], replace)
 
-    def update_all_atts_consistantly(self, dict_, replace=True,
-                                     and_source=False) -> None:
+    def update_all_atts_consistantly(
+        self,
+        dict_: Mapping[str, Any] | Element,
+        replace: bool = True,
+        and_source: bool = False,
+    ) -> None:
         """
         Updates all attributes from node or dictionary `dict_`.
 
@@ -912,8 +1050,12 @@
         self.update_all_atts(dict_, Element.copy_attr_consistent, replace,
                              and_source)
 
-    def update_all_atts_concatenating(self, dict_, replace=True,
-                                      and_source=False) -> None:
+    def update_all_atts_concatenating(
+        self,
+        dict_: Mapping[str, Any] | Element,
+        replace: bool = True,
+        and_source: bool = False,
+    ) -> None:
         """
         Updates all attributes from node or dictionary `dict_`.
 
@@ -936,8 +1078,12 @@
         self.update_all_atts(dict_, Element.copy_attr_concatenate, replace,
                              and_source)
 
-    def update_all_atts_coercion(self, dict_, replace=True,
-                                 and_source=False) -> None:
+    def update_all_atts_coercion(
+        self,
+        dict_: Mapping[str, Any] | Element,
+        replace: bool = True,
+        and_source: bool = False,
+    ) -> None:
         """
         Updates all attributes from node or dictionary `dict_`.
 
@@ -961,7 +1107,11 @@
         self.update_all_atts(dict_, Element.copy_attr_coerce, replace,
                              and_source)
 
-    def update_all_atts_convert(self, dict_, and_source=False) -> None:
+    def update_all_atts_convert(
+        self,
+        dict_: Mapping[str, Any] | Element,
+        and_source: bool = False,
+    ) -> None:
         """
         Updates all attributes from node or dictionary `dict_`.
 
@@ -985,7 +1135,11 @@
     def clear(self) -> None:
         self.children = []
 
-    def replace(self, old, new) -> None:
+    def replace(
+        self,
+        old: Element | Text,
+        new: Element | Text | Iterable[Element | Text],
+    ) -> None:
         """Replace one child `Node` with another child or children."""
         index = self.index(old)
         if isinstance(new, Node):
@@ -994,7 +1148,9 @@
         elif new is not None:
             self[index:index+1] = new
 
-    def replace_self(self, new) -> None:
+    def replace_self(
+        self, new: Element | Text | Sequence[Element | Text],
+    ) -> None:
         """
         Replace `self` node with `new`, where `new` is a node or a
         list of nodes.
@@ -1018,7 +1174,13 @@
                        'Losing "%s" attribute: %s' % (att, self[att])
         self.parent.replace(self, new)
 
-    def first_child_matching_class(self, childclass, start=0, end=sys.maxsize):
+    def first_child_matching_class(
+        self,
+        childclass: type[Element] | type[Text]
+        | tuple[type[Element] | type[Text], ...],
+        start: int = 0,
+        end: int = sys.maxsize,
+    ) -> int | None:
         """
         Return the index of the first child whose class exactly matches.
 
@@ -1037,8 +1199,13 @@
                     return index
         return None
 
-    def first_child_not_matching_class(self, childclass, start=0,
-                                       end=sys.maxsize):
+    def first_child_not_matching_class(
+        self,
+        childclass: type[Element] | type[Text]
+        | tuple[type[Element] | type[Text], ...],
+        start: int = 0,
+        end: int = sys.maxsize,
+    ) -> int | None:
         """
         Return the index of the first child whose class does *not* match.
 
@@ -1059,12 +1226,12 @@
                 return index
         return None
 
-    def pformat(self, indent='    ', level=0):
+    def pformat(self, indent: str = '    ', level: int = 0) -> str:
         tagline = '%s%s\n' % (indent*level, self.starttag())
         childreps = (c.pformat(indent, level+1) for c in self.children)
         return ''.join((tagline, *childreps))
 
-    def copy(self):
+    def copy(self) -> Self:
         obj = self.__class__(rawsource=self.rawsource, **self.attributes)
         obj._document = self._document
         obj.source = self.source
@@ -1071,12 +1238,14 @@
         obj.line = self.line
         return obj
 
-    def deepcopy(self):
+    def deepcopy(self) -> Self:
         copy = self.copy()
         copy.extend([child.deepcopy() for child in self.children])
         return copy
 
-    def note_referenced_by(self, name=None, id=None) -> None:
+    def note_referenced_by(
+        self, name: str | None = None, id: str | None = None,
+    ) -> None:
         """Note that this Element has been referenced by its name
         `name` or id `id`."""
         self.referenced = True
@@ -1094,7 +1263,7 @@
             by_id.referenced = True
 
     @classmethod
-    def is_not_list_attribute(cls, attr) -> bool:
+    def is_not_list_attribute(cls, attr: str) -> bool:
         """
         Returns True if and only if the given attribute is NOT one of the
         basic list attributes defined for all Elements.
@@ -1102,7 +1271,7 @@
         return attr not in cls.list_attributes
 
     @classmethod
-    def is_not_known_attribute(cls, attr) -> bool:
+    def is_not_known_attribute(cls, attr: str) -> bool:
         """
         Return True if `attr` is NOT defined for all Element instances.
 
@@ -1110,7 +1279,7 @@
         """
         return attr not in cls.common_attributes
 
-    def validate_attributes(self):
+    def validate_attributes(self) -> None:
         """Normalize and validate element attributes.
 
         Convert string values to expected datatype.
@@ -1138,7 +1307,11 @@
                                   + '\n  '.join(messages),
                                   problematic_element=self)
 
-    def validate_content(self, model=None, elements=None):
+    def validate_content(
+        self,
+        model: _ContentModelTuple | None = None,
+        elements: Sequence[Element | Text] | None = None,
+    ) -> list[Element | Text]:
         """Test compliance of `elements` with `model`.
 
         :model: content model description, default `self.content_model`,
@@ -1177,7 +1350,11 @@
                     child = None
         return [] if child is None else [child, *ichildren]
 
-    def _report_child(self, child, category) -> str:
+    def _report_child(
+        self,
+        child: Element | Text | None,
+        category: Element | Iterable[Element],
+    ) -> str:
         # Return a str reporting a missing child or child of wrong category.
         try:
             type = category.__name__
@@ -1199,7 +1376,7 @@
         See `subtitle.check_position()` and `transition.check_position()`.
         """
 
-    def validate(self, recursive=True):
+    def validate(self, recursive: bool = True) -> None:
         """Validate Docutils Document Tree element ("doctree").
 
         Raise ValidationError if there are violations.
@@ -1276,7 +1453,7 @@
 
 class Admonition(Body):
     """Admonitions (distinctive and self-contained notices)."""
-    content_model = ((Body, '+'),)  # (%body.elements;)+
+    content_model: Final = ((Body, '+'),)  # (%body.elements;)+
 
 
 class Sequential(Body):
@@ -1303,7 +1480,7 @@
 
     Children of `decoration`.
     """
-    content_model = ((Body, '+'),)  # (%body.elements;)+
+    content_model: Final = ((Body, '+'),)  # (%body.elements;)+
 
 
 class Inline:
@@ -1327,16 +1504,16 @@
 
 
 class Resolvable:
-    resolved = False
+    resolved: bool = False
 
 
 class BackLinkable:
     """Mixin for Elements that accept a "backrefs" attribute."""
 
-    list_attributes = Element.list_attributes + ('backrefs',)
-    valid_attributes = Element.valid_attributes + ('backrefs',)
+    list_attributes: Final = Element.list_attributes + ('backrefs',)
+    valid_attributes: Final = Element.valid_attributes + ('backrefs',)
 
-    def add_backref(self, refid) -> None:
+    def add_backref(self: Element, refid: str) -> None:
         self['backrefs'].append(refid)
 
 
@@ -1346,9 +1523,9 @@
 
 class Targetable(Resolvable):
     """Cross-reference targets (incoming hyperlink)."""
-    referenced = 0
+    referenced: int = 0
 
-    indirect_reference_name = None
+    indirect_reference_name: str | None = None
     """Holds the whitespace_normalized_name (contains mixed case) of a target.
     Required for MoinMoin/reST compatibility.
 
@@ -1373,27 +1550,41 @@
     If passing children to `__init__()`, make sure to set `text` to
     ``''`` or some other suitable value.
     """
-    content_model = (  # (#PCDATA | %inline.elements;)*
-                     ((Text, Inline), '*'),)
+    content_model: Final = (
+        # (#PCDATA | %inline.elements;)*
+        ((Text, Inline), '*'),
+    )
 
-    child_text_separator = ''
+    child_text_separator: Final = ''
     """Separator for child nodes, used by `astext()` method."""
 
-    def __init__(self, rawsource='', text='', *children, **attributes) -> None:
+    def __init__(
+        self,
+        rawsource: str = '',
+        text: str = '',
+        *children: Element | Text,
+        **attributes: Any,
+    ) -> None:
         if text:
             textnode = Text(text)
-            Element.__init__(self, rawsource, textnode, *children,
+            super().__init__(rawsource, textnode, *children,
                              **attributes)
         else:
-            Element.__init__(self, rawsource, *children, **attributes)
+            super().__init__(rawsource, *children, **attributes)
 
 
 class FixedTextElement(TextElement):
     """An element which directly contains preformatted text."""
 
-    valid_attributes = Element.valid_attributes + ('xml:space',)
+    valid_attributes: Final = Element.valid_attributes + ('xml:space',)
 
-    def __init__(self, rawsource='', text='', *children, **attributes) -> None:
+    def __init__(
+        self,
+        rawsource: str = '',
+        text: str = '',
+        *children: Element | Text,
+        **attributes: Any,
+    ) -> None:
         super().__init__(rawsource, text, *children, **attributes)
         self.attributes['xml:space'] = 'preserve'
 
@@ -1400,7 +1591,7 @@
 
 class PureTextElement(TextElement):
     """An element which only contains text, no children."""
-    content_model = ((Text, '?'),)  # (#PCDATA)
+    content_model: Final = ((Text, '?'),)  # (#PCDATA)
 
 
 # =================================
@@ -1422,13 +1613,13 @@
 class title(Titular, PreBibliographic, SubStructural, TextElement):
     """Title of `document`, `section`, `topic` and generic `admonition`.
     """
-    valid_attributes = Element.valid_attributes + ('auto', 'refid')
+    valid_attributes: Final = Element.valid_attributes + ('auto', 'refid')
 
 
 class subtitle(Titular, PreBibliographic, SubStructural, TextElement):
     """Sub-title of `document`, `section` and `sidebar`."""
 
-    def check_position(self):
+    def check_position(self) -> None:
         """Check position of subtitle: must follow a title."""
         if self.parent and self.parent.index(self) == 0:
             raise ValidationError(f'Element {self.parent.starttag()} invalid:'
@@ -1438,28 +1629,32 @@
 
 class meta(PreBibliographic, SubStructural, Element):
     """Container for "invisible" bibliographic data, or meta-data."""
-    valid_attributes = Element.valid_attributes + (
+    valid_attributes: Final = Element.valid_attributes + (
         'content', 'dir', 'http-equiv', 'lang', 'media', 'name', 'scheme')
 
 
 class docinfo(SubStructural, Element):
     """Container for displayed document meta-data."""
-    content_model = (  # (%bibliographic.elements;)+
-                     (Bibliographic, '+'),)
+    content_model: Final = (
+        # (%bibliographic.elements;)+
+        (Bibliographic, '+'),
+    )
 
 
 class decoration(PreBibliographic, SubStructural, Element):
     """Container for `header` and `footer`."""
-    content_model = (  # (header?, footer?)
-                     (header, '?'),  # Empty element does not make sense,
-                     (footer, '?'))  # but is simpler to define.
+    content_model: Final = (
+        # (header?, footer?)
+        (header, '?'),  # Empty element does not make sense,
+        (footer, '?'),  # but is simpler to define.
+    )
 
-    def get_header(self):
+    def get_header(self) -> header:
         if not len(self.children) or not isinstance(self.children[0], header):
             self.insert(0, header())
         return self.children[0]
 
-    def get_footer(self):
+    def get_footer(self) -> footer:
         if not len(self.children) or not isinstance(self.children[-1], footer):
             self.append(footer())
         return self.children[-1]
@@ -1471,7 +1666,7 @@
     __ https://docutils.sourceforge.io/docs/ref/doctree.html#transition
     """
 
-    def check_position(self):
+    def check_position(self) -> None:
         """Check additional constraints on `transition` placement.
 
         A transition may not begin or end a section or document,
@@ -1505,9 +1700,11 @@
 
     __ https://docutils.sourceforge.io/docs/ref/doctree.html#topic
     """
-    content_model = (  # (title?, (%body.elements;)+)
-                     (title, '?'),
-                     (Body, '+'))
+    content_model: Final = (
+        # (title?, (%body.elements;)+)
+        (title, '?'),
+        (Body, '+'),
+    )
 
 
 class sidebar(Structural, Element):
@@ -1519,10 +1716,12 @@
 
     __ https://docutils.sourceforge.io/docs/ref/doctree.html#sidebar
     """
-    content_model = (  # ((title, subtitle?)?, (%body.elements; | topic)+)
-                     (title, '?'),
-                     (subtitle, '?'),
-                     ((topic, Body), '+'))
+    content_model: Final = (
+        # ((title, subtitle?)?, (%body.elements; | topic)+)
+        (title, '?'),
+        (subtitle, '?'),
+        ((topic, Body), '+'),
+    )
     # "subtitle only after title" is ensured in `subtitle.check_position()`.
 
 
@@ -1534,12 +1733,13 @@
     # recursive content model, see below
 
 
-section.content_model = (  # (title, subtitle?, %structure.model;)
-                         (title, '.'),
-                         (subtitle, '?'),
-                         ((Body, topic, sidebar, transition), '*'),
-                         ((section, transition), '*'),
-                         )
+section.content_model: Final = (
+    # (title, subtitle?, %structure.model;)
+    (title, '.'),
+    (subtitle, '?'),
+    ((Body, topic, sidebar, transition), '*'),
+    ((section, transition), '*'),
+)
 # Correct transition placement is ensured in `transition.check_position()`.
 
 
@@ -1553,117 +1753,124 @@
     Do not instantiate this class directly; use
     `docutils.utils.new_document()` instead.
     """
-    valid_attributes = Element.valid_attributes + ('title',)
-    content_model = (  # ( (title, subtitle?)?,
-                       #    meta*,
-                       #    decoration?,
-                       #    (docinfo, transition?)?,
-                       #    %structure.model; )
-                      (title, '?'),
-                      (subtitle, '?'),
-                      (meta, '*'),
-                      (decoration, '?'),
-                      (docinfo, '?'),
-                      (transition, '?'),
-                      ((Body, topic, sidebar, transition), '*'),
-                      ((section, transition), '*'),
-                     )
+    valid_attributes: Final = Element.valid_attributes + ('title',)
+    content_model: Final = (
+        # ( (title, subtitle?)?,
+        #    meta*,
+        #    decoration?,
+        #    (docinfo, transition?)?,
+        #    %structure.model; )
+        (title, '?'),
+        (subtitle, '?'),
+        (meta, '*'),
+        (decoration, '?'),
+        (docinfo, '?'),
+        (transition, '?'),
+        ((Body, topic, sidebar, transition), '*'),
+        ((section, transition), '*'),
+    )
     # Additional restrictions for `subtitle` and `transition` are tested
     # with the respective `check_position()` methods.
 
-    def __init__(self, settings, reporter, *args, **kwargs) -> None:
+    def __init__(
+        self,
+        settings: Values,
+        reporter: Reporter,
+        *args: Element | Text,
+        **kwargs: Any,
+    ) -> None:
         Element.__init__(self, *args, **kwargs)
 
-        self.current_source = None
+        self.current_source: str | os.PathLike[str] | None = None
         """Path to or description of the input source being processed."""
 
-        self.current_line = None
+        self.current_line: int | None = None
         """Line number (1-based) of `current_source`."""
 
-        self.settings = settings
+        self.settings: Values = settings
         """Runtime settings data record."""
 
-        self.reporter = reporter
+        self.reporter: Reporter = reporter
         """System message generator."""
 
-        self.indirect_targets = []
+        self.indirect_targets: list[target] = []
         """List of indirect target nodes."""
 
-        self.substitution_defs = {}
+        self.substitution_defs: dict[str, substitution_definition] = {}
         """Mapping of substitution names to substitution_definition nodes."""
 
-        self.substitution_names = {}
+        self.substitution_names: dict[str, str] = {}
         """Mapping of case-normalized substitution names to case-sensitive
         names."""
 
-        self.refnames = {}
+        self.refnames: dict[str, list[Element]] = {}
         """Mapping of names to lists of referencing nodes."""
 
-        self.refids = {}
+        self.refids: dict[str, list[Element]] = {}
         """Mapping of ids to lists of referencing nodes."""
 
-        self.nameids = {}
+        self.nameids: dict[str, str] = {}
         """Mapping of names to unique id's."""
 
-        self.nametypes = {}
+        self.nametypes: dict[str, bool] = {}
         """Mapping of names to hyperlink type (boolean: True => explicit,
         False => implicit."""
 
-        self.ids = {}
+        self.ids: dict[str, Element] = {}
         """Mapping of ids to nodes."""
 
-        self.footnote_refs = {}
+        self.footnote_refs: dict[str, list[footnote_reference]] = {}
         """Mapping of footnote labels to lists of footnote_reference nodes."""
 
-        self.citation_refs = {}
+        self.citation_refs: dict[str, list[citation_reference]] = {}
         """Mapping of citation labels to lists of citation_reference nodes."""
 
-        self.autofootnotes = []
+        self.autofootnotes: list[footnote] = []
         """List of auto-numbered footnote nodes."""
 
-        self.autofootnote_refs = []
+        self.autofootnote_refs: list[footnote_reference] = []
         """List of auto-numbered footnote_reference nodes."""
 
-        self.symbol_footnotes = []
+        self.symbol_footnotes: list[footnote] = []
         """List of symbol footnote nodes."""
 
-        self.symbol_footnote_refs = []
+        self.symbol_footnote_refs: list[footnote_reference] = []
         """List of symbol footnote_reference nodes."""
 
-        self.footnotes = []
+        self.footnotes: list[footnote] = []
         """List of manually-numbered footnote nodes."""
 
-        self.citations = []
+        self.citations: list[citation] = []
         """List of citation nodes."""
 
-        self.autofootnote_start = 1
+        self.autofootnote_start: int = 1
         """Initial auto-numbered footnote number."""
 
-        self.symbol_footnote_start = 0
+        self.symbol_footnote_start: int = 0
         """Initial symbol footnote symbol index."""
 
-        self.id_counter = Counter()
+        self.id_counter: Counter[int] = Counter()
         """Numbers added to otherwise identical IDs."""
 
-        self.parse_messages = []
+        self.parse_messages: list[system_message] = []
         """System messages generated while parsing."""
 
-        self.transform_messages = []
+        self.transform_messages: list[system_message] = []
         """System messages generated while applying transforms."""
 
         import docutils.transforms
-        self.transformer = docutils.transforms.Transformer(self)
+        self.transformer: Transformer = docutils.transforms.Transformer(self)
         """Storage for transforms to be applied to this document."""
 
-        self.include_log = []
+        self.include_log: list[tuple[str | os.PathLike[str], tuple]] = []
         """The current source's parents (to detect inclusion loops)."""
 
-        self.decoration = None
+        self.decoration: decoration | None = None
         """Document's `decoration` node."""
 
-        self._document = self
+        self._document: document = self
 
-    def __getstate__(self):
+    def __getstate__(self) -> dict[str, Any]:
         """
         Return dict with unpicklable references removed.
         """
@@ -1672,7 +1879,7 @@
         state['transformer'] = None
         return state
 
-    def asdom(self, dom=None):
+    def asdom(self, dom: None = None) -> minidom.Document:
         """Return a DOM representation of this document."""
         if dom is None:
             import xml.dom.minidom as dom
@@ -1680,7 +1887,12 @@
         domroot.appendChild(self._dom_node(domroot))
         return domroot
 
-    def set_id(self, node, msgnode=None, suggested_prefix=''):
+    def set_id(
+        self,
+        node: Element,
+        msgnode: Element | None = None,
+        suggested_prefix: str = '',
+    ) -> str:
         if node['ids']:
             # register and check for duplicates
             for id in node['ids']:
@@ -1725,7 +1937,13 @@
         self.ids[id] = node
         return id
 
-    def set_name_id_map(self, node, id, msgnode=None, explicit=None) -> None:
+    def set_name_id_map(
+        self,
+        node: Element,
+        id: str,
+        msgnode: Element | None = None,
+        explicit: bool = False,
+    ) -> None:
         """
         `self.nameids` maps names to IDs, while `self.nametypes` maps names to
         booleans representing hyperlink type (True==explicit,
@@ -1766,7 +1984,14 @@
                 self.nameids[name] = id
                 self.nametypes[name] = explicit
 
-    def set_duplicate_name_id(self, node, id, name, msgnode, explicit) -> None:
+    def set_duplicate_name_id(
+        self,
+        node: Element,
+        id: str,
+        name: str,
+        msgnode: Element,
+        explicit: bool,
+    ) -> None:
         old_id = self.nameids[name]
         old_explicit = self.nametypes[name]
         self.nametypes[name] = old_explicit or explicit
@@ -1808,66 +2033,75 @@
             if msgnode is not None:
                 msgnode += msg
 
-    def has_name(self, name) -> bool:
+    def has_name(self, name: str) -> bool:
         return name in self.nameids
 
     # "note" here is an imperative verb: "take note of".
-    def note_implicit_target(self, target, msgnode=None) -> None:
+    def note_implicit_target(
+        self, target: Element, msgnode: Element | None = None,
+    ) -> None:
         id = self.set_id(target, msgnode)
         self.set_name_id_map(target, id, msgnode, explicit=False)
 
-    def note_explicit_target(self, target, msgnode=None) -> None:
+    def note_explicit_target(
+        self, target: Element, msgnode: Element | None = None,
+    ) -> None:
         id = self.set_id(target, msgnode)
         self.set_name_id_map(target, id, msgnode, explicit=True)
 
-    def note_refname(self, node) -> None:
+    def note_refname(self, node: Element) -> None:
         self.refnames.setdefault(node['refname'], []).append(node)
 
-    def note_refid(self, node) -> None:
+    def note_refid(self, node: Element) -> None:
         self.refids.setdefault(node['refid'], []).append(node)
 
-    def note_indirect_target(self, target) -> None:
+    def note_indirect_target(self, target: target) -> None:
         self.indirect_targets.append(target)
         if target['names']:
             self.note_refname(target)
 
-    def note_anonymous_target(self, target) -> None:
+    def note_anonymous_target(self, target: target) -> None:
         self.set_id(target)
 
-    def note_autofootnote(self, footnote) -> None:
+    def note_autofootnote(self, footnote: footnote) -> None:
         self.set_id(footnote)
         self.autofootnotes.append(footnote)
 
-    def note_autofootnote_ref(self, ref) -> None:
+    def note_autofootnote_ref(self, ref: footnote_reference) -> None:
         self.set_id(ref)
         self.autofootnote_refs.append(ref)
 
-    def note_symbol_footnote(self, footnote) -> None:
+    def note_symbol_footnote(self, footnote: footnote) -> None:
         self.set_id(footnote)
         self.symbol_footnotes.append(footnote)
 
-    def note_symbol_footnote_ref(self, ref) -> None:
+    def note_symbol_footnote_ref(self, ref: footnote_reference) -> None:
         self.set_id(ref)
         self.symbol_footnote_refs.append(ref)
 
-    def note_footnote(self, footnote) -> None:
+    def note_footnote(self, footnote: footnote) -> None:
         self.set_id(footnote)
         self.footnotes.append(footnote)
 
-    def note_footnote_ref(self, ref) -> None:
+    def note_footnote_ref(self, ref: footnote_reference) -> None:
         self.set_id(ref)
         self.footnote_refs.setdefault(ref['refname'], []).append(ref)
         self.note_refname(ref)
 
-    def note_citation(self, citation) -> None:
+    def note_citation(self, citation: citation) -> None:
         self.citations.append(citation)
 
-    def note_citation_ref(self, ref) -> None:
+    def note_citation_ref(self, ref: citation_reference) -> None:
         self.set_id(ref)
         self.citation_refs.setdefault(ref['refname'], []).append(ref)
         self.note_refname(ref)
 
-    def note_substitution_def(self, subdef, def_name, msgnode=None) -> None:
+    def note_substitution_def(
+        self,
+        subdef: substitution_definition,
+        def_name: str,
+        msgnode: Element | None = None,
+    ) -> None:
         name = whitespace_normalize_name(def_name)
         if name in self.substitution_defs:
             msg = self.reporter.error(
@@ -1882,26 +2116,34 @@
         # case-insensitive mapping:
         self.substitution_names[fully_normalize_name(name)] = name
 
-    def note_substitution_ref(self, subref, refname) -> None:
+    def note_substitution_ref(
+        self, subref: substitution_reference, refname: str,
+    ) -> None:
         subref['refname'] = whitespace_normalize_name(refname)
 
-    def note_pending(self, pending, priority=None) -> None:
+    def note_pending(
+        self, pending: pending, priority: int | None = None,
+    ) -> None:
         self.transformer.add_pending(pending, priority)
 
-    def note_parse_message(self, message) -> None:
+    def note_parse_message(self, message: system_message) -> None:
         self.parse_messages.append(message)
 
-    def note_transform_message(self, message) -> None:
+    def note_transform_message(self, message: system_message) -> None:
         self.transform_messages.append(message)
 
-    def note_source(self, source, offset) -> None:
-        self.current_source = source
+    def note_source(
+        self,
+        source: str | os.PathLike[str] | None,
+        offset: int | None,
+    ) -> None:
+        self.current_source = source and os.fspath(source)
         if offset is None:
             self.current_line = offset
         else:
             self.current_line = offset + 1
 
-    def copy(self):
+    def copy(self) -> Self:
         obj = self.__class__(self.settings, self.reporter,
                              **self.attributes)
         obj.source = self.source
@@ -1908,9 +2150,9 @@
         obj.line = self.line
         return obj
 
-    def get_decoration(self):
+    def get_decoration(self) -> decoration:
         if not self.decoration:
-            self.decoration = decoration()
+            self.decoration: decoration = decoration()
             index = self.first_child_not_matching_class((Titular, meta))
             if index is None:
                 self.append(self.decoration)
@@ -1936,13 +2178,19 @@
 class authors(Bibliographic, Element):
     """Container for author information for documents with multiple authors.
     """
-    content_model = (  # (author, organization?, address?, contact?)+
-                     (author, '+'),
-                     (organization, '?'),
-                     (address, '?'),
-                     (contact, '?'))
+    content_model: Final = (
+        # (author, organization?, address?, contact?)+
+        (author, '+'),
+        (organization, '?'),
+        (address, '?'),
+        (contact, '?'),
+    )
 
-    def validate_content(self):
+    def validate_content(
+        self,
+        model: _ContentModelTuple | None = None,
+        elements: Sequence[Element | Text] | None = None,
+    ) -> list[Element | Text]:
         """Repeatedly test for children matching the content model.
 
         Provisional.
@@ -1966,11 +2214,11 @@
 
 
 class compound(General, Element):
-    content_model = ((Body, '+'),)  # (%body.elements;)+
+    content_model: Final = ((Body, '+'),)  # (%body.elements;)+
 
 
 class container(General, Element):
-    content_model = ((Body, '+'),)  # (%body.elements;)+
+    content_model: Final = ((Body, '+'),)  # (%body.elements;)+
 
 
 class attribution(Part, TextElement):
@@ -1979,9 +2227,11 @@
 
 class block_quote(General, Element):
     """An extended quotation, set off from the main text."""
-    content_model = (  # ((%body.elements;)+, attribution?)
-                     (Body, '+'),
-                     (attribution, '?'))
+    content_model: Final = (
+        # ((%body.elements;)+, attribution?)
+        (Body, '+'),
+        (attribution, '?'),
+    )
 
 
 # Lists
@@ -1990,18 +2240,18 @@
 # Lists (Sequential) and related Body Subelements (Part)
 
 class list_item(Part, Element):
-    content_model = ((Body, '*'),)  # (%body.elements;)*
+    content_model: Final = ((Body, '*'),)  # (%body.elements;)*
 
 
 class bullet_list(Sequential, Element):
-    valid_attributes = Element.valid_attributes + ('bullet',)
-    content_model = ((list_item, '+'),)  # (list_item+)
+    valid_attributes: Final = Element.valid_attributes + ('bullet',)
+    content_model: Final = ((list_item, '+'),)  # (list_item+)
 
 
 class enumerated_list(Sequential, Element):
-    valid_attributes = Element.valid_attributes + (
+    valid_attributes: Final = Element.valid_attributes + (
         'enumtype', 'prefix', 'suffix', 'start')
-    content_model = ((list_item, '+'),)  # (list_item+)
+    content_model: Final = ((list_item, '+'),)  # (list_item+)
 
 
 class term(Part, TextElement): pass
@@ -2010,14 +2260,16 @@
 
 class definition(Part, Element):
     """Definition of a `term` in a `definition_list`."""
-    content_model = ((Body, '+'),)  # (%body.elements;)+
+    content_model: Final = ((Body, '+'),)  # (%body.elements;)+
 
 
 class definition_list_item(Part, Element):
-    content_model = (  # ((term, classifier*)+, definition)
-                     (term, '.'),
-                     ((classifier, term), '*'),
-                     (definition, '.'))
+    content_model: Final = (
+        # ((term, classifier*)+, definition)
+        (term, '.'),
+        ((classifier, term), '*'),
+        (definition, '.'),
+    )
 
 
 class definition_list(Sequential, Element):
@@ -2026,7 +2278,10 @@
     Can be used for glossaries or dictionaries, to describe or
     classify things, for dialogues, or to itemize subtopics.
     """
-    content_model = ((definition_list_item, '+'),)  # (definition_list_item+)
+    content_model: Final = (
+        # (definition_list_item+)
+        (definition_list_item, '+'),
+    )
 
 
 class field_name(Part, TextElement): pass
@@ -2033,13 +2288,15 @@
 
 
 class field_body(Part, Element):
-    content_model = ((Body, '*'),)  # (%body.elements;)*
+    content_model: Final = ((Body, '*'),)  # (%body.elements;)*
 
 
 class field(Part, Bibliographic, Element):
-    content_model = (  # (field_name, field_body)
-                     (field_name, '.'),
-                     (field_body, '.'))
+    content_model: Final = (
+        # (field_name, field_body)
+        (field_name, '.'),
+        (field_body, '.'),
+    )
 
 
 class field_list(Sequential, Element):
@@ -2048,7 +2305,7 @@
     Typically rendered as a two-column list.
     Also used for extension syntax or special processing.
     """
-    content_model = ((field, '+'),)  # (field+)
+    content_model: Final = ((field, '+'),)  # (field+)
 
 
 class option_string(Part, PureTextElement):
@@ -2057,9 +2314,9 @@
 
 class option_argument(Part, PureTextElement):
     """Placeholder text for option arguments."""
-    valid_attributes = Element.valid_attributes + ('delimiter',)
+    valid_attributes: Final = Element.valid_attributes + ('delimiter',)
 
-    def astext(self):
+    def astext(self) -> str:
         return self.get('delimiter', ' ') + TextElement.astext(self)
 
 
@@ -2068,35 +2325,39 @@
 
     Groups an option string with zero or more option argument placeholders.
     """
-    child_text_separator = ''
-    content_model = (  # (option_string, option_argument*)
-                     (option_string, '.'),
-                     (option_argument, '*'))
+    child_text_separator: Final = ''
+    content_model: Final = (
+        # (option_string, option_argument*)
+        (option_string, '.'),
+        (option_argument, '*'),
+    )
 
 
 class option_group(Part, Element):
     """Groups together one or more `option` elements, all synonyms."""
-    child_text_separator = ', '
-    content_model = ((option, '+'),)  # (option+)
+    child_text_separator: Final = ', '
+    content_model: Final = ((option, '+'),)  # (option+)
 
 
 class description(Part, Element):
     """Describtion of a command-line option."""
-    content_model = ((Body, '+'),)  # (%body.elements;)+
+    content_model: Final = ((Body, '+'),)  # (%body.elements;)+
 
 
 class option_list_item(Part, Element):
     """Container for a pair of `option_group` and `description` elements.
     """
-    child_text_separator = '  '
-    content_model = (  # (option_group, description)
-                     (option_group, '.'),
-                     (description, '.'))
+    child_text_separator: Final = '  '
+    content_model: Final = (
+        # (option_group, description)
+        (option_group, '.'),
+        (description, '.'),
+    )
 
 
 class option_list(Sequential, Element):
     """Two-column list of command-line options and descriptions."""
-    content_model = ((option_list_item, '+'),)  # (option_list_item+)
+    content_model: Final = ((option_list_item, '+'),)  # (option_list_item+)
 
 
 # Pre-formatted text blocks
@@ -2112,7 +2373,7 @@
 
 class line(Part, TextElement):
     """Single line of text in a `line_block`."""
-    indent = None
+    indent: str | None = None
 
 
 class line_block(General, Element):
@@ -2121,7 +2382,7 @@
     # recursive content model: (line | line_block)+
 
 
-line_block.content_model = (((line, line_block), '+'),)
+line_block.content_model: Final = (((line, line_block), '+'),)
 
 
 # Admonitions
@@ -2140,9 +2401,11 @@
 
 
 class admonition(Admonition, Element):
-    content_model = (  # (title, (%body.elements;)+)
-                     (title, '.'),
-                     (Body, '+'))
+    content_model: Final = (
+        # (title, (%body.elements;)+)
+        (title, '.'),
+        (Body, '+'),
+    )
 
 
 # Footnote and citation
@@ -2154,10 +2417,12 @@
 
 class footnote(General, BackLinkable, Element, Labeled, Targetable):
     """Labelled note providing additional context (footnote or endnote)."""
-    valid_attributes = Element.valid_attributes + ('auto', 'backrefs')
-    content_model = (  # (label?, (%body.elements;)+)
-                     (label, '?'),
-                     (Body, '+'))
+    valid_attributes: Final = Element.valid_attributes + ('auto', 'backrefs')
+    content_model: Final = (
+        # (label?, (%body.elements;)+)
+        (label, '?'),
+        (Body, '+'),
+    )
     # TODO: Why is the label optional and content required?
     # The rST specification says: "Each footnote consists of an
     # explicit markup start (".. "), a left square bracket,
@@ -2173,9 +2438,11 @@
 
 
 class citation(General, BackLinkable, Element, Labeled, Targetable):
-    content_model = (  # (label, (%body.elements;)+)
-                     (label, '.'),
-                     (Body, '+'))
+    content_model: Final = (
+        # (label, (%body.elements;)+)
+        (label, '.'),
+        (Body, '+'),
+    )
     # TODO: docutils.dtd requires both label and content but the rST parser
     # allows empty citation (see test_rst/test_citations.py).
     # Is this sensible?
@@ -2191,10 +2458,10 @@
 
     May be body element or inline element.
     """
-    valid_attributes = Element.valid_attributes + (
+    valid_attributes: Final = Element.valid_attributes + (
         'uri', 'alt', 'align', 'height', 'width', 'scale', 'loading')
 
-    def astext(self):
+    def astext(self) -> str:
         return self.get('alt', '')
 
 
@@ -2203,16 +2470,18 @@
 
 class legend(Part, Element):
     """A wrapper for text accompanying a `figure` that is not the caption."""
-    content_model = ((Body, '+'),)  # (%body.elements;)+
+    content_model: Final = ((Body, '+'),)  # (%body.elements;)+
 
 
 class figure(General, Element):
     """A formal figure, generally an illustration, with a title."""
-    valid_attributes = Element.valid_attributes + ('align', 'width')
-    content_model = (  # (image, ((caption, legend?) | legend))
-                     (image, '.'),
-                     (caption, '?'),
-                     (legend, '?'))
+    valid_attributes: Final = Element.valid_attributes + ('align', 'width')
+    content_model: Final = (
+        # (image, ((caption, legend?) | legend))
+        (image, '.'),
+        (caption, '?'),
+        (legend, '?'),
+    )
     # TODO: According to the DTD, a caption or legend is required
     # but rST allows "bare" figures which are formatted differently from
     # images (floating in LaTeX, nested in a <figure> in HTML).
@@ -2223,21 +2492,24 @@
 
 class entry(Part, Element):
     """An entry in a `row` (a table cell)."""
-    valid_attributes = Element.valid_attributes + (
+    valid_attributes: Final = Element.valid_attributes + (
         'align', 'char', 'charoff', 'colname', 'colsep', 'morecols',
         'morerows', 'namest', 'nameend', 'rowsep', 'valign')
-    content_model = ((Body, '*'),)  # %tbl.entry.mdl -> (%body.elements;)*
+    content_model: Final = (
+        # %tbl.entry.mdl -> (%body.elements;)*
+        (Body, '*'),
+    )
 
 
 class row(Part, Element):
     """Row of table cells."""
-    valid_attributes = Element.valid_attributes + ('rowsep', 'valign')
-    content_model = ((entry, '+'),)  # (%tbl.row.mdl;) -> entry+
+    valid_attributes: Final = Element.valid_attributes + ('rowsep', 'valign')
+    content_model: Final = ((entry, '+'),)  # (%tbl.row.mdl;) -> entry+
 
 
 class colspec(Part, Element):
     """Specifications for a column in a `tgroup`."""
-    valid_attributes = Element.valid_attributes + (
+    valid_attributes: Final = Element.valid_attributes + (
         'align', 'char', 'charoff', 'colname', 'colnum',
         'colsep', 'colwidth', 'rowsep', 'stub')
 
@@ -2244,33 +2516,37 @@
 
 class thead(Part, Element):
     """Row(s) that form the head of a `tgroup`."""
-    valid_attributes = Element.valid_attributes + ('valign',)
-    content_model = ((row, '+'),)  # (row+)
+    valid_attributes: Final = Element.valid_attributes + ('valign',)
+    content_model: Final = ((row, '+'),)  # (row+)
 
 
 class tbody(Part, Element):
     """Body of a `tgroup`."""
-    valid_attributes = Element.valid_attributes + ('valign',)
-    content_model = ((row, '+'),)  # (row+)
+    valid_attributes: Final = Element.valid_attributes + ('valign',)
+    content_model: Final = ((row, '+'),)  # (row+)
 
 
 class tgroup(Part, Element):
     """A portion of a table. Most tables have just one `tgroup`."""
-    valid_attributes = Element.valid_attributes + (
+    valid_attributes: Final = Element.valid_attributes + (
         'align', 'cols', 'colsep', 'rowsep')
-    content_model = (  # (colspec*, thead?, tbody)
-                     (colspec, '*'),
-                     (thead, '?'),
-                     (tbody, '.'))
+    content_model: Final = (
+        # (colspec*, thead?, tbody)
+        (colspec, '*'),
+        (thead, '?'),
+        (tbody, '.'),
+    )
 
 
 class table(General, Element):
     """A data arrangement with rows and columns."""
-    valid_attributes = Element.valid_attributes + (
+    valid_attributes: Final = Element.valid_attributes + (
         'align', 'colsep', 'frame', 'pgwide', 'rowsep', 'width')
-    content_model = (  # (title?, tgroup+)
-                     (title, '?'),
-                     (tgroup, '+'))
+    content_model: Final = (
+        # (title?, tgroup+)
+        (title, '?'),
+        (tgroup, '+'),
+    )
 
 
 # Special purpose elements
@@ -2282,11 +2558,11 @@
 
 
 class substitution_definition(Invisible, TextElement):
-    valid_attributes = Element.valid_attributes + ('ltrim', 'rtrim')
+    valid_attributes: Final = Element.valid_attributes + ('ltrim', 'rtrim')
 
 
 class target(Invisible, Inline, TextElement, Targetable):
-    valid_attributes = Element.valid_attributes + (
+    valid_attributes: Final = Element.valid_attributes + (
         'anonymous', 'refid', 'refname', 'refuri')
 
 
@@ -2297,11 +2573,16 @@
     Do not instantiate this class directly; use
     ``document.reporter.info/warning/error/severe()`` instead.
     """
-    valid_attributes = BackLinkable.valid_attributes + (
+    valid_attributes: Final = BackLinkable.valid_attributes + (
                            'level', 'line', 'type')
-    content_model = ((Body, '+'),)  # (%body.elements;)+
+    content_model: Final = ((Body, '+'),)  # (%body.elements;)+
 
-    def __init__(self, message=None, *children, **attributes) -> None:
+    def __init__(
+        self,
+        message: str | None = None,
+        *children: Element | Text,
+        **attributes: Any,
+    ) -> None:
         rawsource = attributes.pop('rawsource', '')
         if message:
             p = paragraph('', message)
@@ -2347,18 +2628,24 @@
     transforms.
     """
 
-    def __init__(self, transform, details=None,
-                 rawsource='', *children, **attributes) -> None:
+    def __init__(
+        self,
+        transform: Transform,
+        details: Mapping[str, Any] | None = None,
+        rawsource: str = '',
+        *children: Element | Text,
+        **attributes: Any,
+    ) -> None:
         Element.__init__(self, rawsource, *children, **attributes)
 
-        self.transform = transform
+        self.transform: Transform = transform
         """The `docutils.transforms.Transform` class implementing the pending
         operation."""
 
-        self.details = details or {}
+        self.details: Mapping[str, Any] = details or {}
         """Detail data (dictionary) required by the pending operation."""
 
-    def pformat(self, indent='    ', level=0):
+    def pformat(self, indent: str = '    ', level: int = 0) -> str:
         internals = ['.. internal attributes:',
                      '     .transform: %s.%s' % (self.transform.__module__,
                                                  self.transform.__name__),
@@ -2382,7 +2669,7 @@
                 + ''.join(('    %s%s\n' % (indent * level, line))
                           for line in internals))
 
-    def copy(self):
+    def copy(self) -> Self:
         obj = self.__class__(self.transform, self.details, self.rawsource,
                              **self.attributes)
         obj._document = self._document
@@ -2397,7 +2684,8 @@
 
     Can be used as Body element or Inline element.
     """
-    valid_attributes = Element.valid_attributes + ('format', 'xml:space')
+    valid_attributes: Final = Element.valid_attributes + (
+        'format', 'xml:space')
 
 
 # Inline Elements
@@ -2416,20 +2704,21 @@
 
 
 class reference(General, Inline, Referential, TextElement):
-    valid_attributes = Element.valid_attributes + (
+    valid_attributes: Final = Element.valid_attributes + (
         'anonymous', 'name', 'refid', 'refname', 'refuri')
 
 
 class footnote_reference(Inline, Referential, PureTextElement):
-    valid_attributes = Element.valid_attributes + ('auto', 'refid', 'refname')
+    valid_attributes: Final = Element.valid_attributes + (
+        'auto', 'refid', 'refname')
 
 
 class citation_reference(Inline, Referential, PureTextElement):
-    valid_attributes = Element.valid_attributes + ('refid', 'refname')
+    valid_attributes: Final = Element.valid_attributes + ('refid', 'refname')
 
 
 class substitution_reference(Inline, TextElement):
-    valid_attributes = Element.valid_attributes + ('refname',)
+    valid_attributes: Final = Element.valid_attributes + ('refname',)
 
 
 class math(Inline, PureTextElement):
@@ -2437,7 +2726,7 @@
 
 
 class problematic(Inline, TextElement):
-    valid_attributes = Element.valid_attributes + (
+    valid_attributes: Final = Element.valid_attributes + (
                            'refid', 'refname', 'refuri')
 
 
@@ -2445,7 +2734,7 @@
 #  Auxiliary Classes, Functions, and Data
 # ========================================
 
-node_class_names = """
+node_class_names: Sequence[str] = """
     Text
     abbreviation acronym address admonition attention attribution author
         authors
@@ -2504,7 +2793,7 @@
        1995.
     """
 
-    optional = ('meta',)
+    optional: ClassVar[tuple[str, ...]] = ('meta',)
     """
     Tuple containing node class names (as strings).
 
@@ -2514,10 +2803,10 @@
     Used to ensure transitional compatibility with existing 3rd-party writers.
     """
 
-    def __init__(self, document) -> None:
-        self.document = document
+    def __init__(self, document: document, /) -> None:
+        self.document: document = document
 
-    def dispatch_visit(self, node):
+    def dispatch_visit(self, node: Element | Text) -> None:
         """
         Call self."``visit_`` + node class name" with `node` as
         parameter.  If the ``visit_...`` method does not exist, call
@@ -2530,7 +2819,7 @@
             % (method.__name__, node_name))
         return method(node)
 
-    def dispatch_departure(self, node):
+    def dispatch_departure(self, node: Element | Text) -> None:
         """
         Call self."``depart_`` + node class name" with `node` as
         parameter.  If the ``depart_...`` method does not exist, call
@@ -2543,7 +2832,7 @@
             % (method.__name__, node_name))
         return method(node)
 
-    def unknown_visit(self, node):
+    def unknown_visit(self, node: Element | Text) -> None:
         """
         Called when entering unknown `Node` types.
 
@@ -2555,7 +2844,7 @@
                 '%s visiting unknown node type: %s'
                 % (self.__class__, node.__class__.__name__))
 
-    def unknown_departure(self, node):
+    def unknown_departure(self, node: Element | Text) -> None:
         """
         Called before exiting unknown `Node` types.
 
@@ -2594,24 +2883,33 @@
     be overridden for default behavior.
     """
 
-    def default_visit(self, node):
+    def default_visit(self, node: Element | Text):
         """Override for generic, uniform traversals."""
         raise NotImplementedError
 
-    def default_departure(self, node):
+    def default_departure(self, node: Element | Text):
         """Override for generic, uniform traversals."""
         raise NotImplementedError
 
 
-def _call_default_visit(self, node) -> None:
+def _call_default_visit(
+    self: GenericNodeVisitor,
+    node: Element | Text,
+) -> None:
     self.default_visit(node)
 
 
-def _call_default_departure(self, node) -> None:
+def _call_default_departure(
+    self: GenericNodeVisitor,
+    node: Element | Text,
+) -> None:
     self.default_departure(node)
 
 
-def _nop(self, node) -> None:
+def _nop(
+    self: SparseNodeVisitor,
+    node: Element | Text,
+) -> None:
     pass
 
 
@@ -2632,15 +2930,15 @@
     Make a complete copy of a tree or branch, including element attributes.
     """
 
-    def __init__(self, document) -> None:
-        GenericNodeVisitor.__init__(self, document)
-        self.parent_stack = []
-        self.parent = []
+    def __init__(self, document: document) -> None:
+        super().__init__(document)
+        self.parent_stack: list[list[Element | Text]] = []
+        self.parent: list[Element | Text] = []
 
-    def get_tree_copy(self):
+    def get_tree_copy(self) -> Element | Text:
         return self.parent[0]
 
-    def default_visit(self, node) -> None:
+    def default_visit(self, node: Element | Text) -> None:
         """Copy the current node, and make it the new acting parent."""
         newnode = node.copy()
         self.parent.append(newnode)
@@ -2647,7 +2945,7 @@
         self.parent_stack.append(self.parent)
         self.parent = newnode
 
-    def default_departure(self, node) -> None:
+    def default_departure(self, node: Element | Text) -> None:
         """Restore the previous acting parent."""
         self.parent = self.parent_stack.pop()
 
@@ -2657,7 +2955,7 @@
 
 class ValidationError(ValueError):
     """Invalid Docutils Document Tree Element."""
-    def __init__(self, msg, problematic_element=None) -> None:
+    def __init__(self, msg: str, problematic_element: Element = None) -> None:
         super().__init__(msg)
         self.problematic_element = problematic_element
 
@@ -2719,7 +3017,11 @@
 
 
 # definition moved here from `utils` to avoid circular import dependency
-def unescape(text, restore_backslashes=False, respect_whitespace=False):
+def unescape(
+    text: str,
+    restore_backslashes: bool = False,
+    respect_whitespace: bool = False,
+) -> str:
     """
     Return a string with nulls removed or restored to backslashes.
     Backslash-escaped spaces are also removed.
@@ -2733,7 +3035,7 @@
         return text
 
 
-def make_id(string):
+def make_id(string: str) -> str:
     """
     Convert `string` into an identifier and return it.
 
@@ -2781,9 +3083,9 @@
     return str(id)
 
 
-_non_id_chars = re.compile('[^a-z0-9]+')
-_non_id_at_ends = re.compile('^[-0-9]+|-+$')
-_non_id_translate = {
+_non_id_chars: re.Pattern[str] = re.compile('[^a-z0-9]+')
+_non_id_at_ends: re.Pattern[str] = re.compile('^[-0-9]+|-+$')
+_non_id_translate: dict[int, str] = {
     0x00f8: 'o',       # o with stroke
     0x0111: 'd',       # d with stroke
     0x0127: 'h',       # h with stroke
@@ -2818,7 +3120,7 @@
     0x024d: 'r',       # r with stroke
     0x024f: 'y',       # y with stroke
 }
-_non_id_translate_digraphs = {
+_non_id_translate_digraphs: dict[int, str] = {
     0x00df: 'sz',      # ligature sz
     0x00e6: 'ae',      # ae
     0x0153: 'oe',      # ligature oe
@@ -2827,7 +3129,7 @@
 }
 
 
-def dupname(node, name) -> None:
+def dupname(node: Element, name: str) -> None:
     node['dupnames'].append(name)
     node['names'].remove(name)
     # Assume that `node` is referenced, even though it isn't;
@@ -2835,22 +3137,22 @@
     node.referenced = True
 
 
-def fully_normalize_name(name):
+def fully_normalize_name(name: str) -> str:
     """Return a case- and whitespace-normalized name."""
     return ' '.join(name.lower().split())
 
 
-def whitespace_normalize_name(name):
+def whitespace_normalize_name(name: str) -> str:
     """Return a whitespace-normalized name."""
     return ' '.join(name.split())
 
 
-def serial_escape(value):
+def serial_escape(value: str) -> str:
     """Escape string values that are elements of a list, for serialization."""
     return value.replace('\\', r'\\').replace(' ', r'\ ')
 
 
-def split_name_list(s):
+def split_name_list(s: str) -> list[str]:
     r"""Split a string at non-escaped whitespace.
 
     Backslashes escape internal whitespace (cf. `serial_escape()`).
@@ -2870,7 +3172,7 @@
             for name in names]
 
 
-def pseudo_quoteattr(value) -> str:
+def pseudo_quoteattr(value: str) -> str:
     """Quote attributes for pseudo-xml"""
     return '"%s"' % value
 
@@ -2889,13 +3191,13 @@
 # __ https://docutils.sourceforge.io/docs/ref/doctree.html#attribute-reference
 # __ https://docutils.sourceforge.io/docs/ref/doctree.html#attribute-types
 
-def validate_enumerated_type(*keywords):
+def validate_enumerated_type(*keywords: str) -> Callable[[str], str]:
     """
     Return a function that validates a `str` against given `keywords`.
 
     Provisional.
     """
-    def validate_keywords(value):
+    def validate_keywords(value: str) -> str:
         if value not in keywords:
             allowed = '", \"'.join(keywords)
             raise ValueError(f'"{value}" is not one of "{allowed}".')
@@ -2903,7 +3205,7 @@
     return validate_keywords
 
 
-def validate_identifier(value):
+def validate_identifier(value: str) -> str:
     """
     Validate identifier key or class name.
 
@@ -2918,7 +3220,7 @@
     return value
 
 
-def validate_identifier_list(value):
+def validate_identifier_list(value: str | list[str]) -> list[str]:
     """
     A (space-separated) list of ids or class names.
 
@@ -2940,7 +3242,7 @@
     return value
 
 
-def validate_measure(value):
+def validate_measure(value: str) -> str:
     """
     Validate a length measure__ (number + recognized unit).
 
@@ -2955,7 +3257,7 @@
     return value.replace(' ', '').strip()
 
 
-def validate_NMTOKEN(value):
+def validate_NMTOKEN(value: str) -> str:
     """
     Validate a "name token": a `str` of letters, digits, and [-._].
 
@@ -2966,7 +3268,7 @@
     return value
 
 
-def validate_NMTOKENS(value):
+def validate_NMTOKENS(value: str | list[str]) -> list[str]:
     """
     Validate a list of "name tokens".
 
@@ -2979,7 +3281,7 @@
     return value
 
 
-def validate_refname_list(value):
+def validate_refname_list(value: str | list[str]) -> list[str]:
     """
     Validate a list of `reference names`__.
 
@@ -3001,13 +3303,13 @@
     return [whitespace_normalize_name(name) for name in value]
 
 
-def validate_yesorno(value):
+def validate_yesorno(value: bool | Literal['0', '1']) -> bool:
     if value == "0":
         return False
     return bool(value)
 
 
-ATTRIBUTE_VALIDATORS = {
+ATTRIBUTE_VALIDATORS: dict[str, Callable[[str], Any]] = {
     'alt': str,  # CDATA
     'align': str,
     'anonymous': validate_yesorno,

This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.