Merge branch '1.7'

This commit is contained in:
Takeshi KOMIYA 2018-08-19 02:19:06 +09:00
commit 7f52ef7512
7 changed files with 110 additions and 23 deletions

View File

@ -264,6 +264,8 @@ 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
* #5306: autodoc: ``getargspec()`` raises NameError for invalid typehints
* #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

View File

@ -333,8 +333,9 @@ class BuildEnvironment(object):
self.all_docs[docname] = other.all_docs[docname] self.all_docs[docname] = other.all_docs[docname]
if docname in other.reread_always: if docname in other.reread_always:
self.reread_always.add(docname) self.reread_always.add(docname)
if docname in other.included:
self.included.add(docname) for docname in other.included:
self.included.add(docname)
for domainname, domain in self.domains.items(): for domainname, domain in self.domains.items():
domain.merge_domaindata(docnames, other.domaindata[domainname]) domain.merge_domaindata(docnames, other.domaindata[domainname])

View File

@ -33,7 +33,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
@ -401,7 +401,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
@ -475,9 +477,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):
@ -601,9 +602,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
@ -1029,12 +1028,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:
@ -1102,7 +1100,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()
@ -1317,8 +1315,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):
@ -1350,7 +1347,7 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
@staticmethod @staticmethod
def is_function_or_method(obj): def is_function_or_method(obj):
# type: (Any) -> bool # type: (Any) -> bool
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):

View File

@ -130,8 +130,11 @@ def formatargspec(function, args, varargs=None, varkw=None, defaults=None,
else: else:
return value return value
introspected_hints = (typing.get_type_hints(function) # type: ignore try:
if typing and hasattr(function, '__code__') else {}) introspected_hints = (typing.get_type_hints(function) # type: ignore
if typing and hasattr(function, '__code__') else {})
except Exception:
introspected_hints = {}
fd = StringIO() fd = StringIO()
fd.write('(') fd.write('(')

View File

@ -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
@ -102,8 +103,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."""
@ -158,6 +157,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."""
@ -201,6 +206,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."""
@ -616,7 +633,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
@ -696,7 +713,7 @@ else:
return doc return doc
return None return None
def getdoc(object): def _getdoc(object):
# type: (Any) -> unicode # type: (Any) -> unicode
"""Get the documentation string for an object. """Get the documentation string for an object.
@ -715,3 +732,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

View 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"

View File

@ -1374,6 +1374,44 @@ def test_mocked_module_imports(app):
] ]
@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.sphinx('html', testroot='ext-autodoc') @pytest.mark.sphinx('html', testroot='ext-autodoc')