diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py index 44c7ad8bb..691fef7af 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc.py @@ -489,20 +489,26 @@ class Documenter(object): If *want_all* is True, return all members. Else, only return those members given by *self.options.members* (which may also be none). """ + analyzed_member_names = set() + if self.analyzer: + attr_docs = self.analyzer.find_attr_docs() + namespace = '.'.join(self.objpath) + for item in attr_docs.iteritems(): + if item[0][0] == namespace: + analyzed_member_names.add(item[0][1]) if not want_all: if not self.options.members: return False, [] # specific members given - ret = [] + members = [] for mname in self.options.members: try: - ret.append((mname, self.get_attr(self.object, mname))) + members.append((mname, self.get_attr(self.object, mname))) except AttributeError: - self.directive.warn('missing attribute %s in object %s' - % (mname, self.fullname)) - return False, ret - - if self.options.inherited_members: + if mname not in analyzed_member_names: + self.directive.warn('missing attribute %s in object %s' + % (mname, self.fullname)) + elif self.options.inherited_members: # safe_getmembers() uses dir() which pulls in members from all # base classes members = safe_getmembers(self.object) @@ -521,13 +527,10 @@ class Documenter(object): for mname in obj_dict.keys()] membernames = set(m[0] for m in members) # add instance attributes from the analyzer - if self.analyzer: - attr_docs = self.analyzer.find_attr_docs() - namespace = '.'.join(self.objpath) - for item in attr_docs.iteritems(): - if item[0][0] == namespace: - if item[0][1] not in membernames: - members.append((item[0][1], INSTANCEATTR)) + for aname in analyzed_member_names: + if aname not in membernames and \ + (want_all or aname in self.options.members): + members.append((aname, INSTANCEATTR)) return False, sorted(members) def filter_members(self, members, want_all): diff --git a/tests/root/autodoc.txt b/tests/root/autodoc.txt index 5c03f947c..d4b3404c4 100644 --- a/tests/root/autodoc.txt +++ b/tests/root/autodoc.txt @@ -32,3 +32,16 @@ Just testing a few autodoc possibilities... :noindex: .. autoclass:: MarkupError + + +.. currentmodule:: test_autodoc + +.. autoclass:: InstAttCls + :members: + + All members (5 total) + +.. autoclass:: InstAttCls + :members: ca1, ia1 + + Specific members (2 total) diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 6dedaad8f..642b91412 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -540,6 +540,32 @@ def test_generate(): assert_result_contains( ' :annotation: = None', 'attribute', 'AttCls.a2') + # test explicit members with instance attributes + del directive.env.temp_data['autodoc:class'] + del directive.env.temp_data['autodoc:module'] + directive.env.temp_data['py:module'] = 'test_autodoc' + options.inherited_members = False + options.undoc_members = False + options.members = ALL + assert_processes([ + ('class', 'test_autodoc.InstAttCls'), + ('attribute', 'test_autodoc.InstAttCls.ca1'), + ('attribute', 'test_autodoc.InstAttCls.ca2'), + ('attribute', 'test_autodoc.InstAttCls.ca3'), + ('attribute', 'test_autodoc.InstAttCls.ia1'), + ('attribute', 'test_autodoc.InstAttCls.ia2'), + ], 'class', 'InstAttCls') + del directive.env.temp_data['autodoc:class'] + del directive.env.temp_data['autodoc:module'] + options.members = ['ca1', 'ia1'] + assert_processes([ + ('class', 'test_autodoc.InstAttCls'), + ('attribute', 'test_autodoc.InstAttCls.ca1'), + ('attribute', 'test_autodoc.InstAttCls.ia1'), + ], 'class', 'InstAttCls') + del directive.env.temp_data['autodoc:class'] + del directive.env.temp_data['autodoc:module'] + del directive.env.temp_data['py:module'] # --- generate fodder ------------ @@ -680,3 +706,22 @@ class StrRepr(str): class AttCls(object): a1 = StrRepr('hello\nworld') a2 = None + +class InstAttCls(object): + """Class with documented class and instance attributes.""" + + #: Doc comment for class attribute InstAttCls.ca1. + #: It can have multiple lines. + ca1 = 'a' + + ca2 = 'b' #: Doc comment for InstAttCls.ca2. One line only. + + ca3 = 'c' + """Docstring for class attribute InstAttCls.ca3.""" + + def __init__(self): + #: Doc comment for instance attribute InstAttCls.ia1 + self.ia1 = 'd' + + self.ia2 = 'e' + """Docstring for instance attribute InstAttCls.ia2."""