Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rebase sage_autodoc to sphinx 8.1.3 #38957

Merged
merged 2 commits into from
Nov 16, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 67 additions & 29 deletions src/sage_docbuild/ext/sage_autodoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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__)


Expand Down Expand Up @@ -225,15 +235,17 @@ 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.

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``.
"""
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -2991,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_attrgettrs.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)

Expand Down
Loading