summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakeshi KOMIYA <i.tkomiya@gmail.com>2018-08-17 02:06:20 +0900
committerGitHub <noreply@github.com>2018-08-17 02:06:20 +0900
commit2604920232c9c7588f2a6f1ebfa15c506661cb10 (patch)
treea75740101d8592cee4b9629b31f377dbd2622c02
parentb926ee8aeb620dfaa3bd28566b3cf7f900c71def (diff)
parentc4fa1aed340c32ff9b4b6cb2d5a6302951dea073 (diff)
downloadsphinx-git-2604920232c9c7588f2a6f1ebfa15c506661cb10.tar.gz
Merge pull request #5305 from tk0miya/5211_autodoc_for_partial_functions
Fix #5211: autodoc: No docs generated for functools.partial functions
-rw-r--r--CHANGES1
-rw-r--r--sphinx/ext/autodoc/__init__.py27
-rw-r--r--sphinx/util/inspect.py43
-rw-r--r--tests/roots/test-ext-autodoc/target/partialfunction.py11
-rw-r--r--tests/test_autodoc.py38
5 files changed, 101 insertions, 19 deletions
diff --git a/CHANGES b/CHANGES
index 58431cee8..e17247476 100644
--- a/CHANGES
+++ b/CHANGES
@@ -29,6 +29,7 @@ Bugs fixed
* #5280: autodoc: Fix wrong type annotations for complex typing
* autodoc: Optional types are wrongly rendered
* #5291: autodoc crashed by ForwardRef types
+* #5211: autodoc: No docs generated for functools.partial functions
* #5298: imgmath: math_number_all causes equations to have two numbers in html
Testing
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py
index 058fd7765..7d34e185d 100644
--- a/sphinx/ext/autodoc/__init__.py
+++ b/sphinx/ext/autodoc/__init__.py
@@ -32,7 +32,7 @@ from sphinx.util import rpartition, force_decode
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, ispartial, getdoc
if False:
# For type annotation
@@ -399,7 +399,9 @@ class Documenter(object):
return True
modname = self.get_attr(self.object, '__module__', None)
- if modname and modname != self.modname:
+ if ispartial(self.object) and modname == '_functools': # for pypy
+ return True
+ elif modname and modname != self.modname:
return False
return True
@@ -473,9 +475,8 @@ class Documenter(object):
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):
@@ -599,9 +600,7 @@ class Documenter(object):
# 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
@@ -1022,12 +1021,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:
@@ -1095,7 +1093,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
# __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()
@@ -1304,8 +1302,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
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):
@@ -1336,7 +1333,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):
diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py
index 64e93f267..3b5d48e2a 100644
--- a/sphinx/util/inspect.py
+++ b/sphinx/util/inspect.py
@@ -15,6 +15,7 @@ import re
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
@@ -99,8 +100,6 @@ if PY3:
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."""
@@ -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."""
@@ -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."""
@@ -601,7 +618,7 @@ class Signature(object):
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
@@ -679,7 +696,7 @@ else:
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
@@ -697,3 +714,21 @@ else:
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
diff --git a/tests/roots/test-ext-autodoc/target/partialfunction.py b/tests/roots/test-ext-autodoc/target/partialfunction.py
new file mode 100644
index 000000000..727a62680
--- /dev/null
+++ b/tests/roots/test-ext-autodoc/target/partialfunction.py
@@ -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"
diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py
index 53d8a1e14..97e03c886 100644
--- a/tests/test_autodoc.py
+++ b/tests/test_autodoc.py
@@ -912,6 +912,44 @@ def test_generate():
'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')