mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #4012 from avalentino/docstring-inheritance
Docstring inheritance
This commit is contained in:
commit
8b76d5b064
2
AUTHORS
2
AUTHORS
@ -57,7 +57,7 @@ Other contributors, listed alphabetically, are:
|
|||||||
* Stefan Seefeld -- toctree improvements
|
* Stefan Seefeld -- toctree improvements
|
||||||
* Gregory Szorc -- performance improvements
|
* Gregory Szorc -- performance improvements
|
||||||
* Taku Shimizu -- epub3 builder
|
* Taku Shimizu -- epub3 builder
|
||||||
* Antonio Valentino -- qthelp builder
|
* Antonio Valentino -- qthelp builder, docstring inheritance
|
||||||
* Filip Vavera -- napoleon todo directive
|
* Filip Vavera -- napoleon todo directive
|
||||||
* Pauli Virtanen -- autodoc improvements, autosummary extension
|
* Pauli Virtanen -- autodoc improvements, autosummary extension
|
||||||
* Stefan van der Walt -- autosummary extension
|
* Stefan van der Walt -- autosummary extension
|
||||||
|
1
CHANGES
1
CHANGES
@ -28,6 +28,7 @@ Features added
|
|||||||
- ``ref.python`` (ref: #3866)
|
- ``ref.python`` (ref: #3866)
|
||||||
* #3872: Add latex key to configure literal blocks caption position in PDF
|
* #3872: Add latex key to configure literal blocks caption position in PDF
|
||||||
output (refs #3792, #1723)
|
output (refs #3792, #1723)
|
||||||
|
* In case of missing docstring try to retrieve doc from base classes (ref: #3140)
|
||||||
|
|
||||||
|
|
||||||
Features removed
|
Features removed
|
||||||
|
@ -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
|
If ``False`` is given, autodoc forcely suppresses the error if the imported
|
||||||
module emits warnings. By default, ``True``.
|
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
|
Docstring preprocessing
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ from sphinx.util import logging
|
|||||||
from sphinx.util.nodes import nested_parse_with_titles
|
from sphinx.util.nodes import nested_parse_with_titles
|
||||||
from sphinx.util.inspect import Signature, isdescriptor, safe_getmembers, \
|
from sphinx.util.inspect import Signature, isdescriptor, safe_getmembers, \
|
||||||
safe_getattr, object_description, is_builtin_class_method, \
|
safe_getattr, object_description, is_builtin_class_method, \
|
||||||
isenumclass, isenumattribute
|
isenumclass, isenumattribute, getdoc
|
||||||
from sphinx.util.docstrings import prepare_docstring
|
from sphinx.util.docstrings import prepare_docstring
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
@ -525,6 +525,8 @@ class Documenter(object):
|
|||||||
# type: (unicode, int) -> List[List[unicode]]
|
# type: (unicode, int) -> List[List[unicode]]
|
||||||
"""Decode and return lines of the docstring(s) for the object."""
|
"""Decode and return lines of the docstring(s) for the object."""
|
||||||
docstring = self.get_attr(self.object, '__doc__', None)
|
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
|
# make sure we have Unicode docstrings, then sanitize and split
|
||||||
# into lines
|
# into lines
|
||||||
if isinstance(docstring, text_type):
|
if isinstance(docstring, text_type):
|
||||||
@ -682,6 +684,9 @@ class Documenter(object):
|
|||||||
isattr = False
|
isattr = False
|
||||||
|
|
||||||
doc = self.get_attr(member, '__doc__', None)
|
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
|
# if the member __doc__ is the same as self's __doc__, it's just
|
||||||
# inherited and therefore not the member's doc
|
# inherited and therefore not the member's doc
|
||||||
cls = self.get_attr(member, '__class__', None)
|
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_docstring_signature', True, True)
|
||||||
app.add_config_value('autodoc_mock_imports', [], True)
|
app.add_config_value('autodoc_mock_imports', [], True)
|
||||||
app.add_config_value('autodoc_warningiserror', True, 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-docstring')
|
||||||
app.add_event('autodoc-process-signature')
|
app.add_event('autodoc-process-signature')
|
||||||
app.add_event('autodoc-skip-member')
|
app.add_event('autodoc-skip-member')
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
import typing
|
import typing
|
||||||
import inspect
|
import inspect
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
@ -456,3 +457,102 @@ class Signature(object):
|
|||||||
', '.join(param_strings))
|
', '.join(param_strings))
|
||||||
|
|
||||||
return qualified_name
|
return qualified_name
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 5):
|
||||||
|
getdoc = inspect.getdoc
|
||||||
|
else:
|
||||||
|
# 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
|
||||||
|
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
|
||||||
|
|
||||||
|
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) and getattr(obj, '__self__', None):
|
||||||
|
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) or inspect.ismethod(obj):
|
||||||
|
name = obj.__name__
|
||||||
|
cls = _findclass(obj)
|
||||||
|
if cls is None or getattr(cls, name) != 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)
|
||||||
|
@ -431,6 +431,13 @@ def test_get_doc():
|
|||||||
directive.env.config.autoclass_content = 'both'
|
directive.env.config.autoclass_content = 'both'
|
||||||
assert getdocl('class', I) == ['Class docstring', '', 'New docstring']
|
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')
|
@pytest.mark.usefixtures('setup_test')
|
||||||
def test_docstring_processing():
|
def test_docstring_processing():
|
||||||
@ -941,6 +948,12 @@ class Base(object):
|
|||||||
"""Inherited function."""
|
"""Inherited function."""
|
||||||
|
|
||||||
|
|
||||||
|
class Derived(Base):
|
||||||
|
def inheritedmeth(self):
|
||||||
|
# no docstring here
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Class(Base):
|
class Class(Base):
|
||||||
"""Class to document."""
|
"""Class to document."""
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user