Skip to content

Commit

Permalink
Fix sphinx-doc#5211: autodoc: No docs generated for functools.partial…
Browse files Browse the repository at this point in the history
… functions
  • Loading branch information
tk0miya committed Aug 15, 2018
1 parent 8d0170e commit 1e67832
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 18 deletions.
1 change: 1 addition & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Bugs fixed
font with XeLaTeX/LuaLateX (refs: #5251)
* #5280: autodoc: Fix wrong type annotations for complex typing
* autodoc: Optional types are wrongly rendered
* #5211: autodoc: No docs generated for functools.partial functions

Testing
--------
Expand Down
22 changes: 8 additions & 14 deletions sphinx/ext/autodoc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from sphinx.util.docstrings import prepare_docstring
from sphinx.util.inspect import Signature, isdescriptor, safe_getmembers, \
safe_getattr, object_description, is_builtin_class_method, \
isenumattribute, isclassmethod, isstaticmethod, getdoc
isenumattribute, isclassmethod, isstaticmethod, isfunction, isbuiltin, getdoc

if False:
# For type annotation
Expand Down Expand Up @@ -473,9 +473,7 @@ def add_directive_header(self, sig):
def get_doc(self, encoding=None, ignore=1):
# 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)
docstring = getdoc(self.object, self.get_attr, self.env.config.autodoc_inherit_docstrings)
# make sure we have Unicode docstrings, then sanitize and split
# into lines
if isinstance(docstring, text_type):
Expand Down Expand Up @@ -599,9 +597,7 @@ def filter_members(self, members, want_all):
# if isattr is True, the member is documented as an attribute
isattr = False

doc = self.get_attr(member, '__doc__', None)
if doc is None and self.env.config.autodoc_inherit_docstrings:
doc = getdoc(member)
doc = getdoc(member, self.get_attr, self.env.config.autodoc_inherit_docstrings)

# if the member __doc__ is the same as self's __doc__, it's just
# inherited and therefore not the member's doc
Expand Down Expand Up @@ -1022,12 +1018,11 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
@classmethod
def can_document_member(cls, member, membername, isattr, parent):
# type: (Any, unicode, bool, Any) -> bool
return inspect.isfunction(member) or inspect.isbuiltin(member)
return isfunction(member) or isbuiltin(member)

def format_args(self):
# type: () -> unicode
if inspect.isbuiltin(self.object) or \
inspect.ismethoddescriptor(self.object):
if isbuiltin(self.object) or inspect.ismethoddescriptor(self.object):
# cannot introspect arguments of a C function or method
return None
try:
Expand Down Expand Up @@ -1095,7 +1090,7 @@ def format_args(self):
# __init__ written in C?
if initmeth is None or \
is_builtin_class_method(self.object, '__init__') or \
not(inspect.ismethod(initmeth) or inspect.isfunction(initmeth)):
not(inspect.ismethod(initmeth) or isfunction(initmeth)):
return None
try:
return Signature(initmeth, bound_method=True, has_retval=False).format_args()
Expand Down Expand Up @@ -1304,8 +1299,7 @@ def import_object(self):

def format_args(self):
# type: () -> unicode
if inspect.isbuiltin(self.object) or \
inspect.ismethoddescriptor(self.object):
if isbuiltin(self.object) or inspect.ismethoddescriptor(self.object):
# can never get arguments of a C function or method
return None
if isstaticmethod(self.object, cls=self.parent, name=self.object_name):
Expand Down Expand Up @@ -1336,7 +1330,7 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):

@staticmethod
def is_function_or_method(obj):
return inspect.isfunction(obj) or inspect.isbuiltin(obj) or inspect.ismethod(obj)
return isfunction(obj) or isbuiltin(obj) or inspect.ismethod(obj)

@classmethod
def can_document_member(cls, member, membername, isattr, parent):
Expand Down
43 changes: 39 additions & 4 deletions sphinx/util/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import sys
import typing
from collections import OrderedDict
from functools import partial

