From 183ea892919abd3c535c637558d2ec41e32cedc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Bissey?= Date: Tue, 12 Nov 2024 19:47:11 +1300 Subject: [PATCH 1/2] rebase sage_autodoc to sphinx 8.1.3 --- src/sage_docbuild/ext/sage_autodoc.py | 87 ++++++++++++++++++--------- 1 file changed, 60 insertions(+), 27 deletions(-) diff --git a/src/sage_docbuild/ext/sage_autodoc.py b/src/sage_docbuild/ext/sage_autodoc.py index 87e4e69d7bd..b9d6dbae0c9 100644 --- a/src/sage_docbuild/ext/sage_autodoc.py +++ b/src/sage_docbuild/ext/sage_autodoc.py @@ -35,6 +35,8 @@ - François Bissey (2024-08-24): rebased on Sphinx 8.0.2 - François Bissey (2024-09-10): Tweaks to support python 3.9 (and older sphinx) as well + +- François Bissey (2024-11-12): rebased on Sphinx 8.1.3 (while trying to keep python 3.9 compatibility) """ from __future__ import annotations @@ -44,7 +46,7 @@ import sys import re from inspect import Parameter, Signature -from typing import TYPE_CHECKING, Any, ClassVar, NewType, TypeVar +from typing import TYPE_CHECKING, Any, NewType, TypeVar from docutils.statemachine import StringList @@ -86,11 +88,19 @@ def getdoc(obj, *args, **kwargs): if TYPE_CHECKING: from collections.abc import Callable, Iterator, Sequence from types import ModuleType + from typing import ClassVar, Literal, TypeAlias from sphinx.application import Sphinx from sphinx.environment import BuildEnvironment from sphinx.ext.autodoc.directive import DocumenterBridge + _AutodocObjType = Literal[ + 'module', 'class', 'exception', 'function', 'method', 'attribute' + ] + _AutodocProcessDocstringListener: TypeAlias = Callable[ + [Sphinx, _AutodocObjType, str, Any, dict[str, bool], list[str]], None + ] + logger = logging.getLogger(__name__) @@ -225,7 +235,9 @@ def merge_members_option(options: dict) -> None: # Some useful event listener factories for autodoc-process-docstring. -def cut_lines(pre: int, post: int = 0, what: str | None = None) -> Callable: +def cut_lines( + pre: int, post: int = 0, what: Sequence[str] | None = None +) -> _AutodocProcessDocstringListener: """Return a listener that removes the first *pre* and last *post* lines of every docstring. If *what* is a sequence of strings, only docstrings of a type in *what* will be processed. @@ -233,7 +245,7 @@ def cut_lines(pre: int, post: int = 0, what: str | None = None) -> Callable: Use like this (e.g. in the ``setup()`` function of :file:`conf.py`):: from sphinx.ext.autodoc import cut_lines - app.connect('autodoc-process-docstring', cut_lines(4, what=['module'])) + app.connect('autodoc-process-docstring', cut_lines(4, what={'module'})) This can (and should) be used in place of ``automodule_skip_lines``. """ @@ -250,9 +262,22 @@ def cut_lines(pre: int, post: int = 0, what: str | None = None) -> Callable: # # ... in place of ``automodule_skip_lines``. # ------------------------------------------------------------------------- - def process(app: Sphinx, what_: str, name: str, obj: Any, options: Any, lines: list[str], - ) -> None: - if what and what_ not in what: + if not what: + what_unique: frozenset[str] = frozenset() + elif isinstance(what, str): # strongly discouraged + what_unique = frozenset({what}) + else: + what_unique = frozenset(what) + + def process( + app: Sphinx, + what_: _AutodocObjType, + name: str, + obj: Any, + options: dict[str, bool], + lines: list[str], + ) -> None: + if what_unique and what_ not in what_unique: return del lines[:pre] if post: @@ -271,7 +296,7 @@ def between( what: Sequence[str] | None = None, keepempty: bool = False, exclude: bool = False, -) -> Callable: +) -> _AutodocProcessDocstringListener: """Return a listener that either keeps, or if *exclude* is True excludes, lines between lines that match the *marker* regular expression. If no line matches, the resulting docstring would be empty, so no change will be made @@ -282,8 +307,14 @@ def between( """ marker_re = re.compile(marker) - def process(app: Sphinx, what_: str, name: str, obj: Any, options: Any, lines: list[str], - ) -> None: + def process( + app: Sphinx, + what_: _AutodocObjType, + name: str, + obj: Any, + options: dict[str, bool], + lines: list[str], + ) -> None: if what and what_ not in what: return deleted = 0 @@ -308,7 +339,7 @@ def process(app: Sphinx, what_: str, name: str, obj: Any, options: Any, lines: l # This class is used only in ``sphinx.ext.autodoc.directive``, # But we define this class here to keep compatibility (see #4538) -class Options(dict): +class Options(dict[str, Any]): """A dict/attribute hybrid that returns None on nonexisting keys.""" def copy(self) -> Options: @@ -476,9 +507,10 @@ def import_object(self, raiseerror: bool = False) -> bool: """ with mock(self.config.autodoc_mock_imports): try: - ret = import_object(self.modname, self.objpath, self.objtype, - attrgetter=self.get_attr, - warningiserror=self.config.autodoc_warningiserror) + ret = import_object( + self.modname, self.objpath, self.objtype, + attrgetter=self.get_attr, + ) self.module, self.parent, self.object_name, self.object = ret if ismock(self.object): self.object = undecorate(self.object) @@ -1145,7 +1177,8 @@ def get_object_members(self, want_all: bool) -> tuple[bool, list[ObjectMember]]: else: logger.warning(__('missing attribute mentioned in :members: option: ' 'module %s, attribute %s'), - safe_getattr(self.object, '__name__', '???', name), + safe_getattr(self.object, '__name__', '???'), + name, type='autodoc') return False, ret @@ -2179,7 +2212,7 @@ def import_object(self, raiseerror: bool = False) -> bool: # annotation only instance variable (PEP-526) try: with mock(self.config.autodoc_mock_imports): - parent = import_module(self.modname, self.config.autodoc_warningiserror) + parent = import_module(self.modname) annotations = get_type_hints(parent, None, self.config.autodoc_type_aliases, include_extras=True) @@ -2629,9 +2662,10 @@ def import_object(self, raiseerror: bool = False) -> bool: except ImportError as exc: try: with mock(self.config.autodoc_mock_imports): - ret = import_object(self.modname, self.objpath[:-1], 'class', - attrgetter=self.get_attr, # type: ignore[attr-defined] - warningiserror=self.config.autodoc_warningiserror) + ret = import_object( + self.modname, self.objpath[:-1], 'class', + attrgetter=self.get_attr, # type: ignore[attr-defined] + ) parent = ret[3] if self.is_runtime_instance_attribute(parent): self.object = self.RUNTIME_INSTANCE_ATTRIBUTE @@ -2676,16 +2710,17 @@ def is_uninitialized_instance_attribute(self, parent: Any) -> bool: return self.objpath[-1] in annotations def import_object(self, raiseerror: bool = False) -> bool: - """Check the exisitence of uninitialized instance attribute when failed to import + """Check the existence of uninitialized instance attribute when failed to import the attribute. """ try: return super().import_object(raiseerror=True) # type: ignore[misc] except ImportError as exc: try: - ret = import_object(self.modname, self.objpath[:-1], 'class', - attrgetter=self.get_attr, # type: ignore[attr-defined] - warningiserror=self.config.autodoc_warningiserror) + ret = import_object( + self.modname, self.objpath[:-1], 'class', + attrgetter=self.get_attr, # type: ignore[attr-defined] + ) parent = ret[3] if self.is_uninitialized_instance_attribute(parent): self.object = UNINITIALIZED_ATTR @@ -2760,9 +2795,7 @@ def can_document_member( if isinstance(type(member), ClasscallMetaclass): return True # --------------------------------------------------------------------- - if not inspect.isroutine(member) and not isinstance(member, type): - return True - return False + return not inspect.isroutine(member) and not isinstance(member, type) def document_members(self, all_members: bool = False) -> None: pass @@ -2918,7 +2951,7 @@ def can_document_member( return False def import_object(self, raiseerror: bool = False) -> bool: - """Check the exisitence of uninitialized instance attribute when failed to import + """Check the existence of uninitialized instance attribute when failed to import the attribute. """ ret = super().import_object(raiseerror) @@ -2991,7 +3024,7 @@ def _get_property_getter(self) -> Callable | None: def autodoc_attrgetter(app: Sphinx, obj: Any, name: str, *defargs: Any) -> Any: """Alternative getattr() for types""" - for typ, func in app.registry.autodoc_attrgettrs.items(): + for typ, func in app.registry.autodoc_attrgetters.items(): if isinstance(obj, typ): return func(obj, name, *defargs) From 9783ba7d3c4b8b8ead33dc109718bea0ffd60a9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Bissey?= Date: Tue, 12 Nov 2024 20:29:30 +1300 Subject: [PATCH 2/2] try supporting old autodoc_attrgettrs attribute as well as new autodoc_attrgetters attribute --- src/sage_docbuild/ext/sage_autodoc.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/sage_docbuild/ext/sage_autodoc.py b/src/sage_docbuild/ext/sage_autodoc.py index b9d6dbae0c9..6f048222437 100644 --- a/src/sage_docbuild/ext/sage_autodoc.py +++ b/src/sage_docbuild/ext/sage_autodoc.py @@ -3024,9 +3024,14 @@ def _get_property_getter(self) -> Callable | None: def autodoc_attrgetter(app: Sphinx, obj: Any, name: str, *defargs: Any) -> Any: """Alternative getattr() for types""" - for typ, func in app.registry.autodoc_attrgetters.items(): - if isinstance(obj, typ): - return func(obj, name, *defargs) + try: + for typ, func in app.registry.autodoc_attrgetters.items(): + if isinstance(obj, typ): + return func(obj, name, *defargs) + except AttributeError: + for typ, func in app.registry.autodoc_attrgettrs.items(): + if isinstance(obj, typ): + return func(obj, name, *defargs) return safe_getattr(obj, name, *defargs)