Skip to content

Commit

Permalink
Fix sphinx-doc#6311: autosummary: autosummary table gets confused by …
Browse files Browse the repository at this point in the history
…complex type hints
  • Loading branch information
tk0miya committed May 12, 2019
1 parent b5cb9b4 commit d290dfd
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 36 deletions.
1 change: 1 addition & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------
Expand Down
65 changes: 36 additions & 29 deletions sphinx/ext/autodoc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,8 +365,8 @@ def check_module(self):
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.
Expand All @@ -385,8 +385,8 @@ def format_name(self):
# 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.
Expand All @@ -397,7 +397,13 @@ def format_signature(self):
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')
Expand Down Expand Up @@ -953,24 +959,24 @@ def get_doc(self, encoding=None, ignore=1):
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):
"""
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
Expand All @@ -980,7 +986,7 @@ def format_signature(self):
# 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
Expand All @@ -997,8 +1003,8 @@ def can_document_member(cls, member, membername, isattr, parent):
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
Expand All @@ -1008,9 +1014,9 @@ def format_args(self):
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__')):
Expand All @@ -1021,10 +1027,10 @@ def format_args(self):
# 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('\\', '\\\\')
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -1096,8 +1102,8 @@ def import_object(self):
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
Expand All @@ -1107,18 +1113,19 @@ def format_args(self):
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
Expand Down Expand Up @@ -1307,15 +1314,15 @@ def import_object(self):

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
Expand Down
9 changes: 7 additions & 2 deletions sphinx/ext/autosummary/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,12 @@ def get_items(self, names):

# -- 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:
Expand Down Expand Up @@ -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:
Expand Down
6 changes: 3 additions & 3 deletions sphinx/util/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,8 +407,8 @@ def return_annotation(self):
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()):
Expand All @@ -429,7 +429,7 @@ def format_args(self):
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]))
Expand Down
5 changes: 5 additions & 0 deletions tests/roots/test-ext-autosummary/autosummary_dummy_module.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from os import * # NOQA
from typing import Union


class Foo:
Expand All @@ -11,3 +12,7 @@ def bar(self):
@property
def baz(self):
pass


def bar(x: Union[int, str], y: int = 1):
pass
1 change: 1 addition & 0 deletions tests/roots/test-ext-autosummary/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@

autosummary_dummy_module
autosummary_dummy_module.Foo
autosummary_dummy_module.bar
autosummary_importfail
26 changes: 24 additions & 2 deletions tests/test_ext_autosummary.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@
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()
Expand Down Expand Up @@ -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'
Expand Down

0 comments on commit d290dfd

Please sign in to comment.