Merge pull request #8028 from tk0miya/8011_ivar_for_autosummary

Close #8011: autosummary: Support instance attributes
This commit is contained in:
Takeshi KOMIYA 2020-08-02 18:24:32 +09:00 committed by GitHub
commit 03a6028663
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 55 additions and 10 deletions

View File

@ -20,6 +20,8 @@ Features added
* #2076: autodoc: Allow overriding of exclude-members in skip-member function * #2076: autodoc: Allow overriding of exclude-members in skip-member function
* #2024: autosummary: Add :confval:`autosummary_filename_map` to avoid conflict * #2024: autosummary: Add :confval:`autosummary_filename_map` to avoid conflict
of filenames between two object with different case of filenames between two object with different case
* #8011: autosummary: Support instance attributes as a target of autosummary
directive
* #7849: html: Add :confval:`html_codeblock_linenos_style` to change the style * #7849: html: Add :confval:`html_codeblock_linenos_style` to change the style
of line numbers for code-blocks of line numbers for code-blocks
* #7853: C and C++, support parameterized GNU style attributes. * #7853: C and C++, support parameterized GNU style attributes.

View File

@ -75,7 +75,7 @@ from sphinx.application import Sphinx
from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning
from sphinx.environment import BuildEnvironment from sphinx.environment import BuildEnvironment
from sphinx.environment.adapters.toctree import TocTree from sphinx.environment.adapters.toctree import TocTree
from sphinx.ext.autodoc import Documenter from sphinx.ext.autodoc import Documenter, INSTANCEATTR
from sphinx.ext.autodoc.directive import DocumenterBridge, Options from sphinx.ext.autodoc.directive import DocumenterBridge, Options
from sphinx.ext.autodoc.importer import import_module from sphinx.ext.autodoc.importer import import_module
from sphinx.ext.autodoc.mock import mock from sphinx.ext.autodoc.mock import mock
@ -285,6 +285,19 @@ class Autosummary(SphinxDirective):
return nodes return nodes
def import_by_name(self, name: str, prefixes: List[str]) -> Tuple[str, Any, Any, str]:
with mock(self.config.autosummary_mock_imports):
try:
return import_by_name(name, prefixes)
except ImportError as exc:
# check existence of instance attribute
try:
return import_ivar_by_name(name, prefixes)
except ImportError:
pass
raise exc # re-raise ImportError if instance attribute not found
def get_items(self, names: List[str]) -> List[Tuple[str, str, str, str]]: def get_items(self, names: List[str]) -> List[Tuple[str, str, str, str]]:
"""Try to import the given names, and return a list of """Try to import the given names, and return a list of
``[(name, signature, summary_string, real_name), ...]``. ``[(name, signature, summary_string, real_name), ...]``.
@ -302,8 +315,7 @@ class Autosummary(SphinxDirective):
display_name = name.split('.')[-1] display_name = name.split('.')[-1]
try: try:
with mock(self.config.autosummary_mock_imports): real_name, obj, parent, modname = self.import_by_name(name, prefixes=prefixes)
real_name, obj, parent, modname = import_by_name(name, prefixes=prefixes)
except ImportError: except ImportError:
logger.warning(__('autosummary: failed to import %s'), name, logger.warning(__('autosummary: failed to import %s'), name,
location=self.get_source_info()) location=self.get_source_info())
@ -659,6 +671,23 @@ def _import_by_name(name: str) -> Tuple[Any, Any, str]:
raise ImportError(*e.args) from e raise ImportError(*e.args) from e
def import_ivar_by_name(name: str, prefixes: List[str] = [None]) -> Tuple[str, Any, Any, str]:
"""Import an instance variable that has the given *name*, under one of the
*prefixes*. The first name that succeeds is used.
"""
try:
name, attr = name.rsplit(".", 1)
real_name, obj, parent, modname = import_by_name(name, prefixes)
qualname = real_name.replace(modname + ".", "")
analyzer = ModuleAnalyzer.for_module(modname)
if (qualname, attr) in analyzer.find_attr_docs():
return real_name + "." + attr, INSTANCEATTR, obj, modname
except (ImportError, ValueError):
pass
raise ImportError
# -- :autolink: (smart default role) ------------------------------------------- # -- :autolink: (smart default role) -------------------------------------------
def autolink_role(typ: str, rawtext: str, etext: str, lineno: int, inliner: Inliner, def autolink_role(typ: str, rawtext: str, etext: str, lineno: int, inliner: Inliner,

View File

@ -41,7 +41,7 @@ from sphinx.builders import Builder
from sphinx.config import Config from sphinx.config import Config
from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning
from sphinx.ext.autodoc import Documenter from sphinx.ext.autodoc import Documenter
from sphinx.ext.autosummary import import_by_name, get_documenter from sphinx.ext.autosummary import import_by_name, import_ivar_by_name, get_documenter
from sphinx.locale import __ from sphinx.locale import __
from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.registry import SphinxComponentRegistry from sphinx.registry import SphinxComponentRegistry
@ -413,8 +413,13 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None,
name, obj, parent, modname = import_by_name(entry.name) name, obj, parent, modname = import_by_name(entry.name)
qualname = name.replace(modname + ".", "") qualname = name.replace(modname + ".", "")
except ImportError as e: except ImportError as e:
_warn(__('[autosummary] failed to import %r: %s') % (entry.name, e)) try:
continue # try to importl as an instance attribute
name, obj, parent, modname = import_ivar_by_name(entry.name)
qualname = name.replace(modname + ".", "")
except ImportError:
_warn(__('[autosummary] failed to import %r: %s') % (entry.name, e))
continue
context = {} context = {}
if app: if app:

View File

@ -16,7 +16,8 @@ class Foo:
pass pass
def __init__(self): def __init__(self):
pass #: docstring
self.value = 1
def bar(self): def bar(self):
pass pass

View File

@ -10,6 +10,7 @@
autosummary_dummy_module autosummary_dummy_module
autosummary_dummy_module.Foo autosummary_dummy_module.Foo
autosummary_dummy_module.Foo.Bar autosummary_dummy_module.Foo.Bar
autosummary_dummy_module.Foo.value
autosummary_dummy_module.bar autosummary_dummy_module.bar
autosummary_dummy_module.qux autosummary_dummy_module.qux
autosummary_importfail autosummary_importfail

View File

@ -293,15 +293,17 @@ def test_autosummary_generate(app, status, warning):
nodes.row, nodes.row,
nodes.row, nodes.row,
nodes.row, nodes.row,
nodes.row,
nodes.row)])]) nodes.row)])])
assert_node(doctree[4][0], addnodes.toctree, caption="An autosummary") assert_node(doctree[4][0], addnodes.toctree, caption="An autosummary")
assert len(doctree[3][0][0][2]) == 5 assert len(doctree[3][0][0][2]) == 6
assert doctree[3][0][0][2][0].astext() == 'autosummary_dummy_module\n\n' 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][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][2].astext() == 'autosummary_dummy_module.Foo.Bar()\n\n'
assert doctree[3][0][0][2][3].astext() == 'autosummary_dummy_module.bar(x[, y])\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.qux\n\na module-level attribute' 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'
module = (app.srcdir / 'generated' / 'autosummary_dummy_module.rst').read_text() module = (app.srcdir / 'generated' / 'autosummary_dummy_module.rst').read_text()
assert (' .. autosummary::\n' assert (' .. autosummary::\n'
@ -333,6 +335,11 @@ def test_autosummary_generate(app, status, warning):
'\n' '\n'
'.. autoclass:: Foo.Bar\n' in FooBar) '.. autoclass:: Foo.Bar\n' in FooBar)
Foo_value = (app.srcdir / 'generated' / 'autosummary_dummy_module.Foo.value.rst').read_text()
assert ('.. currentmodule:: autosummary_dummy_module\n'
'\n'
'.. autoattribute:: Foo.value' in Foo_value)
qux = (app.srcdir / 'generated' / 'autosummary_dummy_module.qux.rst').read_text() qux = (app.srcdir / 'generated' / 'autosummary_dummy_module.qux.rst').read_text()
assert ('.. currentmodule:: autosummary_dummy_module\n' assert ('.. currentmodule:: autosummary_dummy_module\n'
'\n' '\n'