Commits

Georg Brandl committed db95aed

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

  • Participants
  • Parent commits 89294ce

Comments (0)

Files changed (5)

 
 * #537: Added :confval:`nitpick_ignore`.
 
+* #437: autodoc now shows values of class data attributes.
+
 * autodoc now supports documenting the signatures of ``functools.partial``
   objects.
 

sphinx/domains/python.py

     option_spec = {
         'noindex': directives.flag,
         'module': directives.unchanged,
+        'annotation': directives.unchanged,
     }
 
     doc_field_types = [
                 nodetext = modname + '.'
                 signode += addnodes.desc_addname(nodetext, nodetext)
 
+        anno = self.options.get('annotation')
+
         signode += addnodes.desc_name(name, name)
         if not arglist:
             if self.needs_arglist():
                 signode += addnodes.desc_parameterlist()
             if retann:
                 signode += addnodes.desc_returns(retann, retann)
+            if anno:
+                signode += addnodes.desc_annotation(' ' + anno, ' ' + anno)
             return fullname, name_prefix
+
         _pseudo_parse_arglist(signode, arglist)
         if retann:
             signode += addnodes.desc_returns(retann, retann)
+        if anno:
+            signode += addnodes.desc_annotation(' ' + anno, ' ' + anno)
         return fullname, name_prefix
 
     def get_index_text(self, modname, name):

sphinx/ext/autodoc.py

 from sphinx.util.nodes import nested_parse_with_titles
 from sphinx.util.compat import Directive
 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.docstrings import prepare_docstring
 
                 skip = False
                 isattr = True
             else:
-                # ignore undocumented members if :undoc-members:
-                # is not given
+                # ignore undocumented members if :undoc-members: is not given
                 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
 
             # give the user a chance to decide whether this member
     """
     objtype = 'data'
     member_order = 40
+    priority = -10
 
     @classmethod
     def can_document_member(cls, member, membername, isattr, parent):
         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):
         pass
 
     def can_document_member(cls, member, membername, isattr, parent):
         isdatadesc = isdescriptor(member) and not \
                      isinstance(member, cls.method_types)
-        return isdatadesc or \
-               (isattr and not isinstance(parent, ModuleDocumenter))
+        return isdatadesc or (not isinstance(parent, ModuleDocumenter)
+                              and not inspect.isroutine(member)
+                              and not isinstance(member, class_types))
 
     def document_members(self, all_members=False):
         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):
         return self.get_attr(self.parent or self.object, '__module__', None) \
                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):
     """
         """Never import anything."""
         # disguise as an attribute
         self.objtype = 'attribute'
+        self._datadescriptor = False
         return True
 
     def add_content(self, more_content, no_docstring=False):

sphinx/util/inspect.py

             results.append((key, value))
     results.sort()
     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', ' ')

tests/test_autodoc.py

         inst = AutoDirective._registry[objtype](directive, name)
         inst.generate(**kw)
         assert directive.result
+        #print '\n'.join(directive.result)
         assert len(_warnings) == 0, _warnings
         del directive.result[:]
 
     options.members = ALL
     assert_processes(should, 'class', 'Class')
     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')))
     assert_processes(should, 'class', 'Class')
     options.inherited_members = True
         '.. py:classmethod:: Class.moore(a, e, f) -> happiness', 'method',
         '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 ------------
 
 __all__ = ['Class']
 
+#: documentation for the integer
 integer = 1
 
 class CustomEx(Exception):
 
         rest of docstring
         """
+
+class StrRepr(str):
+    def __repr__(self):
+        return self
+
+class AttCls(object):
+    a1 = StrRepr('hello\nworld')
+    a2 = None