Fix documentation of inner classes in autodoc. Messy!

This commit is contained in:
Georg Brandl 2008-12-23 20:05:56 +01:00
parent 409cc1fe39
commit dafb55a467
3 changed files with 104 additions and 65 deletions

View File

@ -35,7 +35,9 @@ New features added
- Italian by Sandro Dentella.
* Extension API:
* Extensions and API:
- Autodoc now handles inner classes and their methods.
- There is now a ``Sphinx.add_lexer()`` method to be able to use
custom Pygments lexers easily.

View File

@ -23,7 +23,6 @@ from docutils.parsers.rst import directives
from docutils.statemachine import ViewList
from sphinx.util import rpartition, nested_parse_with_titles
from sphinx.directives.desc import py_sig_re
try:
base_exception = BaseException
@ -33,6 +32,14 @@ except NameError:
_charset_re = re.compile(r'coding[:=]\s*([-\w.]+)')
_module_charsets = {}
py_ext_sig_re = re.compile(
r'''^ ([\w.]+::)? # explicit module name
([\w.]+\.)? # module and/or class name(s)
(\w+) \s* # thing name
(?: \((.*)\) # optional arguments
(\s* -> \s* .*)? )? $ # optional return annotation
''', re.VERBOSE)
class Options(object):
pass
@ -282,24 +289,31 @@ class RstGenerator(object):
# first, parse the definition -- auto directives for classes and functions
# can contain a signature which is then used instead of an autogenerated one
try:
path, base, args, retann = py_sig_re.match(name).groups()
mod, path, base, args, retann = py_ext_sig_re.match(name).groups()
except:
self.warn('invalid signature for auto%s (%r)' % (what, name))
return
# fullname is the fully qualified name, base the name after the last dot
fullname = (path or '') + base
return None, [], None, None
# support explicit module and class name separation via ::
if mod is not None:
mod = mod[:-2]
parents = path and path.rstrip('.').split('.') or []
else:
parents = []
if what == 'module':
if mod is not None:
self.warn('"::" in automodule name doesn\'t make sense')
if args or retann:
self.warn('ignoring signature arguments and return annotation '
'for automodule %s' % fullname)
return fullname, fullname, [], None, None
'for automodule %s' % name)
return (path or '') + base, [], None, None
elif what in ('class', 'exception', 'function'):
elif what in ('exception', 'function', 'class'):
if mod is None:
if path:
mod = path.rstrip('.')
else:
mod = None
# if documenting a toplevel object without explicit module, it can
# be contained in another auto directive ...
if hasattr(self.env, 'autodoc_current_module'):
@ -307,9 +321,10 @@ class RstGenerator(object):
# ... or in the scope of a module directive
if not mod:
mod = self.env.currmodule
return fullname, mod, [base], args, retann
return mod, parents + [base], args, retann
else:
if mod is None:
if path:
mod_cls = path.rstrip('.')
else:
@ -323,14 +338,15 @@ class RstGenerator(object):
mod_cls = self.env.currclass
# ... if still None, there's no way to know
if mod_cls is None:
return fullname, None, [], args, retann
return None, [], None, None
mod, cls = rpartition(mod_cls, '.')
parents = [cls]
# if the module name is still missing, get it like above
if not mod and hasattr(self.env, 'autodoc_current_module'):
mod = self.env.autodoc_current_module
if not mod:
mod = self.env.currmodule
return fullname, mod, [cls, base], args, retann
return mod, parents + [base], args, retann
def format_signature(self, what, name, obj, args, retann):
"""
@ -386,13 +402,15 @@ class RstGenerator(object):
"""
Generate reST for the object in self.result.
"""
fullname, mod, objpath, args, retann = self.resolve_name(what, name)
mod, objpath, args, retann = self.resolve_name(what, name)
if not mod:
# need a module to import
self.warn('don\'t know which module to import for autodocumenting %r '
'(try placing a "module" or "currentmodule" directive in the '
'document, or giving an explicit module name)' % fullname)
'document, or giving an explicit module name)' % name)
return
# fully-qualified name
fullname = mod + (objpath and '.' + '.'.join(objpath) or '')
# the name to put into the generated directive -- doesn't contain the module
name_in_directive = '.'.join(objpath) or mod
@ -423,7 +441,7 @@ class RstGenerator(object):
# format the object's signature, if any
try:
sig = self.format_signature(what, name, todoc, args, retann)
sig = self.format_signature(what, fullname, todoc, args, retann)
except Exception, err:
self.warn('error while formatting signature for %s: %s' %
(fullname, err))
@ -548,8 +566,7 @@ class RstGenerator(object):
if isinstance(member, (types.FunctionType,
types.BuiltinFunctionType)):
memberwhat = 'function'
elif isinstance(member, types.ClassType) or \
isinstance(member, type):
elif isinstance(member, (types.ClassType, type)):
if issubclass(member, base_exception):
memberwhat = 'exception'
else:
@ -558,14 +575,18 @@ class RstGenerator(object):
# XXX: todo -- attribute docs
continue
else:
if callable(member):
if isinstance(member, (types.ClassType, type)):
memberwhat = 'class'
elif callable(member):
memberwhat = 'method'
elif isdescriptor(member):
memberwhat = 'attribute'
else:
# XXX: todo -- attribute docs
continue
full_membername = fullname + '.' + membername
# give explicitly separated module name, so that members of inner classes
# can be documented
full_membername = mod + '::' + '.'.join(objpath + [membername])
self.generate(memberwhat, full_membername, ['__all__'], None, indent,
check_module=members_check_module)

View File

@ -82,41 +82,41 @@ def skip_member(app, what, name, obj, skip, options):
def test_resolve_name():
# for modules
assert gen.resolve_name('module', 'test_autodoc') == \
('test_autodoc', 'test_autodoc', [], None, None)
('test_autodoc', [], None, None)
assert gen.resolve_name('module', 'test.test_autodoc') == \
('test.test_autodoc', 'test.test_autodoc', [], None, None)
('test.test_autodoc', [], None, None)
assert gen.resolve_name('module', 'test(arg)') == \
('test', 'test', [], None, None)
('test', [], None, None)
assert 'ignoring signature arguments' in gen.warnings[0]
del gen.warnings[:]
# for functions/classes
assert gen.resolve_name('function', 'util.raises') == \
('util.raises', 'util', ['raises'], None, None)
('util', ['raises'], None, None)
assert gen.resolve_name('function', 'util.raises(exc) -> None') == \
('util.raises', 'util', ['raises'], 'exc', ' -> None')
('util', ['raises'], 'exc', ' -> None')
gen.env.autodoc_current_module = 'util'
assert gen.resolve_name('function', 'raises') == \
('raises', 'util', ['raises'], None, None)
('util', ['raises'], None, None)
gen.env.autodoc_current_module = None
gen.env.currmodule = 'util'
assert gen.resolve_name('function', 'raises') == \
('raises', 'util', ['raises'], None, None)
('util', ['raises'], None, None)
assert gen.resolve_name('class', 'TestApp') == \
('TestApp', 'util', ['TestApp'], None, None)
('util', ['TestApp'], None, None)
# for members
gen.env.currmodule = 'foo'
assert gen.resolve_name('method', 'util.TestApp.cleanup') == \
('util.TestApp.cleanup', 'util', ['TestApp', 'cleanup'], None, None)
('util', ['TestApp', 'cleanup'], None, None)
gen.env.currmodule = 'util'
gen.env.currclass = 'Foo'
gen.env.autodoc_current_class = 'TestApp'
assert gen.resolve_name('method', 'cleanup') == \
('cleanup', 'util', ['TestApp', 'cleanup'], None, None)
('util', ['TestApp', 'cleanup'], None, None)
assert gen.resolve_name('method', 'TestApp.cleanup') == \
('TestApp.cleanup', 'util', ['TestApp', 'cleanup'], None, None)
('util', ['TestApp', 'cleanup'], None, None)
# and clean up
gen.env.currmodule = None
@ -321,17 +321,17 @@ def test_generate():
assert_works('exception', 'test_autodoc.CustomEx', [], None)
# test diverse inclusion settings for members
should = [('class', 'Class')]
should = [('class', 'test_autodoc.Class')]
assert_processes(should, 'class', 'Class', [], None)
should.extend([('method', 'Class.meth')])
should.extend([('method', 'test_autodoc.Class.meth')])
assert_processes(should, 'class', 'Class', ['meth'], None)
should.extend([('attribute', 'Class.prop')])
should.extend([('attribute', 'test_autodoc.Class.prop')])
assert_processes(should, 'class', 'Class', ['__all__'], None)
options.undoc_members = True
should.append(('method', 'Class.undocmeth'))
should.append(('method', 'test_autodoc.Class.undocmeth'))
assert_processes(should, 'class', 'Class', ['__all__'], None)
options.inherited_members = True
should.append(('method', 'Class.inheritedmeth'))
should.append(('method', 'test_autodoc.Class.inheritedmeth'))
assert_processes(should, 'class', 'Class', ['__all__'], None)
# test module flags
@ -363,6 +363,12 @@ def test_generate():
assert_result_contains('.. class:: CustomDict', 'class', 'CustomDict',
['__all__'], None)
# test inner class handling
assert_processes([('class', 'test_autodoc.Outer'),
('class', 'test_autodoc.Outer.Inner'),
('method', 'test_autodoc.Outer.Inner.meth')],
'class', 'Outer', ['__all__'], None)
# --- generate fodder ------------
@ -404,3 +410,13 @@ def function(foo, *args, **kwds):
Return spam.
"""
pass
class Outer(object):
"""Foo"""
class Inner(object):
"""Foo"""
def meth(self):
"""Foo"""