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

Fix #9250: autodoc: autodoc_inherited_docstrings doesn't effect to classes #9262

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES
Expand Up @@ -55,6 +55,8 @@ Bugs fixed
undocumented
* #9185: autodoc: typehints for overloaded functions and methods are inaccurate
* #9250: autodoc: The inherited method not having docstring is wrongly parsed
* #9250: autodoc: :confval:`autodoc_inherited_docstrings` does not effect to the
docstring of classes
* #9217: manpage: The name of manpage directory that is generated by
:confval:`man_make_section_directory` is not correct
* #9224: ``:param:`` and ``:type:`` fields does not support a type containing
Expand Down
10 changes: 5 additions & 5 deletions sphinx/ext/autodoc/__init__.py
Expand Up @@ -31,8 +31,8 @@
from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.util import inspect, logging
from sphinx.util.docstrings import prepare_docstring, separate_metadata
from sphinx.util.inspect import (evaluate_signature, getdoc, object_description, safe_getattr,
stringify_signature)
from sphinx.util.inspect import (evaluate_signature, getclassdoc, getdoc, object_description,
safe_getattr, stringify_signature)
from sphinx.util.typing import OptionSpec, get_type_hints, restify
from sphinx.util.typing import stringify as stringify_typehint

Expand Down Expand Up @@ -1694,9 +1694,9 @@ def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]:
classdoc_from = self.options.get('class-doc-from', self.config.autoclass_content)

docstrings = []
attrdocstring = self.get_attr(self.object, '__doc__', None)
if attrdocstring:
docstrings.append(attrdocstring)
classdocstring = getclassdoc(self.object, self.config.autodoc_inherit_docstrings)
if classdocstring:
docstrings.append(classdocstring)

# for classes, what the "docstring" is can be controlled via a
# config value; the default is only the class docstring
Expand Down
8 changes: 8 additions & 0 deletions sphinx/ext/autodoc/mock.py
Expand Up @@ -165,6 +165,14 @@ def ismock(subject: Any) -> bool:
if isinstance(subject, _MockModule):
return True

try:
# check the object is a mock class
__mro__ = safe_getattr(subject, '__mro__', [])
if len(__mro__) > 2 and __mro__[1] is _MockObject:
return True
except AttributeError:
pass

try:
# check the object is mocked object
__mro__ = safe_getattr(type(subject), '__mro__', [])
Expand Down
18 changes: 18 additions & 0 deletions sphinx/util/inspect.py
Expand Up @@ -824,6 +824,24 @@ def signature_from_ast(node: ast.FunctionDef, code: str = '') -> inspect.Signatu
return inspect.Signature(params, return_annotation=return_annotation)


def getclassdoc(obj: Type, allow_inherited: bool = False) -> Optional[str]:
"""Get the docstring for the class."""
from sphinx.ext.autodoc.mock import ismock

if not allow_inherited:
return safe_getattr(obj, '__doc__', None)
else:
for basecls in getmro(obj)[:-1]:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This excepts the object class. But other built-in classes are not excepted yet. So the docstring of list, dict, str, int and so on are fetched. I think it's an unexpected broken change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh...

>>> inspect.isbuiltin(list)
False
>>> list.__module__
'builtins'

if ismock(basecls):
break

doc = safe_getattr(basecls, '__doc__', None)
if doc is not None:
return doc

return None


def getdoc(obj: Any, attrgetter: Callable = safe_getattr,
allow_inherited: bool = False, cls: Any = None, name: str = None) -> str:
"""Get the docstring for the object.
Expand Down
2 changes: 2 additions & 0 deletions tests/test_ext_autodoc.py
Expand Up @@ -2296,6 +2296,8 @@ def test_overload2(app):
' Baz(x: str, y: str)',
' :module: target.overload2',
'',
' docstring',
'',
]


Expand Down
23 changes: 23 additions & 0 deletions tests/test_util_inspect.py
Expand Up @@ -671,6 +671,29 @@ def func1(a, b, c):
assert inspect.unpartial(func3) is func1


def test_getclassdoc():
class Foo:
"""docstring"""

class Bar(Foo):
pass

assert inspect.getclassdoc(Foo) == "docstring"
assert inspect.getclassdoc(Bar) is None
assert inspect.getclassdoc(Bar, True) == "docstring"


def test_getclassdoc_mocked():
from sphinx.ext.autodoc.mock import _MockModule

dummy = _MockModule('dummy')

class Foo(dummy.Dummy): # type: ignore
pass

assert inspect.getclassdoc(Foo) is None


def test_getdoc_inherited_decorated_method():
class Foo:
def meth(self):
Expand Down