from six import PY2, PY3, StringIO, binary_type, string_types, itervalues
from six.moves import builtins
Expand Down Expand Up @@ -99,8 +100,6 @@ def getargspec(func):
kwonlyargs, kwdefaults, annotations)

else: # 2.7
from functools import partial

def getargspec(func):
# type: (Any) -> Any
"""Like inspect.getargspec but supports functools.partial as well."""
Expand Down Expand Up @@ -155,6 +154,12 @@ def isenumattribute(x):
return isinstance(x, enum.Enum)


def ispartial(obj):
# type: (Any) -> bool
"""Check if the object is partial."""
return isinstance(obj, partial)


def isclassmethod(obj):
# type: (Any) -> bool
"""Check if the object is classmethod."""
Expand Down Expand Up @@ -198,6 +203,18 @@ def isdescriptor(x):
return False


def isfunction(obj):
# type: (Any) -> bool
"""Check if the object is function."""
return inspect.isfunction(obj) or ispartial(obj) and inspect.isfunction(obj.func)


def isbuiltin(obj):
# type: (Any) -> bool
"""Check if the object is builtin."""
return inspect.isbuiltin(obj) or ispartial(obj) and inspect.isbuiltin(obj.func)


def safe_getattr(obj, name, *defargs):
# type: (Any, unicode, unicode) -> object
"""A getattr() that turns all exceptions into AttributeErrors."""
Expand Down Expand Up @@ -597,7 +614,7 @@ def format_annotation_old(self, annotation):


if sys.version_info >= (3, 5):
getdoc = inspect.getdoc
_getdoc = inspect.getdoc
else:
# code copied from the inspect.py module of the standard library
# of Python 3.5
Expand Down Expand Up @@ -675,7 +692,7 @@ def _finddoc(obj):
return doc
return None

def getdoc(object):
def _getdoc(object):
"""Get the documentation string for an object.
All tabs are expanded to spaces. To clean up docstrings that are
Expand All @@ -693,3 +710,21 @@ def getdoc(object):
if not isinstance(doc, str):
return None
return inspect.cleandoc(doc)


def getdoc(obj, attrgetter=safe_getattr, allow_inherited=False):
# type: (Any, Callable, bool) -> unicode
"""Get the docstring for the object.
This tries to obtain the docstring for some kind of objects additionally:
* partial functions
* inherited docstring
"""
doc = attrgetter(obj, '__doc__', None)
if ispartial(obj) and doc == obj.__class__.__doc__:
return getdoc(obj.func)
elif doc is None and allow_inherited:
doc = _getdoc(obj)

return doc
11 changes: 11 additions & 0 deletions tests/roots/test-ext-autodoc/target/partialfunction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from functools import partial


def func1():
"""docstring of func1"""
pass


func2 = partial(func1)
func3 = partial(func1)
func3.__doc__ = "docstring of func3"
38 changes: 38 additions & 0 deletions tests/test_autodoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,44 @@ def assert_order(items, objtype, name, member_order, **kw):
'module', 'autodoc_missing_imports')


@pytest.mark.usefixtures('setup_test')
def test_partialfunction():
def call_autodoc(objtype, name):
inst = app.registry.documenters[objtype](directive, name)
inst.generate()
result = list(directive.result)
del directive.result[:]
return result

options.members = ALL
#options.undoc_members = True
expected = [
'',
'.. py:module:: target.partialfunction',
'',
'',
'.. py:function:: func1()',
' :module: target.partialfunction',
'',
' docstring of func1',
' ',
'',
'.. py:function:: func2()',
' :module: target.partialfunction',
'',
' docstring of func1',
' ',
'',
'.. py:function:: func3()',
' :module: target.partialfunction',
'',
' docstring of func3',
' '
]

assert call_autodoc('module', 'target.partialfunction') == expected


@pytest.mark.skipif(sys.version_info < (3, 4),
reason='functools.partialmethod is available on py34 or above')
@pytest.mark.usefixtures('setup_test')
Expand Down

0 comments on commit 1e67832

Please sign in to comment.