Merge pull request #6353 from tk0miya/6311_autosummary_confused_by_complex_typehints2

Fix #6311: autosummary: autosummary table gets confused by complex type hints
This commit is contained in:
Takeshi KOMIYA 2019-05-13 21:51:11 +09:00 committed by GitHub
commit 774c59821c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 77 additions and 36 deletions

View File

@ -108,6 +108,7 @@ Bugs fixed
* #6351: "Hyperlink target is not referenced" message is shown even if
referenced
* #6165: autodoc: ``tab_width`` setting of docutils has been ignored
* #6311: autosummary: autosummary table gets confused by complex type hints
* Generated Makefiles lack a final EOL (refs: #6232)
Testing

View File

@ -365,8 +365,8 @@ class Documenter:
return False
return True
def format_args(self):
# type: () -> str
def format_args(self, **kwargs):
# type: (Any) -> str
"""Format the argument signature of *self.object*.
Should return None if the object does not have a signature.
@ -385,8 +385,8 @@ class Documenter:
# directives of course)
return '.'.join(self.objpath) or self.modname
def format_signature(self):
# type: () -> str
def format_signature(self, **kwargs):
# type: (Any) -> str
"""Format the signature (arguments and return annotation) of the object.
Let the user process it via the ``autodoc-process-signature`` event.
@ -397,7 +397,13 @@ class Documenter:
else:
# try to introspect the signature
try:
args = self.format_args()
try:
args = self.format_args(**kwargs)
except TypeError:
warnings.warn("Documenter.format_args() takes keyword arguments "
"since Sphinx-2.1",
RemovedInSphinx40Warning)
args = self.format_args()
except Exception as err:
logger.warning(__('error while formatting arguments for %s: %s') %
(self.fullname, err), type='autodoc')
@ -956,15 +962,15 @@ class DocstringSignatureMixin:
return lines
return super().get_doc(None, ignore) # type: ignore
def format_signature(self):
# type: () -> str
def format_signature(self, **kwargs):
# type: (Any) -> str
if self.args is None and self.env.config.autodoc_docstring_signature: # type: ignore
# only act if a signature is not explicitly given already, and if
# the feature is enabled
result = self._find_signature()
if result is not None:
self.args, self.retann = result
return super().format_signature() # type: ignore
return super().format_signature(**kwargs) # type: ignore
class DocstringStripSignatureMixin(DocstringSignatureMixin):
@ -972,8 +978,8 @@ class DocstringStripSignatureMixin(DocstringSignatureMixin):
Mixin for AttributeDocumenter to provide the
feature of stripping any function signature from the docstring.
"""
def format_signature(self):
# type: () -> str
def format_signature(self, **kwargs):
# type: (Any) -> str
if self.args is None and self.env.config.autodoc_docstring_signature: # type: ignore
# only act if a signature is not explicitly given already, and if
# the feature is enabled
@ -983,7 +989,7 @@ class DocstringStripSignatureMixin(DocstringSignatureMixin):
# DocstringSignatureMixin.format_signature.
# Documenter.format_signature use self.args value to format.
_args, self.retann = result
return super().format_signature()
return super().format_signature(**kwargs)
class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: ignore
@ -1000,8 +1006,8 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
return (inspect.isfunction(member) or inspect.isbuiltin(member) or
(inspect.isroutine(member) and isinstance(parent, ModuleDocumenter)))
def format_args(self):
# type: () -> str
def format_args(self, **kwargs):
# type: (Any) -> str
if inspect.isbuiltin(self.object) or inspect.ismethoddescriptor(self.object):
# cannot introspect arguments of a C function or method
return None
@ -1011,9 +1017,9 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
not inspect.isbuiltin(self.object) and
not inspect.isclass(self.object) and
hasattr(self.object, '__call__')):
args = Signature(self.object.__call__).format_args()
args = Signature(self.object.__call__).format_args(**kwargs)
else:
args = Signature(self.object).format_args()
args = Signature(self.object).format_args(**kwargs)
except TypeError:
if (inspect.is_builtin_class_method(self.object, '__new__') and
inspect.is_builtin_class_method(self.object, '__init__')):
@ -1024,10 +1030,10 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
# signature without the first argument.
try:
sig = Signature(self.object.__new__, bound_method=True, has_retval=False)
args = sig.format_args()
args = sig.format_args(**kwargs)
except TypeError:
sig = Signature(self.object.__init__, bound_method=True, has_retval=False)
args = sig.format_args()
args = sig.format_args(**kwargs)
# escape backslashes for reST
args = args.replace('\\', '\\\\')
@ -1055,8 +1061,8 @@ class DecoratorDocumenter(FunctionDocumenter):
# must be lower than FunctionDocumenter
priority = -1
def format_args(self):
args = super().format_args()
def format_args(self, **kwargs):
args = super().format_args(**kwargs)
if ',' in args:
return args
else:
@ -1099,8 +1105,8 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
self.doc_as_attr = True
return ret
def format_args(self):
# type: () -> str
def format_args(self, **kwargs):
# type: (Any) -> str
# for classes, the relevant signature is the __init__ method's
initmeth = self.get_attr(self.object, '__init__', None)
# classes without __init__ method, default __init__ or
@ -1110,18 +1116,19 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
not(inspect.ismethod(initmeth) or inspect.isfunction(initmeth)):
return None
try:
return Signature(initmeth, bound_method=True, has_retval=False).format_args()
sig = Signature(initmeth, bound_method=True, has_retval=False)
return sig.format_args(**kwargs)
except TypeError:
# still not possible: happens e.g. for old-style classes
# with __init__ in C
return None
def format_signature(self):
# type: () -> str
def format_signature(self, **kwargs):
# type: (Any) -> str
if self.doc_as_attr:
return ''
return super().format_signature()
return super().format_signature(**kwargs)
def add_directive_header(self, sig):
# type: (str) -> None
@ -1312,15 +1319,15 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
return ret
def format_args(self):
# type: () -> str
def format_args(self, **kwargs):
# type: (Any) -> str
if inspect.isbuiltin(self.object) or inspect.ismethoddescriptor(self.object):
# can never get arguments of a C function or method
return None
if inspect.isstaticmethod(self.object, cls=self.parent, name=self.object_name):
args = Signature(self.object, bound_method=False).format_args()
args = Signature(self.object, bound_method=False).format_args(**kwargs)
else:
args = Signature(self.object, bound_method=True).format_args()
args = Signature(self.object, bound_method=True).format_args(**kwargs)
# escape backslashes for reST
args = args.replace('\\', '\\\\')
return args

View File

@ -329,7 +329,12 @@ class Autosummary(SphinxDirective):
# -- Grab the signature
sig = documenter.format_signature()
try:
sig = documenter.format_signature(show_annotation=False)
except TypeError:
# the documenter does not support ``show_annotation`` option
sig = documenter.format_signature()
if not sig:
sig = ''
else:
@ -445,7 +450,7 @@ def mangle_signature(sig, max_chars=30):
args = [] # type: List[str]
opts = [] # type: List[str]
opt_re = re.compile(r"^(.*, |)([a-zA-Z0-9_*]+)=")
opt_re = re.compile(r"^(.*, |)([a-zA-Z0-9_*]+)\s*=\s*")
while s:
m = opt_re.search(s)
if not m:

View File

@ -413,8 +413,8 @@ class Signature:
else:
return None
def format_args(self):
# type: () -> str
def format_args(self, show_annotation=True):
# type: (bool) -> str
args = []
last_kind = None
for i, param in enumerate(self.parameters.values()):
@ -435,7 +435,7 @@ class Signature:
param.POSITIONAL_OR_KEYWORD,
param.KEYWORD_ONLY):
arg.write(param.name)
if param.annotation is not param.empty:
if show_annotation and param.annotation is not param.empty:
if isinstance(param.annotation, str) and param.name in self.annotations:
arg.write(': ')
arg.write(self.format_annotation(self.annotations[param.name]))

View File

@ -1,4 +1,5 @@
from os import * # NOQA
from typing import Union
class Foo:
@ -11,3 +12,7 @@ class Foo:
@property
def baz(self):
pass
def bar(x: Union[int, str], y: int = 1):
pass

View File

@ -8,4 +8,5 @@
autosummary_dummy_module
autosummary_dummy_module.Foo
autosummary_dummy_module.bar
autosummary_importfail

View File

@ -13,9 +13,13 @@ from io import StringIO
from unittest.mock import Mock
import pytest
from docutils import nodes
from sphinx.ext.autosummary import mangle_signature, import_by_name, extract_summary
from sphinx.testing.util import etree_parse
from sphinx import addnodes
from sphinx.ext.autosummary import (
autosummary_table, autosummary_toc, mangle_signature, import_by_name, extract_summary
)
from sphinx.testing.util import assert_node, etree_parse
from sphinx.util.docutils import new_document
html_warnfile = StringIO()
@ -179,6 +183,24 @@ def test_escaping(app, status, warning):
def test_autosummary_generate(app, status, warning):
app.builder.build_all()
doctree = app.env.get_doctree('index')
assert_node(doctree, (nodes.paragraph,
nodes.paragraph,
addnodes.tabular_col_spec,
autosummary_table,
autosummary_toc))
assert_node(doctree[3],
[autosummary_table, nodes.table, nodes.tgroup, (nodes.colspec,
nodes.colspec,
[nodes.tbody, (nodes.row,
nodes.row,
nodes.row,
nodes.row)])])
assert doctree[3][0][0][2][0].astext() == 'autosummary_dummy_module\n\n'
assert doctree[3][0][0][2][1].astext() == 'autosummary_dummy_module.Foo()\n\n'
assert doctree[3][0][0][2][2].astext() == 'autosummary_dummy_module.bar(x[, y])\n\n'
assert doctree[3][0][0][2][3].astext() == 'autosummary_importfail\n\n'
module = (app.srcdir / 'generated' / 'autosummary_dummy_module.rst').text()
assert (' .. autosummary::\n'
' \n'