Fix #6311: autosummary: autosummary table gets confused by complex type hints

This commit is contained in:
Takeshi KOMIYA
2019-05-12 14:13:35 +09:00
parent b5cb9b4ca4
commit d290dfd7e9
7 changed files with 77 additions and 36 deletions

View File

@@ -95,6 +95,7 @@ Bugs fixed
* commented term in glossary directive is wrongly recognized
* #6299: rst domain: rst:directive directive generates waste space
* #6331: man: invalid output when doctest follows rubric
* #6311: autosummary: autosummary table gets confused by complex type hints
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')
@@ -953,15 +959,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):
@@ -969,8 +975,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
@@ -980,7 +986,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
@@ -997,8 +1003,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
@@ -1008,9 +1014,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__')):
@@ -1021,10 +1027,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('\\', '\\\\')
@@ -1052,8 +1058,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:
@@ -1096,8 +1102,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
@@ -1107,18 +1113,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
@@ -1307,15 +1314,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

@@ -407,8 +407,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()):
@@ -429,7 +429,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'