mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
autosummary: Support documenting inherited attributes (#10691)
The current implementation of ``import_ivar_by_name`` filters attributes if the name of the object that the attribute belongs to does not match the object being documented. However, for inherited attributes this is not the case. Filtering only on the attribute name seems to resolve the issue. It is not clear to me if there are any unwanted sideeffects of this and we should filter on the list of qualnames for the object and all its super classes (if any). Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
This commit is contained in:
parent
7ecf037280
commit
3edae68904
@ -88,7 +88,7 @@ from sphinx.util.docutils import (
|
||||
new_document,
|
||||
switch_source_input,
|
||||
)
|
||||
from sphinx.util.inspect import signature_from_str
|
||||
from sphinx.util.inspect import getmro, signature_from_str
|
||||
from sphinx.util.matching import Matcher
|
||||
from sphinx.util.typing import OptionSpec
|
||||
from sphinx.writers.html import HTML5Translator
|
||||
@ -715,12 +715,22 @@ def import_ivar_by_name(name: str, prefixes: list[str | None] = [None],
|
||||
try:
|
||||
name, attr = name.rsplit(".", 1)
|
||||
real_name, obj, parent, modname = import_by_name(name, prefixes, grouped_exception)
|
||||
qualname = real_name.replace(modname + ".", "")
|
||||
analyzer = ModuleAnalyzer.for_module(getattr(obj, '__module__', modname))
|
||||
analyzer.analyze()
|
||||
# check for presence in `annotations` to include dataclass attributes
|
||||
if (qualname, attr) in analyzer.attr_docs or (qualname, attr) in analyzer.annotations:
|
||||
return real_name + "." + attr, INSTANCEATTR, obj, modname
|
||||
|
||||
# Get ancestors of the object (class.__mro__ includes the class itself as
|
||||
# the first entry)
|
||||
candidate_objects = getmro(obj)
|
||||
if len(candidate_objects) == 0:
|
||||
candidate_objects = (obj,)
|
||||
|
||||
for candidate_obj in candidate_objects:
|
||||
analyzer = ModuleAnalyzer.for_module(getattr(candidate_obj, '__module__', modname))
|
||||
analyzer.analyze()
|
||||
# check for presence in `annotations` to include dataclass attributes
|
||||
found_attrs = set()
|
||||
found_attrs |= {attr for (qualname, attr) in analyzer.attr_docs}
|
||||
found_attrs |= {attr for (qualname, attr) in analyzer.annotations}
|
||||
if attr in found_attrs:
|
||||
return real_name + "." + attr, INSTANCEATTR, obj, modname
|
||||
except (ImportError, ValueError, PycodeError) as exc:
|
||||
raise ImportError from exc
|
||||
except ImportExceptionGroup:
|
||||
|
@ -0,0 +1,13 @@
|
||||
from autosummary_dummy_module import Foo
|
||||
|
||||
|
||||
class InheritedAttrClass(Foo):
|
||||
|
||||
def __init__(self):
|
||||
#: other docstring
|
||||
self.subclassattr = "subclassattr"
|
||||
|
||||
super().__init__()
|
||||
|
||||
|
||||
__all__ = ["InheritedAttrClass"]
|
@ -13,4 +13,6 @@
|
||||
autosummary_dummy_module.Foo.value
|
||||
autosummary_dummy_module.bar
|
||||
autosummary_dummy_module.qux
|
||||
autosummary_dummy_inherited_module.InheritedAttrClass
|
||||
autosummary_dummy_inherited_module.InheritedAttrClass.subclassattr
|
||||
autosummary_importfail
|
||||
|
@ -319,6 +319,33 @@ def test_autosummary_generate_content_for_module_imported_members(app):
|
||||
assert context['objtype'] == 'module'
|
||||
|
||||
|
||||
@pytest.mark.sphinx(testroot='ext-autosummary')
|
||||
def test_autosummary_generate_content_for_module_imported_members_inherited_module(app):
|
||||
import autosummary_dummy_inherited_module
|
||||
template = Mock()
|
||||
|
||||
generate_autosummary_content('autosummary_dummy_inherited_module',
|
||||
autosummary_dummy_inherited_module, None,
|
||||
template, None, True, app, False, {})
|
||||
assert template.render.call_args[0][0] == 'module'
|
||||
|
||||
context = template.render.call_args[0][1]
|
||||
assert context['members'] == ['Foo', 'InheritedAttrClass', '__all__', '__builtins__', '__cached__',
|
||||
'__doc__', '__file__', '__loader__', '__name__',
|
||||
'__package__', '__spec__']
|
||||
assert context['functions'] == []
|
||||
assert context['classes'] == ['Foo', 'InheritedAttrClass']
|
||||
assert context['exceptions'] == []
|
||||
assert context['all_exceptions'] == []
|
||||
assert context['attributes'] == []
|
||||
assert context['all_attributes'] == []
|
||||
assert context['fullname'] == 'autosummary_dummy_inherited_module'
|
||||
assert context['module'] == 'autosummary_dummy_inherited_module'
|
||||
assert context['objname'] == ''
|
||||
assert context['name'] == ''
|
||||
assert context['objtype'] == 'module'
|
||||
|
||||
|
||||
@pytest.mark.sphinx('dummy', testroot='ext-autosummary')
|
||||
def test_autosummary_generate(app, status, warning):
|
||||
app.builder.build_all()
|
||||
@ -337,16 +364,20 @@ def test_autosummary_generate(app, status, warning):
|
||||
nodes.row,
|
||||
nodes.row,
|
||||
nodes.row,
|
||||
nodes.row,
|
||||
nodes.row,
|
||||
nodes.row)])])
|
||||
assert_node(doctree[4][0], addnodes.toctree, caption="An autosummary")
|
||||
|
||||
assert len(doctree[3][0][0][2]) == 6
|
||||
assert len(doctree[3][0][0][2]) == 8
|
||||
assert doctree[3][0][0][2][0].astext() == 'autosummary_dummy_module\n\n'
|
||||
assert doctree[3][0][0][2][1].astext() == 'autosummary_dummy_module.Foo()\n\n'
|
||||
assert doctree[3][0][0][2][2].astext() == 'autosummary_dummy_module.Foo.Bar()\n\n'
|
||||
assert doctree[3][0][0][2][3].astext() == 'autosummary_dummy_module.Foo.value\n\ndocstring'
|
||||
assert doctree[3][0][0][2][4].astext() == 'autosummary_dummy_module.bar(x[, y])\n\n'
|
||||
assert doctree[3][0][0][2][5].astext() == 'autosummary_dummy_module.qux\n\na module-level attribute'
|
||||
assert doctree[3][0][0][2][6].astext() == 'autosummary_dummy_inherited_module.InheritedAttrClass()\n\n'
|
||||
assert doctree[3][0][0][2][7].astext() == 'autosummary_dummy_inherited_module.InheritedAttrClass.subclassattr\n\nother docstring'
|
||||
|
||||
module = (app.srcdir / 'generated' / 'autosummary_dummy_module.rst').read_text(encoding='utf8')
|
||||
|
||||
@ -392,6 +423,28 @@ def test_autosummary_generate(app, status, warning):
|
||||
'\n'
|
||||
'.. autodata:: qux' in qux)
|
||||
|
||||
InheritedAttrClass = (app.srcdir / 'generated' / 'autosummary_dummy_inherited_module.InheritedAttrClass.rst').read_text(encoding='utf8')
|
||||
print(InheritedAttrClass)
|
||||
assert '.. automethod:: __init__' in Foo
|
||||
assert (' .. autosummary::\n'
|
||||
' \n'
|
||||
' ~InheritedAttrClass.__init__\n'
|
||||
' ~InheritedAttrClass.bar\n'
|
||||
' \n' in InheritedAttrClass)
|
||||
assert (' .. autosummary::\n'
|
||||
' \n'
|
||||
' ~InheritedAttrClass.CONSTANT3\n'
|
||||
' ~InheritedAttrClass.CONSTANT4\n'
|
||||
' ~InheritedAttrClass.baz\n'
|
||||
' ~InheritedAttrClass.subclassattr\n'
|
||||
' ~InheritedAttrClass.value\n'
|
||||
' \n' in InheritedAttrClass)
|
||||
|
||||
InheritedAttrClass_subclassattr = (app.srcdir / 'generated' / 'autosummary_dummy_inherited_module.InheritedAttrClass.subclassattr.rst').read_text(encoding='utf8')
|
||||
assert ('.. currentmodule:: autosummary_dummy_inherited_module\n'
|
||||
'\n'
|
||||
'.. autoattribute:: InheritedAttrClass.subclassattr' in InheritedAttrClass_subclassattr)
|
||||
|
||||
|
||||
@pytest.mark.sphinx('dummy', testroot='ext-autosummary',
|
||||
confoverrides={'autosummary_generate_overwrite': False})
|
||||
|
Loading…
Reference in New Issue
Block a user