* Fix: autosummary can't handle nested classes correctly. Closes #1441

This commit is contained in:
Takayuki Shimizukawa 2014-06-21 17:05:31 +09:00
parent 1d742bdcf5
commit ff8ce91f77
5 changed files with 32 additions and 13 deletions

View File

@ -14,6 +14,7 @@ Bugs fixed
object to str implicitly" error when link target url has a hash part. object to str implicitly" error when link target url has a hash part.
Thanks to Jorge_C. Thanks to Jorge_C.
* #1467: Exception on Python3 if nonexistent method is specified by automethod * #1467: Exception on Python3 if nonexistent method is specified by automethod
* #1441: autosummary can't handle nested classes correctly.
Release 1.2.2 (released Mar 2, 2014) Release 1.2.2 (released Mar 2, 2014)
==================================== ====================================

View File

@ -243,16 +243,21 @@ class Autosummary(Directive):
display_name = name.split('.')[-1] display_name = name.split('.')[-1]
try: try:
real_name, obj, parent = import_by_name(name, prefixes=prefixes) real_name, obj, parent, modname = import_by_name(name, prefixes=prefixes)
except ImportError: except ImportError:
self.warn('failed to import %s' % name) self.warn('failed to import %s' % name)
items.append((name, '', '', name)) items.append((name, '', '', name))
continue continue
# NB. using real_name here is important, since Documenters
# handle module prefixes slightly differently
self.result = ViewList() # initialize for each documenter self.result = ViewList() # initialize for each documenter
documenter = get_documenter(obj, parent)(self, real_name) full_name = real_name
if full_name.startswith(modname + '.'):
# give explicitly separated module name, so that members
# of inner classes can be documented
full_name = modname + '::' + full_name[len(modname)+1:]
# NB. using full_name here is important, since Documenters
# handle module prefixes slightly differently
documenter = get_documenter(obj, parent)(self, full_name)
if not documenter.parse_name(): if not documenter.parse_name():
self.warn('failed to parse name %s' % real_name) self.warn('failed to parse name %s' % real_name)
items.append((display_name, '', '', real_name)) items.append((display_name, '', '', real_name))
@ -447,8 +452,8 @@ def import_by_name(name, prefixes=[None]):
prefixed_name = '.'.join([prefix, name]) prefixed_name = '.'.join([prefix, name])
else: else:
prefixed_name = name prefixed_name = name
obj, parent = _import_by_name(prefixed_name) obj, parent, modname = _import_by_name(prefixed_name)
return prefixed_name, obj, parent return prefixed_name, obj, parent, modname
except ImportError: except ImportError:
tried.append(prefixed_name) tried.append(prefixed_name)
raise ImportError('no module named %s' % ' or '.join(tried)) raise ImportError('no module named %s' % ' or '.join(tried))
@ -464,7 +469,7 @@ def _import_by_name(name):
try: try:
__import__(modname) __import__(modname)
mod = sys.modules[modname] mod = sys.modules[modname]
return getattr(mod, name_parts[-1]), mod return getattr(mod, name_parts[-1]), mod, modname
except (ImportError, IndexError, AttributeError): except (ImportError, IndexError, AttributeError):
pass pass
@ -487,9 +492,9 @@ def _import_by_name(name):
for obj_name in name_parts[last_j:]: for obj_name in name_parts[last_j:]:
parent = obj parent = obj
obj = getattr(obj, obj_name) obj = getattr(obj, obj_name)
return obj, parent return obj, parent, modname
else: else:
return sys.modules[modname], None return sys.modules[modname], None, modname
except (ValueError, ImportError, AttributeError, KeyError), e: except (ValueError, ImportError, AttributeError, KeyError), e:
raise ImportError(*e.args) raise ImportError(*e.args)
@ -510,7 +515,7 @@ def autolink_role(typ, rawtext, etext, lineno, inliner,
prefixes = get_import_prefixes_from_env(env) prefixes = get_import_prefixes_from_env(env)
try: try:
name, obj, parent = import_by_name(pnode['reftarget'], prefixes) name, obj, parent, modname = import_by_name(pnode['reftarget'], prefixes)
except ImportError: except ImportError:
content = pnode[0] content = pnode[0]
r[0][0] = nodes.emphasis(rawtext, content[0].astext(), r[0][0] = nodes.emphasis(rawtext, content[0].astext(),

View File

@ -126,7 +126,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst',
ensuredir(path) ensuredir(path)
try: try:
name, obj, parent = import_by_name(name) name, obj, parent, mod_name = import_by_name(name)
except ImportError, e: except ImportError, e:
warn('[autosummary] failed to import %r: %s' % (name, e)) warn('[autosummary] failed to import %r: %s' % (name, e))
continue continue
@ -235,7 +235,7 @@ def find_autosummary_in_docstring(name, module=None, filename=None):
See `find_autosummary_in_lines`. See `find_autosummary_in_lines`.
""" """
try: try:
real_name, obj, parent = import_by_name(name) real_name, obj, parent, modname = import_by_name(name)
lines = pydoc.getdoc(obj).splitlines() lines = pydoc.getdoc(obj).splitlines()
return find_autosummary_in_lines(lines, module=name, filename=filename) return find_autosummary_in_lines(lines, module=name, filename=filename)
except AttributeError: except AttributeError:

View File

@ -5,6 +5,7 @@
C.class_attr C.class_attr
C.prop_attr1 C.prop_attr1
C.prop_attr2 C.prop_attr2
C.C2
""" """
def withSentence(): def withSentence():
@ -63,3 +64,8 @@ class C:
value is string. value is string.
""" """
class C2:
'''
This is a nested inner class docstring
'''

View File

@ -10,11 +10,14 @@
""" """
import sys import sys
from functools import wraps from functools import wraps
from StringIO import StringIO
from sphinx.ext.autosummary import mangle_signature from sphinx.ext.autosummary import mangle_signature
from util import test_roots, TestApp from util import test_roots, TestApp
html_warnfile = StringIO()
def with_autosummary_app(*args, **kw): def with_autosummary_app(*args, **kw):
default_kw = { default_kw = {
@ -75,7 +78,7 @@ def test_mangle_signature():
assert res == outp, (u"'%s' -> '%s' != '%s'" % (inp, res, outp)) assert res == outp, (u"'%s' -> '%s' != '%s'" % (inp, res, outp))
@with_autosummary_app(buildername='html') @with_autosummary_app(buildername='html', warning=html_warnfile)
def test_get_items_summary(app): def test_get_items_summary(app):
app.builddir.rmtree(True) app.builddir.rmtree(True)
@ -98,6 +101,9 @@ def test_get_items_summary(app):
finally: finally:
sphinx.ext.autosummary.Autosummary.get_items = orig_get_items sphinx.ext.autosummary.Autosummary.get_items = orig_get_items
html_warnings = html_warnfile.getvalue()
assert html_warnings == ''
expected_values = { expected_values = {
'withSentence': 'I have a sentence which spans multiple lines.', 'withSentence': 'I have a sentence which spans multiple lines.',
'noSentence': "this doesn't start with a", 'noSentence': "this doesn't start with a",
@ -106,6 +112,7 @@ def test_get_items_summary(app):
'C.class_attr': 'This is a class attribute', 'C.class_attr': 'This is a class attribute',
'C.prop_attr1': 'This is a function docstring', 'C.prop_attr1': 'This is a function docstring',
'C.prop_attr2': 'This is a attribute docstring', 'C.prop_attr2': 'This is a attribute docstring',
'C.C2': 'This is a nested inner class docstring',
} }
for key, expected in expected_values.iteritems(): for key, expected in expected_values.iteritems():
assert autosummary_items[key][2] == expected, 'Summary for %s was %r -'\ assert autosummary_items[key][2] == expected, 'Summary for %s was %r -'\