mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #5305 from tk0miya/5211_autodoc_for_partial_functions
Fix #5211: autodoc: No docs generated for functools.partial functions
This commit is contained in:
commit
2604920232
1
CHANGES
1
CHANGES
@ -29,6 +29,7 @@ Bugs fixed
|
|||||||
* #5280: autodoc: Fix wrong type annotations for complex typing
|
* #5280: autodoc: Fix wrong type annotations for complex typing
|
||||||
* autodoc: Optional types are wrongly rendered
|
* autodoc: Optional types are wrongly rendered
|
||||||
* #5291: autodoc crashed by ForwardRef types
|
* #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
|
* #5298: imgmath: math_number_all causes equations to have two numbers in html
|
||||||
|
|
||||||
Testing
|
Testing
|
||||||
|
@ -32,7 +32,7 @@ from sphinx.util import rpartition, force_decode
|
|||||||
from sphinx.util.docstrings import prepare_docstring
|
from sphinx.util.docstrings import prepare_docstring
|
||||||
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, \
|
||||||
isenumattribute, isclassmethod, isstaticmethod, getdoc
|
isenumattribute, isclassmethod, isstaticmethod, isfunction, isbuiltin, ispartial, getdoc
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
# For type annotation
|
# For type annotation
|
||||||
@ -399,7 +399,9 @@ class Documenter(object):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
modname = self.get_attr(self.object, '__module__', None)
|
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 False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -473,9 +475,8 @@ class Documenter(object):
|
|||||||
def get_doc(self, encoding=None, ignore=1):
|
def get_doc(self, encoding=None, ignore=1):
|
||||||
# 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 = getdoc(self.object, self.get_attr,
|
||||||
if docstring is None and self.env.config.autodoc_inherit_docstrings:
|
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):
|
||||||
@ -599,9 +600,7 @@ class Documenter(object):
|
|||||||
# if isattr is True, the member is documented as an attribute
|
# if isattr is True, the member is documented as an attribute
|
||||||
isattr = False
|
isattr = False
|
||||||
|
|
||||||
doc = self.get_attr(member, '__doc__', None)
|
doc = getdoc(member, self.get_attr, self.env.config.autodoc_inherit_docstrings)
|
||||||
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
|
||||||
@ -1022,12 +1021,11 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
|||||||
@classmethod
|
@classmethod
|
||||||
def can_document_member(cls, member, membername, isattr, parent):
|
def can_document_member(cls, member, membername, isattr, parent):
|
||||||
# type: (Any, unicode, bool, Any) -> bool
|
# type: (Any, unicode, bool, Any) -> bool
|
||||||
return inspect.isfunction(member) or inspect.isbuiltin(member)
|
return isfunction(member) or isbuiltin(member)
|
||||||
|
|
||||||
def format_args(self):
|
def format_args(self):
|
||||||
# type: () -> unicode
|
# type: () -> unicode
|
||||||
if inspect.isbuiltin(self.object) or \
|
if isbuiltin(self.object) or inspect.ismethoddescriptor(self.object):
|
||||||
inspect.ismethoddescriptor(self.object):
|
|
||||||
# cannot introspect arguments of a C function or method
|
# cannot introspect arguments of a C function or method
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
@ -1095,7 +1093,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
|||||||
# __init__ written in C?
|
# __init__ written in C?
|
||||||
if initmeth is None or \
|
if initmeth is None or \
|
||||||
is_builtin_class_method(self.object, '__init__') 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
|
return None
|
||||||
try:
|
try:
|
||||||
return Signature(initmeth, bound_method=True, has_retval=False).format_args()
|
return Signature(initmeth, bound_method=True, has_retval=False).format_args()
|
||||||
@ -1304,8 +1302,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
|
|||||||
|
|
||||||
def format_args(self):
|
def format_args(self):
|
||||||
# type: () -> unicode
|
# type: () -> unicode
|
||||||
if inspect.isbuiltin(self.object) or \
|
if isbuiltin(self.object) or inspect.ismethoddescriptor(self.object):
|
||||||
inspect.ismethoddescriptor(self.object):
|
|
||||||
# can never get arguments of a C function or method
|
# can never get arguments of a C function or method
|
||||||
return None
|
return None
|
||||||
if isstaticmethod(self.object, cls=self.parent, name=self.object_name):
|
if isstaticmethod(self.object, cls=self.parent, name=self.object_name):
|
||||||
@ -1336,7 +1333,7 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_function_or_method(obj):
|
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
|
@classmethod
|
||||||
def can_document_member(cls, member, membername, isattr, parent):
|
def can_document_member(cls, member, membername, isattr, parent):
|
||||||
|
@ -15,6 +15,7 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
import typing
|
import typing
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
from six import PY2, PY3, StringIO, binary_type, string_types, itervalues
|
from six import PY2, PY3, StringIO, binary_type, string_types, itervalues
|
||||||
from six.moves import builtins
|
from six.moves import builtins
|
||||||
@ -99,8 +100,6 @@ if PY3:
|
|||||||
kwonlyargs, kwdefaults, annotations)
|
kwonlyargs, kwdefaults, annotations)
|
||||||
|
|
||||||
else: # 2.7
|
else: # 2.7
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
def getargspec(func):
|
def getargspec(func):
|
||||||
# type: (Any) -> Any
|
# type: (Any) -> Any
|
||||||
"""Like inspect.getargspec but supports functools.partial as well."""
|
"""Like inspect.getargspec but supports functools.partial as well."""
|
||||||
@ -155,6 +154,12 @@ def isenumattribute(x):
|
|||||||
return isinstance(x, enum.Enum)
|
return isinstance(x, enum.Enum)
|
||||||
|
|
||||||
|
|
||||||
|
def ispartial(obj):
|
||||||
|
# type: (Any) -> bool
|
||||||
|
"""Check if the object is partial."""
|
||||||
|
return isinstance(obj, partial)
|
||||||
|
|
||||||
|
|
||||||
def isclassmethod(obj):
|
def isclassmethod(obj):
|
||||||
# type: (Any) -> bool
|
# type: (Any) -> bool
|
||||||
"""Check if the object is classmethod."""
|
"""Check if the object is classmethod."""
|
||||||
@ -198,6 +203,18 @@ def isdescriptor(x):
|
|||||||
return False
|
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):
|
def safe_getattr(obj, name, *defargs):
|
||||||
# type: (Any, unicode, unicode) -> object
|
# type: (Any, unicode, unicode) -> object
|
||||||
"""A getattr() that turns all exceptions into AttributeErrors."""
|
"""A getattr() that turns all exceptions into AttributeErrors."""
|
||||||
@ -601,7 +618,7 @@ class Signature(object):
|
|||||||
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 5):
|
if sys.version_info >= (3, 5):
|
||||||
getdoc = inspect.getdoc
|
_getdoc = inspect.getdoc
|
||||||
else:
|
else:
|
||||||
# code copied from the inspect.py module of the standard library
|
# code copied from the inspect.py module of the standard library
|
||||||
# of Python 3.5
|
# of Python 3.5
|
||||||
@ -679,7 +696,7 @@ else:
|
|||||||
return doc
|
return doc
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def getdoc(object):
|
def _getdoc(object):
|
||||||
"""Get the documentation string for an object.
|
"""Get the documentation string for an object.
|
||||||
|
|
||||||
All tabs are expanded to spaces. To clean up docstrings that are
|
All tabs are expanded to spaces. To clean up docstrings that are
|
||||||
@ -697,3 +714,21 @@ else:
|
|||||||
if not isinstance(doc, str):
|
if not isinstance(doc, str):
|
||||||
return None
|
return None
|
||||||
return inspect.cleandoc(doc)
|
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
tests/roots/test-ext-autodoc/target/partialfunction.py
Normal file
11
tests/roots/test-ext-autodoc/target/partialfunction.py
Normal file
@ -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"
|
@ -912,6 +912,44 @@ def test_generate():
|
|||||||
'module', 'autodoc_missing_imports')
|
'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),
|
@pytest.mark.skipif(sys.version_info < (3, 4),
|
||||||
reason='functools.partialmethod is available on py34 or above')
|
reason='functools.partialmethod is available on py34 or above')
|
||||||
@pytest.mark.usefixtures('setup_test')
|
@pytest.mark.usefixtures('setup_test')
|
||||||
|
Loading…
Reference in New Issue
Block a user