diff --git a/doc/conf.py b/doc/conf.py index ac078872c..5b9e55f83 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -36,7 +36,6 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ - "sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.autosummary", "sphinx.ext.intersphinx", @@ -293,29 +292,6 @@ # texinfo_show_urls = 'footnote' -patparam = re.compile(r"(\W*):\W*param.*") - - -def process_docstring_insert_self(app, what, name, obj, options, lines): - """ - Insert self in front of documented params for instance methods - """ - - if ( - what == "method" - and app.config.autodoc_typehints in ("both", "description") - and app.config.autodoc_typehints_description_target in ("all") - and getattr(obj, "__self__", None) is None - and "self" in obj.__annotations__ - ): - for i, line in enumerate(lines): - if m := patparam.match(line): - indent = m.group(1) - lines.insert(i, f"{indent}:param self:") - break - - def setup(app): app.add_css_file("tables.css") - app.connect("autodoc-process-docstring", process_docstring_insert_self) diff --git a/doc/ext/sphinx_autodoc_multimethod.py b/doc/ext/sphinx_autodoc_multimethod.py deleted file mode 100644 index 86527d825..000000000 --- a/doc/ext/sphinx_autodoc_multimethod.py +++ /dev/null @@ -1,293 +0,0 @@ -from types import ModuleType -from typing import Any, List, Tuple, ValuesView -from multimethod import multimethod -import re - -from sphinx.ext.autosummary import Autosummary -from sphinx.ext.autosummary import ( - get_import_prefixes_from_env, - ImportExceptionGroup, - mangle_signature, - extract_summary, -) -from docutils.statemachine import StringList -from sphinx.pycode import ModuleAnalyzer, PycodeError - -from sphinx.ext.autodoc import MethodDocumenter as SphinxMethodDocumenter - -from sphinx.util import inspect, logging -from sphinx.util.inspect import evaluate_signature, safe_getattr, stringify_signature -from sphinx.util.typing import get_type_hints - -logger = logging.getLogger(__name__) - - -def get_first(obj): - """Use to return first element (first param type annotation or first registered multimethod).""" - return next(iter(obj)) - - -patindent = re.compile(r"(\W*)") - - -def process_docstring_multimethod(app, what, name, obj, options, lines): - """multimethod docstring customization - - Remove extraneous signatures and combine docstrings if docstring also defined - in registered methods. Requires sphinx-build -E if rebuilding docs. - """ - - methods = [] - - if what == "method" and isinstance(obj, multimethod): - # instance or static method - - # handle functools.singledispatch style register (multiple names) - if obj.pending: - methods = set(m.__name__ for m in obj.pending) - else: - methods = set(m.__name__ for m in obj.values()) - - elif what == "method" and inspect.isclassmethod(obj) and hasattr(obj, "pending"): - - if obj.pending: - methods = set(m.__name__ for m in obj.pending) - else: - methods = set(m.__name__ for m in obj.__func__.values()) - - if methods: - lines_replace = [] - patsig = re.compile(rf"\W*[{'|'.join(methods)}]+\(.*\).*") - - indent = -1 - for line in lines: - if indent < 0: - # fix indent when multiple docstrings defined - if m := patindent.match(line): - indent = len(m.group(1)) - else: - indent = 0 - - if patsig.match(line): - lines_replace.append("") - else: - lines_replace.append(line[indent:]) - - del lines[:] - lines.extend(lines_replace) - - -class MultimethodAutosummary(Autosummary): - """Customize autosummary multimethod signature.""" - - def get_items(self, names: List[str]) -> List[Tuple[str, str, str, str]]: - """Try to import the given names, and return a list of - ``[(name, signature, summary_string, real_name), ...]``. - """ - prefixes = get_import_prefixes_from_env(self.env) - - items: List[Tuple[str, str, str, str]] = [] - - max_item_chars = 50 - - for name in names: - display_name = name - if name.startswith("~"): - name = name[1:] - display_name = name.split(".")[-1] - - try: - real_name, obj, parent, modname = self.import_by_name( - name, prefixes=prefixes - ) - except ImportExceptionGroup as exc: - errors = list( - set("* %s: %s" % (type(e).__name__, e) for e in exc.exceptions) - ) - logger.warning( - __("autosummary: failed to import %s.\nPossible hints:\n%s"), - name, - "\n".join(errors), - location=self.get_location(), - ) - continue - - self.bridge.result = StringList() # initialize for each documenter - full_name = real_name - if not isinstance(obj, ModuleType): - # give explicitly separated module name, so that members - # of inner classes can be documented - full_name = modname + "::" + full_name[len(modname) + 1 :] - # NB. using full_name here is important, since Documenters - # handle module prefixes slightly differently - documenter = self.create_documenter(self.env.app, obj, parent, full_name) - if not documenter.parse_name(): - logger.warning( - __("failed to parse name %s"), - real_name, - location=self.get_location(), - ) - items.append((display_name, "", "", real_name)) - continue - if not documenter.import_object(): - logger.warning( - __("failed to import object %s"), - real_name, - location=self.get_location(), - ) - items.append((display_name, "", "", real_name)) - continue - - # try to also get a source code analyzer for attribute docs - try: - documenter.analyzer = ModuleAnalyzer.for_module( - documenter.get_real_modname() - ) - # parse right now, to get PycodeErrors on parsing (results will - # be cached anyway) - documenter.analyzer.find_attr_docs() - except PycodeError as err: - logger.debug("[autodoc] module analyzer failed: %s", err) - # no source file -- e.g. for builtin and C modules - documenter.analyzer = None - - # -- Grab the signature - - try: - sig = documenter.format_signature(show_annotation=False) - # -- multimethod customization - if isinstance(obj, multimethod): - sig = "(...)" - # -- end customization - except TypeError: - # the documenter does not support ``show_annotation`` option - sig = documenter.format_signature() - - if not sig: - sig = "" - else: - max_chars = max(10, max_item_chars - len(display_name)) - sig = mangle_signature(sig, max_chars=max_chars) - - # -- Grab the summary - - documenter.add_content(None) - summary = extract_summary(self.bridge.result.data[:], self.state.document) - - items.append((display_name, sig, summary, real_name)) - - return items - - -class MethodDocumenter(SphinxMethodDocumenter): - """Customize to append multimethod signatures.""" - - def append_signature_multiple_dispatch(self, methods: ValuesView[Any]): - - sigs = [] - for dispatchmeth in methods: - documenter = MethodDocumenter(self.directive, "") - documenter.parent = self.parent - documenter.object = dispatchmeth - documenter.objpath = [None] - sigs.append(documenter.format_signature()) - - return sigs - - def format_signature(self, **kwargs: Any) -> str: - if self.config.autodoc_typehints_format == "short": - kwargs.setdefault("unqualified_typehints", True) - - sigs = [] - if ( - self.analyzer - and ".".join(self.objpath) in self.analyzer.overloads - and self.config.autodoc_typehints != "none" - ): - # Use signatures for overloaded methods instead of the implementation method. - overloaded = True - else: - overloaded = False - sig = super(SphinxMethodDocumenter, self).format_signature(**kwargs) - sigs.append(sig) - - meth = self.parent.__dict__.get(self.objpath[-1]) - if inspect.is_singledispatch_method(meth): - # append signature of singledispatch'ed functions - for typ, func in meth.dispatcher.registry.items(): - if typ is object: - pass # default implementation. skipped. - else: - dispatchmeth = self.annotate_to_first_argument(func, typ) - if dispatchmeth: - documenter = MethodDocumenter(self.directive, "") - documenter.parent = self.parent - documenter.object = dispatchmeth - documenter.objpath = [None] - sigs.append(documenter.format_signature()) - # -- multimethod customization - elif isinstance(meth, multimethod): - if meth.pending: - methods = meth.pending - else: - methods = set(meth.values()) - sigs = self.append_signature_multiple_dispatch(methods) - elif inspect.isclassmethod(self.object) and hasattr(self.object, "pending"): - if self.object.pending: - methods = self.object.pending - else: - methods = set(self.object.__func__.values()) - sigs = self.append_signature_multiple_dispatch(methods) - elif inspect.isstaticmethod(meth) and isinstance(self.object, multimethod): - sigs = [] - methods = self.object.values() - for dispatchmeth in methods: - actual = inspect.signature( - dispatchmeth, - bound_method=False, - type_aliases=self.config.autodoc_type_aliases, - ) - sig = stringify_signature(actual, **kwargs) - sigs.append(sig) - # -- end customization - if overloaded: - if inspect.isstaticmethod( - self.object, cls=self.parent, name=self.object_name - ): - actual = inspect.signature( - self.object, - bound_method=False, - type_aliases=self.config.autodoc_type_aliases, - ) - else: - actual = inspect.signature( - self.object, - bound_method=True, - type_aliases=self.config.autodoc_type_aliases, - ) - - __globals__ = safe_getattr(self.object, "__globals__", {}) - for overload in self.analyzer.overloads.get(".".join(self.objpath)): - overload = self.merge_default_value(actual, overload) - overload = evaluate_signature( - overload, __globals__, self.config.autodoc_type_aliases - ) - - if not inspect.isstaticmethod( - self.object, cls=self.parent, name=self.object_name - ): - parameters = list(overload.parameters.values()) - overload = overload.replace(parameters=parameters[1:]) - sig = stringify_signature(overload, **kwargs) - sigs.append(sig) - - return "\n".join(sigs) - - -def setup(app): - - app.connect("autodoc-process-docstring", process_docstring_multimethod) - app.add_directive("autosummary", MultimethodAutosummary, override=True) - app.add_autodocumenter(MethodDocumenter, override=True) - - return {"parallel_read_safe": True, "parallel_write_safe": True} diff --git a/doc/ext/sphinx_autodoc_multimethod/__init__.py b/doc/ext/sphinx_autodoc_multimethod/__init__.py new file mode 100644 index 000000000..8b29f11b5 --- /dev/null +++ b/doc/ext/sphinx_autodoc_multimethod/__init__.py @@ -0,0 +1,205 @@ +import re +from types import ModuleType +from typing import Any, List, Tuple, ValuesView + +import sphinx.ext.autodoc +from sphinx.ext.autosummary import Autosummary +from sphinx.ext.autosummary import ( + get_import_prefixes_from_env, + ImportExceptionGroup, + mangle_signature, + extract_summary, +) +from sphinx.locale import __ +from sphinx.ext.autodoc._directive_options import _AutoDocumenterOptions +from sphinx.ext.autodoc.directive import _AutodocAttrGetter +from sphinx.ext.autosummary import _get_documenter +from sphinx.ext.autodoc.importer import _load_object_by_name +from sphinx.ext.autodoc._docstrings import _prepare_docstrings, _process_docstrings +from sphinx.pycode import ModuleAnalyzer, PycodeError +from sphinx.util import logging + +from .importer import _format_signatures + +sphinx.ext.autodoc.importer._format_signatures = _format_signatures + +logger = logging.getLogger(__name__) +patindent = re.compile(r"(\W*)") + + +def process_docstring_multimethod(app, what, name, obj, options, lines): + """multimethod docstring customization + + Remove extraneous signatures and combine docstrings if docstring also defined + in registered method. + """ + + # get list of multimethod names identified during signature formatting + from .importer import MM_NAMES + + if name not in MM_NAMES: + return + + lines_replace = [] + patsig = re.compile(rf"\W*[{'|'.join(name)}]+\(.*\).*") + + indent = -1 + prevline = " \t" + for line in lines: + if indent < 0: + # fix indent when multiple docstrings defined + if m := patindent.match(line): + indent = len(m.group(1)) + else: + indent = 0 + + if patsig.match(line) and prevline == "": + lines_replace.append("") + else: + lines_replace.append(line[indent:]) + prevline = line + + del lines[:] + lines.extend(lines_replace) + + +class MultimethodAutosummary(Autosummary): + """Customize autosummary multimethod signature. + + Display signature as "(...)" to indicate multiple argument dispatching. + """ + + def get_items(self, names: list[str]) -> list[tuple[str, str | None, str, str]]: + """Try to import the given names, and return a list of + ``[(name, signature, summary_string, real_name), ...]``. + + signature is already formatted and is None if :nosignatures: option was given. + """ + prefixes = get_import_prefixes_from_env(self.env) + + items: list[tuple[str, str | None, str, str]] = [] + + signatures_option = self.options.get("signatures") + if signatures_option is None: + signatures_option = "none" if "nosignatures" in self.options else "long" + if signatures_option not in {"none", "short", "long"}: + msg = ( + "Invalid value for autosummary :signatures: option: " + f"{signatures_option!r}. Valid values are 'none', 'short', 'long'" + ) + raise ValueError(msg) + + env = self.env + config = env.config + current_document = env.current_document + events = env.events + get_attr = _AutodocAttrGetter(env._registry.autodoc_attrgetters) + opts = _AutoDocumenterOptions() + + max_item_chars = 50 + + for name in names: + display_name = name + if name.startswith("~"): + name = name[1:] + display_name = name.split(".")[-1] + + try: + real_name, obj, parent, modname = self.import_by_name( + name, prefixes=prefixes + ) + except ImportExceptionGroup as exc: + errors = list({f"* {type(e).__name__}: {e}" for e in exc.exceptions}) + logger.warning( + __("autosummary: failed to import %s.\nPossible hints:\n%s"), + name, + "\n".join(errors), + location=self.get_location(), + ) + continue + + obj_type = _get_documenter(obj, parent) + if isinstance(obj, ModuleType): + full_name = real_name + else: + # give explicitly separated module name, so that members + # of inner classes can be documented + full_name = f"{modname}::{real_name[len(modname) + 1 :]}" + # NB. using full_name here is important, since Documenters + # handle module prefixes slightly differently + props = _load_object_by_name( + name=full_name, + objtype=obj_type, + mock_imports=config.autodoc_mock_imports, + type_aliases=config.autodoc_type_aliases, + current_document=current_document, + config=config, + env=env, + events=events, + get_attr=get_attr, + options=opts, + ) + if props is None: + logger.warning( + __("failed to import object %s"), + real_name, + location=self.get_location(), + ) + items.append((display_name, "", "", real_name)) + continue + + # try to also get a source code analyzer for attribute docs + real_module = props._obj___module__ or props.module_name + try: + analyzer = ModuleAnalyzer.for_module(real_module) + # parse right now, to get PycodeErrors on parsing (results will + # be cached anyway) + analyzer.analyze() + except PycodeError as err: + logger.debug("[autodoc] module analyzer failed: %s", err) + # no source file -- e.g. for builtin and C modules + analyzer = None + + # -- Grab the signature + + if signatures_option == "none": + sig = None + elif not props.signatures: + sig = "" + elif signatures_option == "short": + sig = "()" if props.signatures == ("()",) else "(…)" + else: # signatures_option == 'long' + max_chars = max(10, max_item_chars - len(display_name)) + # -- multimethod customization + if (obj_type == "method" or obj_type == "function") and isinstance( + props._obj, dict + ): + sig = "(...)" + else: + sig = mangle_signature( + "\n".join(props.signatures), max_chars=max_chars + ) + # -- end multimethod customization + + # -- Grab the summary + + # get content from docstrings or attribute documentation + attr_docs = {} if analyzer is None else analyzer.attr_docs + docstrings = _prepare_docstrings(props=props, attr_docs=attr_docs) + docstring_lines = _process_docstrings( + docstrings, events=events, props=props, options=opts, + ) + summary = extract_summary(list(docstring_lines), self.state.document) + + items.append((display_name, sig, summary, real_name)) + + return items + + +def setup(app): + + app.setup_extension("sphinx.ext.autodoc") + app.connect("autodoc-process-docstring", process_docstring_multimethod) + app.add_directive("autosummary", MultimethodAutosummary, override=True) + + return {"parallel_read_safe": True, "parallel_write_safe": True} diff --git a/doc/ext/sphinx_autodoc_multimethod/importer.py b/doc/ext/sphinx_autodoc_multimethod/importer.py new file mode 100644 index 000000000..c12cd3e98 --- /dev/null +++ b/doc/ext/sphinx_autodoc_multimethod/importer.py @@ -0,0 +1,381 @@ +"""Importer utilities for autodoc + +See sphinx.ext.autodoc.importer.py + +Modify _format_signatures for multimethod customization. +""" + +from __future__ import annotations + +import re +from typing import TYPE_CHECKING +from sphinx.errors import PycodeError +from sphinx.pycode import ModuleAnalyzer +from sphinx.util import inspect, logging +from sphinx.util.inspect import ( + _stringify_signature_to_parts, + evaluate_signature, + safe_getattr, +) +from inspect import Parameter + +if TYPE_CHECKING: + from collections.abc import Callable, Mapping, Sequence + from importlib.machinery import ModuleSpec + from typing import Any, Protocol, TypeAlias + + from sphinx.config import Config + from sphinx.events import EventManager + from sphinx.ext.autodoc._directive_options import _AutoDocumenterOptions + + _FormattedSignature: TypeAlias = tuple[str, str] + + class _AttrGetter(Protocol): + def __call__(self, obj: Any, name: str, default: Any = ..., /) -> Any: + ... + + +from sphinx.ext.autodoc.importer import ( + _extract_signatures_from_docstring, + _extract_signature_from_object, + _annotate_to_first_argument, + _merge_default_value, +) + +logger = logging.getLogger(__name__) + +# create list of identified multimethod names +MM_NAMES = [] + + +def append_signature_multiple_dispatch(self, methods: ValuesView[Any]): + + sigs = [] + for dispatchmeth in methods: + documenter = MethodDocumenter(self.directive, "") + documenter.parent = self.parent + documenter.object = dispatchmeth + documenter.objpath = [None] + sigs.append(documenter.format_signature()) + + return sigs + + +def _format_multimethod_signatures( + config: Config, + events: EventManager, + get_attr: _AttrGetter, + parent: Any, + props: _ItemProperties, +): + signatures = [] + + parts = [props.module_name] + parts.extend(props.parts) + name = ".".join(parts) + MM_NAMES.append(name) + for _, f in props._obj.items(): + props._obj = f + sig = _extract_signature_from_object( + config=config, events=events, get_attr=get_attr, parent=parent, props=props, + ) + signatures.append(sig[0]) + return signatures + + +def _format_signatures( + *, + config: Config, + events: EventManager, + get_attr: _AttrGetter, + parent: Any, + options: _AutoDocumenterOptions, + props: _ItemProperties, + args: str | None = None, + retann: str | None = "", + **kwargs: Any, +) -> list[_FormattedSignature]: + """Format the signature (arguments and return annotation) of the object. + + Let the user process it via the ``autodoc-process-signature`` event. + """ + if props.obj_type in {"class", "exception"}: + from sphinx.ext.autodoc._property_types import _ClassDefProperties + + assert isinstance(props, _ClassDefProperties) + if props.doc_as_attr: + return [] + if config.autodoc_class_signature == "separated": + # do not show signatures + return [] + + if config.autodoc_typehints_format == "short": + kwargs.setdefault("unqualified_typehints", True) + if config.python_display_short_literal_types: + kwargs.setdefault("short_literals", True) + + if args is None: + signatures: list[_FormattedSignature] = [] + else: + signatures = [(args, retann or "")] + + if ( + not signatures + and config.autodoc_docstring_signature + and props.obj_type not in {"module", "data", "type"} + ): + # only act if a signature is not explicitly given already, + # and if the feature is enabled + signatures[:] = _extract_signatures_from_docstring( + config=config, + get_attr=get_attr, + options=options, + parent=parent, + props=props, + ) + + if not signatures: + # try to introspect the signature + try: + signatures[:] = _extract_signature_from_object( + config=config, + events=events, + get_attr=get_attr, + parent=parent, + props=props, + **kwargs, + ) + except Exception as exc: + msg = __("error while formatting arguments for %s: %s") + logger.warning(msg, props.full_name, exc, type="autodoc") + + if props.obj_type in {"attribute", "property"}: + # Only keep the return annotation + signatures = [("", retann) for _args, retann in signatures] + + if result := events.emit_firstresult( + "autodoc-process-signature", + props.obj_type, + props.full_name, + props._obj, + options, + signatures[0][0] if signatures else None, # args + signatures[0][1] if signatures else "", # retann + ): + if len(result) == 2 and isinstance(result[0], str): + args, retann = result + signatures[0] = (args, retann if isinstance(retann, str) else "") + + if props.obj_type in {"module", "data", "type"}: + signatures[1:] = () # discard all signatures save the first + + if real_modname := props._obj___module__ or props.module_name: + try: + analyzer = ModuleAnalyzer.for_module(real_modname) + # parse right now, to get PycodeErrors on parsing (results will + # be cached anyway) + analyzer.analyze() + except PycodeError as exc: + logger.debug("[autodoc] module analyzer failed: %s", exc) + # no source file -- e.g. for builtin and C modules + analyzer = None + else: + analyzer = None + + if props.obj_type in {"function", "decorator"}: + overloaded = ( + analyzer is not None + and props.dotted_parts in analyzer.overloads + and config.autodoc_typehints != "none" + ) + is_singledispatch = inspect.is_singledispatch_function(props._obj) + + if overloaded: + # Use signatures for overloaded functions and methods instead of + # their implementations. + signatures.clear() + # -- multimethod customization + elif isinstance(props._obj, dict): + return _format_multimethod_signatures( + config, events, get_attr, parent, props + ) + # -- end multimethod customization + elif not is_singledispatch: + return signatures + + if is_singledispatch: + from sphinx.ext.autodoc._property_types import _FunctionDefProperties + + # append signature of singledispatch'ed functions + for typ, func in props._obj.registry.items(): + if typ is object: + continue # default implementation. skipped. + dispatch_func = _annotate_to_first_argument( + func, typ, config=config, props=props + ) + if not dispatch_func: + continue + dispatch_props = _FunctionDefProperties( + obj_type="function", + module_name="", + parts=("",), + docstring_lines=(), + signatures=(), + _obj=dispatch_func, + _obj___module__=None, + _obj___qualname__=None, + _obj___name__=None, + properties=frozenset(), + ) + signatures += _format_signatures( + config=config, + events=events, + get_attr=get_attr, + parent=None, + options=options, + props=dispatch_props, + ) + if overloaded and analyzer is not None: + actual = inspect.signature( + props._obj, type_aliases=config.autodoc_type_aliases + ) + obj_globals = safe_getattr(props._obj, "__globals__", {}) + overloads = analyzer.overloads[props.dotted_parts] + for overload in overloads: + overload = _merge_default_value(actual, overload) + overload = evaluate_signature( + overload, obj_globals, config.autodoc_type_aliases + ) + signatures.append(_stringify_signature_to_parts(overload, **kwargs)) + + return signatures + + if props.obj_type in {"class", "exception"}: + from sphinx.ext.autodoc._property_types import _ClassDefProperties + + assert isinstance(props, _ClassDefProperties) + method_name = props._signature_method_name + if method_name == "__call__": + signature_cls = type(props._obj) + else: + signature_cls = props._obj + overloads = [] + overloaded = False + if method_name: + for cls in signature_cls.__mro__: + try: + analyzer = ModuleAnalyzer.for_module(cls.__module__) + analyzer.analyze() + except PycodeError: + pass + else: + qualname = f"{cls.__qualname__}.{method_name}" + if qualname in analyzer.overloads: + overloads = analyzer.overloads[qualname] + overloaded = True + break + if qualname in analyzer.tagorder: + # the constructor is defined in the class, but not overridden. + break + if overloaded and config.autodoc_typehints != "none": + # Use signatures for overloaded methods instead of the implementation method. + signatures.clear() + method = safe_getattr(signature_cls, method_name, None) + method_globals = safe_getattr(method, "__globals__", {}) + for overload in overloads: + overload = evaluate_signature( + overload, method_globals, config.autodoc_type_aliases + ) + + parameters = list(overload.parameters.values()) + overload = overload.replace( + parameters=parameters[1:], return_annotation=Parameter.empty + ) + signatures.append(_stringify_signature_to_parts(overload, **kwargs)) + return signatures + + return signatures + + if props.obj_type == "method": + overloaded = ( + analyzer is not None + and props.dotted_parts in analyzer.overloads + and config.autodoc_typehints != "none" + ) + meth = parent.__dict__.get(props.name) + is_singledispatch = inspect.is_singledispatch_method(meth) + + if overloaded: + # Use signatures for overloaded functions and methods instead of + # their implementations. + signatures.clear() + # -- multimethod customization + elif isinstance(props._obj, dict): + return _format_multimethod_signatures( + config, events, get_attr, parent, props + ) + # -- end multimethod customization + elif not is_singledispatch: + return signatures + + if is_singledispatch: + from sphinx.ext.autodoc._property_types import _FunctionDefProperties + + # append signature of singledispatch'ed methods + for typ, func in meth.dispatcher.registry.items(): + if typ is object: + continue # default implementation. skipped. + if inspect.isclassmethod(func): + func = func.__func__ + dispatch_meth = _annotate_to_first_argument( + func, typ, config=config, props=props + ) + if not dispatch_meth: + continue + dispatch_props = _FunctionDefProperties( + obj_type="method", + module_name="", + parts=("",), + docstring_lines=(), + signatures=(), + _obj=dispatch_meth, + _obj___module__=None, + _obj___qualname__=None, + _obj___name__=None, + properties=frozenset(), + ) + signatures += _format_signatures( + config=config, + events=events, + get_attr=get_attr, + parent=parent, + options=options, + props=dispatch_props, + ) + if overloaded and analyzer is not None: + from sphinx.ext.autodoc._property_types import _FunctionDefProperties + + assert isinstance(props, _FunctionDefProperties) + actual = inspect.signature( + props._obj, + bound_method=not props.is_staticmethod, + type_aliases=config.autodoc_type_aliases, + ) + + obj_globals = safe_getattr(props._obj, "__globals__", {}) + overloads = analyzer.overloads[props.dotted_parts] + for overload in overloads: + overload = _merge_default_value(actual, overload) + overload = evaluate_signature( + overload, obj_globals, config.autodoc_type_aliases + ) + + if not props.is_staticmethod: + # hide the first argument (e.g. 'self') + parameters = list(overload.parameters.values()) + overload = overload.replace(parameters=parameters[1:]) + signatures.append(_stringify_signature_to_parts(overload, **kwargs)) + + return signatures + + return signatures diff --git a/environment.yml b/environment.yml index f30b0fbf5..c8212bfe1 100644 --- a/environment.yml +++ b/environment.yml @@ -7,8 +7,8 @@ dependencies: - ocp=7.8.1 - vtk=*=qt* - pyparsing>=2.1.9 - - sphinx=8.1.3 - - sphinx_rtd_theme +# - sphinx=8.1.3 +# - sphinx_rtd_theme - mypy - codecov - pytest @@ -29,5 +29,7 @@ dependencies: - trame-vtk - pip - pip: + - sphinx_rtd_theme + - git+https://github.com/sphinx-doc/sphinx.git@de745a9 - --editable=. - git+https://github.com/cadquery/black.git@cq