diff --git a/autoapi/_parser.py b/autoapi/_parser.py index 3b58d06f..0ca92130 100644 --- a/autoapi/_parser.py +++ b/autoapi/_parser.py @@ -152,20 +152,20 @@ def parse_classdef(self, node, data=None): continue for child in base.get_children(): - name = getattr(child, "name", None) - if isinstance(child, (astroid.Assign, astroid.AnnAssign)): - assign_value = _astroid_utils.get_assign_value(child) - if not assign_value: + children_data = self.parse(child) + for child_data in children_data: + name = child_data["name"] + + existing_child = children.get(name) + if existing_child and not existing_child["doc"]: + existing_child["doc"] = child_data["doc"] + + if name in overridden: continue - name = assign_value[0] - if not name or name in overridden: - continue - seen.add(name) - child_data = self.parse(child) - base_children.extend( - _parse_child(node, child_data, overloads, base, name) - ) + seen.add(name) + if _parse_child(node, child_data, overloads, base): + base_children.append(child_data) overridden.update(seen) @@ -297,11 +297,13 @@ def parse_module(self, node): top_name = node.name.split(".", 1)[0] for child in node.get_children(): if _astroid_utils.is_local_import_from(child, top_name): - child_data = self._parse_local_import_from(child) + children_data = self._parse_local_import_from(child) else: - child_data = self.parse(child) + children_data = self.parse(child) - data["children"].extend(_parse_child(node, child_data, overloads)) + for child_data in children_data: + if _parse_child(node, child_data, overloads): + data["children"].append(child_data) return data @@ -346,28 +348,28 @@ def parse(self, node): data = self.parse(child) if data: break + return data -def _parse_child(node, child_data, overloads, base=None, name=None): - result = [] - for single_data in child_data: - if single_data["type"] in ("function", "method", "property"): - if name is None: - name = single_data["name"] - if name in overloads: - grouped = overloads[name] - grouped["doc"] = single_data["doc"] - if single_data["is_overload"]: - grouped["overloads"].append( - (single_data["args"], single_data["return_annotation"]) - ) - continue - if single_data["is_overload"] and name not in overloads: - overloads[name] = single_data +def _parse_child(node, child_data, overloads, base=None) -> bool: + if child_data["type"] in ("function", "method", "property"): + name = child_data["name"] + if name in overloads: + grouped = overloads[name] + grouped["doc"] = child_data["doc"] + + if child_data["is_overload"]: + grouped["overloads"].append( + (child_data["args"], child_data["return_annotation"]) + ) + + return False + + if child_data["is_overload"] and name not in overloads: + overloads[name] = child_data - if base: - single_data["inherited"] = base is not node - result.append(single_data) + if base: + child_data["inherited"] = base is not node - return result + return True diff --git a/docs/changes/477.bugfix.rst b/docs/changes/477.bugfix.rst new file mode 100644 index 00000000..877b26b1 --- /dev/null +++ b/docs/changes/477.bugfix.rst @@ -0,0 +1 @@ +Fix instance attributes not being documented by inherited-members diff --git a/tests/python/py3example/example/example.py b/tests/python/py3example/example/example.py index d02405bf..4ce74aab 100644 --- a/tests/python/py3example/example/example.py +++ b/tests/python/py3example/example/example.py @@ -185,3 +185,30 @@ def __getitem__(self, i): def __len__(self): return 3 + + +class InheritBaseError(Exception): + """The base exception.""" + def __init__(self): + self.my_message = "one" + """My message.""" + super().__init__(self.my_message) + + +class InheritError(InheritBaseError): + """The middle exception.""" + def __init__(self): + self.my_other_message = "two" + """My other message.""" + super().__init__() + + +class SubInheritError(InheritError): + """The last exception.""" + + +class DuplicateInheritError(InheritBaseError): + """Not the base exception.""" + def __init__(self): + self.my_message = "three" + super().__init__() diff --git a/tests/python/test_pyintegration.py b/tests/python/test_pyintegration.py index c81a0a3e..28e16ca0 100644 --- a/tests/python/test_pyintegration.py +++ b/tests/python/test_pyintegration.py @@ -277,6 +277,9 @@ def test_integration(self, parse): assert "Initialize self" not in example_file assert "a new type" not in example_file + def test_inheritance(self, parse): + example_file = parse("_build/html/autoapi/example/index.html") + # Test that members are not inherited from standard library classes. assert example_file.find(id="example.MyException") assert not example_file.find(id="example.MyException.args") @@ -285,6 +288,33 @@ def test_integration(self, parse): assert example_file.find(id="example.My123.__contains__") assert example_file.find(id="example.My123.index") + # Test that classes inherit instance attributes + exc = example_file.find(id="example.InheritError") + assert exc + message = example_file.find(id="example.InheritError.my_message") + assert message + message_docstring = message.parent.find("dd").text.strip() + assert message_docstring == "My message." + + # Test that classes inherit from the whole mro + exc = example_file.find(id="example.SubInheritError") + assert exc + message = example_file.find(id="example.SubInheritError.my_message") + message_docstring = message.parent.find("dd").text.strip() + assert message_docstring == "My message." + message = example_file.find(id="example.SubInheritError.my_other_message") + assert message + message_docstring = message.parent.find("dd").text.strip() + assert message_docstring == "My other message." + + # Test that inherited instance attributes include the docstring + exc = example_file.find(id="example.DuplicateInheritError") + assert exc + message = example_file.find(id="example.DuplicateInheritError.my_message") + assert message + message_docstring = message.parent.find("dd").text.strip() + assert message_docstring == "My message." + def test_annotations(self, parse): example_file = parse("_build/html/autoapi/example/index.html")