mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
#437: autodoc now shows values of class data attributes.
This commit is contained in:
parent
b4558269a6
commit
dc0c43da9a
2
CHANGES
2
CHANGES
@ -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.
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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', ' ')
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user