#437: autodoc now shows values of class data attributes.

This commit is contained in:
Georg Brandl 2011-01-08 23:45:58 +01:00
parent b4558269a6
commit dc0c43da9a
5 changed files with 93 additions and 6 deletions

View File

@ -85,6 +85,8 @@ Release 1.1 (in development)
* #537: Added :confval:`nitpick_ignore`. * #537: Added :confval:`nitpick_ignore`.
* #437: autodoc now shows values of class data attributes.
* autodoc now supports documenting the signatures of ``functools.partial`` * autodoc now supports documenting the signatures of ``functools.partial``
objects. objects.

View File

@ -88,6 +88,7 @@ class PyObject(ObjectDescription):
option_spec = { option_spec = {
'noindex': directives.flag, 'noindex': directives.flag,
'module': directives.unchanged, 'module': directives.unchanged,
'annotation': directives.unchanged,
} }
doc_field_types = [ doc_field_types = [
@ -180,6 +181,8 @@ class PyObject(ObjectDescription):
nodetext = modname + '.' nodetext = modname + '.'
signode += addnodes.desc_addname(nodetext, nodetext) signode += addnodes.desc_addname(nodetext, nodetext)
anno = self.options.get('annotation')
signode += addnodes.desc_name(name, name) signode += addnodes.desc_name(name, name)
if not arglist: if not arglist:
if self.needs_arglist(): if self.needs_arglist():
@ -187,10 +190,15 @@ class PyObject(ObjectDescription):
signode += addnodes.desc_parameterlist() signode += addnodes.desc_parameterlist()
if retann: if retann:
signode += addnodes.desc_returns(retann, retann) signode += addnodes.desc_returns(retann, retann)
if anno:
signode += addnodes.desc_annotation(' ' + anno, ' ' + anno)
return fullname, name_prefix return fullname, name_prefix
_pseudo_parse_arglist(signode, arglist) _pseudo_parse_arglist(signode, arglist)
if retann: if retann:
signode += addnodes.desc_returns(retann, retann) signode += addnodes.desc_returns(retann, retann)
if anno:
signode += addnodes.desc_annotation(' ' + anno, ' ' + anno)
return fullname, name_prefix return fullname, name_prefix
def get_index_text(self, modname, name): def get_index_text(self, modname, name):

View File

@ -28,7 +28,7 @@ from sphinx.application import ExtensionError
from sphinx.util.nodes import nested_parse_with_titles from sphinx.util.nodes import nested_parse_with_titles
from sphinx.util.compat import Directive from sphinx.util.compat import Directive
from sphinx.util.inspect import getargspec, isdescriptor, safe_getmembers, \ from sphinx.util.inspect import getargspec, isdescriptor, safe_getmembers, \
safe_getattr safe_getattr, safe_repr
from sphinx.util.pycompat import base_exception, class_types from sphinx.util.pycompat import base_exception, class_types
from sphinx.util.docstrings import prepare_docstring from sphinx.util.docstrings import prepare_docstring
@ -566,9 +566,15 @@ class Documenter(object):
skip = False skip = False
isattr = True isattr = True
else: else:
# ignore undocumented members if :undoc-members: # ignore undocumented members if :undoc-members: is not given
# is not given
doc = self.get_attr(member, '__doc__', None) doc = self.get_attr(member, '__doc__', None)
# if the member __doc__ is the same as self's __doc__, it's just
# inherited and therefore not the member's doc
cls = self.get_attr(member, '__class__', None)
if cls:
cls_doc = self.get_attr(cls, '__doc__', None)
if cls_doc == doc:
doc = None
skip = not self.options.undoc_members and not doc skip = not self.options.undoc_members and not doc
# give the user a chance to decide whether this member # give the user a chance to decide whether this member
@ -1058,11 +1064,21 @@ class DataDocumenter(ModuleLevelDocumenter):
""" """
objtype = 'data' objtype = 'data'
member_order = 40 member_order = 40
priority = -10
@classmethod @classmethod
def can_document_member(cls, member, membername, isattr, parent): def can_document_member(cls, member, membername, isattr, parent):
return isinstance(parent, ModuleDocumenter) and isattr return isinstance(parent, ModuleDocumenter) and isattr
def add_directive_header(self, sig):
ModuleLevelDocumenter.add_directive_header(self, sig)
try:
objrepr = safe_repr(self.object)
except ValueError:
pass
else:
self.add_line(u' :annotation: = ' + objrepr, '<autodoc>')
def document_members(self, all_members=False): def document_members(self, all_members=False):
pass pass
@ -1144,16 +1160,44 @@ class AttributeDocumenter(ClassLevelDocumenter):
def can_document_member(cls, member, membername, isattr, parent): def can_document_member(cls, member, membername, isattr, parent):
isdatadesc = isdescriptor(member) and not \ isdatadesc = isdescriptor(member) and not \
isinstance(member, cls.method_types) isinstance(member, cls.method_types)
return isdatadesc or \ return isdatadesc or (not isinstance(parent, ModuleDocumenter)
(isattr and not isinstance(parent, ModuleDocumenter)) and not inspect.isroutine(member)
and not isinstance(member, class_types))
def document_members(self, all_members=False): def document_members(self, all_members=False):
pass pass
def import_object(self):
ret = ClassLevelDocumenter.import_object(self)
if isdescriptor(self.object) and \
not isinstance(self.object, self.method_types):
self._datadescriptor = True
else:
# if it's not a data descriptor
self._datadescriptor = False
return ret
def get_real_modname(self): def get_real_modname(self):
return self.get_attr(self.parent or self.object, '__module__', None) \ return self.get_attr(self.parent or self.object, '__module__', None) \
or self.modname or self.modname
def add_directive_header(self, sig):
ClassLevelDocumenter.add_directive_header(self, sig)
if not self._datadescriptor:
try:
objrepr = safe_repr(self.object)
except ValueError:
pass
else:
self.add_line(u' :annotation: = ' + objrepr, '<autodoc>')
def add_content(self, more_content, no_docstring=False):
if not self._datadescriptor:
# if it's not a data descriptor, its docstring is very probably the
# wrong thing to display
no_docstring = True
ClassLevelDocumenter.add_content(self, more_content, no_docstring)
class InstanceAttributeDocumenter(AttributeDocumenter): class InstanceAttributeDocumenter(AttributeDocumenter):
""" """
@ -1176,6 +1220,7 @@ class InstanceAttributeDocumenter(AttributeDocumenter):
"""Never import anything.""" """Never import anything."""
# disguise as an attribute # disguise as an attribute
self.objtype = 'attribute' self.objtype = 'attribute'
self._datadescriptor = False
return True return True
def add_content(self, more_content, no_docstring=False): def add_content(self, more_content, no_docstring=False):

View File

@ -79,3 +79,12 @@ def safe_getmembers(object, predicate=None):
results.append((key, value)) results.append((key, value))
results.sort() results.sort()
return results return results
def safe_repr(object):
"""A repr() implementation that returns text safe to use in reST context."""
try:
s = repr(object)
except Exception:
raise ValueError
return s.replace('\n', ' ')

View File

@ -341,6 +341,7 @@ def test_generate():
inst = AutoDirective._registry[objtype](directive, name) inst = AutoDirective._registry[objtype](directive, name)
inst.generate(**kw) inst.generate(**kw)
assert directive.result assert directive.result
#print '\n'.join(directive.result)
assert len(_warnings) == 0, _warnings assert len(_warnings) == 0, _warnings
del directive.result[:] del directive.result[:]
@ -430,7 +431,8 @@ def test_generate():
options.members = ALL options.members = ALL
assert_processes(should, 'class', 'Class') assert_processes(should, 'class', 'Class')
options.undoc_members = True options.undoc_members = True
should.extend((('method', 'test_autodoc.Class.undocmeth'), should.extend((('attribute', 'test_autodoc.Class.skipattr'),
('method', 'test_autodoc.Class.undocmeth'),
('method', 'test_autodoc.Class.roger'))) ('method', 'test_autodoc.Class.roger')))
assert_processes(should, 'class', 'Class') assert_processes(should, 'class', 'Class')
options.inherited_members = True options.inherited_members = True
@ -519,11 +521,24 @@ def test_generate():
'.. py:classmethod:: Class.moore(a, e, f) -> happiness', 'method', '.. py:classmethod:: Class.moore(a, e, f) -> happiness', 'method',
'test_autodoc.Class.moore') 'test_autodoc.Class.moore')
# test new attribute documenter behavior
directive.env.temp_data['py:module'] = 'test_autodoc'
options.undoc_members = True
assert_processes([('class', 'test_autodoc.AttCls'),
('attribute', 'test_autodoc.AttCls.a1'),
('attribute', 'test_autodoc.AttCls.a2'),
], 'class', 'AttCls')
assert_result_contains(
' :annotation: = hello world', 'attribute', 'AttCls.a1')
assert_result_contains(
' :annotation: = None', 'attribute', 'AttCls.a2')
# --- generate fodder ------------ # --- generate fodder ------------
__all__ = ['Class'] __all__ = ['Class']
#: documentation for the integer
integer = 1 integer = 1
class CustomEx(Exception): class CustomEx(Exception):
@ -642,3 +657,11 @@ First line of docstring
rest of docstring rest of docstring
""" """
class StrRepr(str):
def __repr__(self):
return self
class AttCls(object):
a1 = StrRepr('hello\nworld')
a2 = None