Skip to content

Commit

Permalink
Fix sphinx-doc#9250: autodoc: autodoc_inherited_docstrings doesn't ef…
Browse files Browse the repository at this point in the history
…fect to classes

The docstring of the superclass is not extracted even if the docstring
of the subclass is empty and autodoc_inherited_docstrings=True.

This adds `sphinx.util.inspect.getclassdoc()` to respect the configuration
on getting docstring.
  • Loading branch information
tk0miya committed May 23, 2021
1 parent 830b3fb commit 99e3736
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 6 deletions.
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
10 changes: 9 additions & 1 deletion sphinx/ext/autodoc/mock.py
Expand Up @@ -165,10 +165,18 @@ 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__', [])
if len(__mro__) > 2 and __mro__[1] is _MockObject:
: if len(__mro__) > 2 and __mro__[1] is _MockObject:
return True
except AttributeError:
pass
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]:
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

0 comments on commit 99e3736

Please sign in to comment.