From 620491fcd55d4ff0b8365fddf78a2fca2659c04b Mon Sep 17 00:00:00 2001 From: Antonio Valentino Date: Fri, 18 Aug 2017 10:51:24 +0200 Subject: [PATCH] Retireve docstirng form base classes (Closes #3140) --- doc/ext/autodoc.rst | 10 ++++ sphinx/ext/autodoc/__init__.py | 8 ++- sphinx/util/inspect.py | 96 ++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) diff --git a/doc/ext/autodoc.rst b/doc/ext/autodoc.rst index 1f1892dbf..bfd55c81a 100644 --- a/doc/ext/autodoc.rst +++ b/doc/ext/autodoc.rst @@ -393,6 +393,16 @@ There are also new config values that you can set: If ``False`` is given, autodoc forcely suppresses the error if the imported module emits warnings. By default, ``True``. +.. confval:: autodoc_inherit_docstrings + + This value controls the docstrings inheritance. + If set to True the cocstring for classes or methods, if not explicitly set, + is inherited form parents. + + The default is ``True``. + + .. versionadded:: 1.7 + Docstring preprocessing ----------------------- diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 967cd9c5a..0af344751 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -35,7 +35,7 @@ from sphinx.util import logging from sphinx.util.nodes import nested_parse_with_titles from sphinx.util.inspect import Signature, isdescriptor, safe_getmembers, \ safe_getattr, object_description, is_builtin_class_method, \ - isenumclass, isenumattribute + isenumclass, isenumattribute, getdoc from sphinx.util.docstrings import prepare_docstring if False: @@ -525,6 +525,8 @@ class Documenter(object): # type: (unicode, int) -> List[List[unicode]] """Decode and return lines of the docstring(s) for the object.""" docstring = self.get_attr(self.object, '__doc__', None) + if docstring is None and self.env.config.autodoc_inherit_docstrings: + docstring = getdoc(self.object) # make sure we have Unicode docstrings, then sanitize and split # into lines if isinstance(docstring, text_type): @@ -682,6 +684,9 @@ class Documenter(object): isattr = False doc = self.get_attr(member, '__doc__', None) + if doc is None and self.env.config.autodoc_inherit_docstrings: + doc = getdoc(member) + # if the member __doc__ is the same as self's __doc__, it's just # inherited and therefore not the member's doc cls = self.get_attr(member, '__class__', None) @@ -1617,6 +1622,7 @@ def setup(app): app.add_config_value('autodoc_docstring_signature', True, True) app.add_config_value('autodoc_mock_imports', [], True) app.add_config_value('autodoc_warningiserror', True, True) + app.add_config_value('autodoc_inherit_docstrings', True, True) app.add_event('autodoc-process-docstring') app.add_event('autodoc-process-signature') app.add_event('autodoc-skip-member') diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 3f95dfcfe..2128ec5fe 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -11,6 +11,7 @@ from __future__ import absolute_import import re +import sys import typing import inspect from collections import OrderedDict @@ -456,3 +457,98 @@ class Signature(object): ', '.join(param_strings)) return qualified_name + + +if sys.version_info >= (3, 5): + getdoc = inspect.getdoc +else: + # code copyed from the inspect.py module of the standard library + # of Python 3.5 + + def _findclass(func): + cls = sys.modules.get(func.__module__) + if cls is None: + return None + for name in func.__qualname__.split('.')[:-1]: + cls = getattr(cls, name) + if not inspect.isclass(cls): + return None + return cls + + def _finddoc(obj): + if inspect.isclass(obj): + for base in obj.__mro__: + if base is not object: + try: + doc = base.__doc__ + except AttributeError: + continue + if doc is not None: + return doc + return None + + if inspect.ismethod(obj): + name = obj.__func__.__name__ + self = obj.__self__ + if (inspect.isclass(self) and + getattr(getattr(self, name, None), '__func__') is obj.__func__): + # classmethod + cls = self + else: + cls = self.__class__ + elif inspect.isfunction(obj): + name = obj.__name__ + cls = _findclass(obj) + if cls is None or getattr(cls, name) is not obj: + return None + elif inspect.isbuiltin(obj): + name = obj.__name__ + self = obj.__self__ + if (inspect.isclass(self) and + self.__qualname__ + '.' + name == obj.__qualname__): + # classmethod + cls = self + else: + cls = self.__class__ + # Should be tested before isdatadescriptor(). + elif isinstance(obj, property): + func = obj.fget + name = func.__name__ + cls = _findclass(func) + if cls is None or getattr(cls, name) is not obj: + return None + elif inspect.ismethoddescriptor(obj) or inspect.isdatadescriptor(obj): + name = obj.__name__ + cls = obj.__objclass__ + if getattr(cls, name) is not obj: + return None + else: + return None + + for base in cls.__mro__: + try: + doc = getattr(base, name).__doc__ + except AttributeError: + continue + if doc is not None: + return doc + return None + + def getdoc(object): + """Get the documentation string for an object. + + All tabs are expanded to spaces. To clean up docstrings that are + indented to line up with blocks of code, any whitespace than can be + uniformly removed from the second line onwards is removed.""" + try: + doc = object.__doc__ + except AttributeError: + return None + if doc is None: + try: + doc = _finddoc(object) + except (AttributeError, TypeError): + return None + if not isinstance(doc, str): + return None + return inspect.cleandoc(doc)