From 620491fcd55d4ff0b8365fddf78a2fca2659c04b Mon Sep 17 00:00:00 2001 From: Antonio Valentino Date: Fri, 18 Aug 2017 10:51:24 +0200 Subject: [PATCH 1/7] 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) From 23a40f88d04667bddbf6d50a6d04853bfd62a294 Mon Sep 17 00:00:00 2001 From: Antonio Valentino Date: Fri, 18 Aug 2017 11:05:33 +0200 Subject: [PATCH 2/7] Improve fomatting (make flake8 happy) --- sphinx/util/inspect.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 2128ec5fe..390505c71 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -491,7 +491,8 @@ else: name = obj.__func__.__name__ self = obj.__self__ if (inspect.isclass(self) and - getattr(getattr(self, name, None), '__func__') is obj.__func__): + getattr(getattr(self, name, None), '__func__') + is obj.__func__): # classmethod cls = self else: @@ -505,7 +506,7 @@ else: name = obj.__name__ self = obj.__self__ if (inspect.isclass(self) and - self.__qualname__ + '.' + name == obj.__qualname__): + self.__qualname__ + '.' + name == obj.__qualname__): # classmethod cls = self else: From beac2f8647f97aa1a2dc40a5ce0426ab2d51a644 Mon Sep 17 00:00:00 2001 From: Antonio Valentino Date: Fri, 18 Aug 2017 11:26:10 +0200 Subject: [PATCH 3/7] Update AUTHORS and CHANGES --- AUTHORS | 2 +- CHANGES | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 580feeb32..c531d5ac1 100644 --- a/AUTHORS +++ b/AUTHORS @@ -57,7 +57,7 @@ Other contributors, listed alphabetically, are: * Stefan Seefeld -- toctree improvements * Gregory Szorc -- performance improvements * Taku Shimizu -- epub3 builder -* Antonio Valentino -- qthelp builder +* Antonio Valentino -- qthelp builder, docstring ingeritance * Filip Vavera -- napoleon todo directive * Pauli Virtanen -- autodoc improvements, autosummary extension * Stefan van der Walt -- autosummary extension diff --git a/CHANGES b/CHANGES index 6eaf62af2..bee7cbf32 100644 --- a/CHANGES +++ b/CHANGES @@ -28,6 +28,7 @@ Features added - ``ref.python`` (ref: #3866) * #3872: Add latex key to configure literal blocks caption position in PDF output (refs #3792, #1723) +* In case of missing docstring try to retrieve doc from base classes (ref: #3140) Features removed From 5148d6953b5cb46a47f3f88554d46614cf170009 Mon Sep 17 00:00:00 2001 From: Antonio Valentino Date: Fri, 18 Aug 2017 19:26:39 +0200 Subject: [PATCH 4/7] Typo --- AUTHORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index c531d5ac1..96d08788f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -57,7 +57,7 @@ Other contributors, listed alphabetically, are: * Stefan Seefeld -- toctree improvements * Gregory Szorc -- performance improvements * Taku Shimizu -- epub3 builder -* Antonio Valentino -- qthelp builder, docstring ingeritance +* Antonio Valentino -- qthelp builder, docstring inheritance * Filip Vavera -- napoleon todo directive * Pauli Virtanen -- autodoc improvements, autosummary extension * Stefan van der Walt -- autosummary extension From 96ee24d7c1025e25417901b375f68731fc1c6085 Mon Sep 17 00:00:00 2001 From: Antonio Valentino Date: Sat, 19 Aug 2017 21:52:30 +0200 Subject: [PATCH 5/7] Basic test for docstring inheritance --- tests/test_autodoc.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index caf31b7e9..af94de80e 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -431,6 +431,13 @@ def test_get_doc(): directive.env.config.autoclass_content = 'both' assert getdocl('class', I) == ['Class docstring', '', 'New docstring'] + # NOTE: inspect.getdoc seems not to work with locally defined classes + directive.env.config.autodoc_inherit_docstrings = False + assert getdocl('method', Base.inheritedmeth) == ['Inherited function.'] + assert getdocl('method', Derived.inheritedmeth) == [] + directive.env.config.autodoc_inherit_docstrings = True + assert getdocl('method', Derived.inheritedmeth) == ['Inherited function.'] + @pytest.mark.usefixtures('setup_test') def test_docstring_processing(): @@ -941,6 +948,12 @@ class Base(object): """Inherited function.""" +class Derived(Base): + def inheritedmeth(self): + # no docstring here + pass + + class Class(Base): """Class to document.""" From d454bb7472273a6106b249e353cdf83e31c8e342 Mon Sep 17 00:00:00 2001 From: Antonio Valentino Date: Sun, 20 Aug 2017 11:10:35 +0200 Subject: [PATCH 6/7] Fix compatibility with Python 2 --- sphinx/util/inspect.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 390505c71..d75a86dc8 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -462,15 +462,18 @@ class Signature(object): if sys.version_info >= (3, 5): getdoc = inspect.getdoc else: - # code copyed from the inspect.py module of the standard library + # code copied 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 hasattr(func, 'im_class'): + cls = func.im_class + else: + for name in func.__qualname__.split('.')[:-1]: + cls = getattr(cls, name) if not inspect.isclass(cls): return None return cls @@ -487,7 +490,7 @@ else: return doc return None - if inspect.ismethod(obj): + if inspect.ismethod(obj) and getattr(obj, '__self__' , None): name = obj.__func__.__name__ self = obj.__self__ if (inspect.isclass(self) and @@ -497,10 +500,10 @@ else: cls = self else: cls = self.__class__ - elif inspect.isfunction(obj): + elif inspect.isfunction(obj) or inspect.ismethod(obj): name = obj.__name__ cls = _findclass(obj) - if cls is None or getattr(cls, name) is not obj: + if cls is None or getattr(cls, name) != obj: return None elif inspect.isbuiltin(obj): name = obj.__name__ From 849c19c0e58d8817252aec7e8a98c5d4e554039c Mon Sep 17 00:00:00 2001 From: Antonio Valentino Date: Sun, 20 Aug 2017 11:48:13 +0200 Subject: [PATCH 7/7] PEP8 --- sphinx/util/inspect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index d75a86dc8..a2928fc7e 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -490,7 +490,7 @@ else: return doc return None - if inspect.ismethod(obj) and getattr(obj, '__self__' , None): + if inspect.ismethod(obj) and getattr(obj, '__self__', None): name = obj.__func__.__name__ self = obj.__self__ if (inspect.isclass(self) and