Skip to content

Commit

Permalink
Merge pull request #8028 from tk0miya/8011_ivar_for_autosummary
Browse files Browse the repository at this point in the history
Close #8011: autosummary: Support instance attributes
  • Loading branch information
tk0miya committed Aug 2, 2020
2 parents 89df9a1 + 92e863f commit 03a6028
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 10 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Expand Up @@ -20,6 +20,8 @@ Features added
* #2076: autodoc: Allow overriding of exclude-members in skip-member function
* #2024: autosummary: Add :confval:`autosummary_filename_map` to avoid conflict
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
of line numbers for code-blocks
* #7853: C and C++, support parameterized GNU style attributes.
Expand Down
35 changes: 32 additions & 3 deletions sphinx/ext/autosummary/__init__.py
Expand Up @@ -75,7 +75,7 @@
from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning
from sphinx.environment import BuildEnvironment
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.importer import import_module
from sphinx.ext.autodoc.mock import mock
Expand Down Expand Up @@ -285,6 +285,19 @@ def run(self) -> List[Node]:

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]]:
"""Try to import the given names, and return a list of
``[(name, signature, summary_string, real_name), ...]``.
Expand All @@ -302,8 +315,7 @@ def get_items(self, names: List[str]) -> List[Tuple[str, str, str, str]]:
display_name = name.split('.')[-1]

try:
with mock(self.config.autosummary_mock_imports):
real_name, obj, parent, modname = import_by_name(name, prefixes=prefixes)
real_name, obj, parent, modname = self.import_by_name(name, prefixes=prefixes)
except ImportError:
logger.warning(__('autosummary: failed to import %s'), name,
location=self.get_source_info())
Expand Down Expand Up @@ -659,6 +671,23 @@ def _import_by_name(name: str) -> Tuple[Any, Any, str]:
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) -------------------------------------------

def autolink_role(typ: str, rawtext: str, etext: str, lineno: int, inliner: Inliner,
Expand Down
11 changes: 8 additions & 3 deletions sphinx/ext/autosummary/generate.py
Expand Up @@ -41,7 +41,7 @@
from sphinx.config import Config
from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning
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.pycode import ModuleAnalyzer, PycodeError
from sphinx.registry import SphinxComponentRegistry
Expand Down Expand Up @@ -413,8 +413,13 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None,
name, obj, parent, modname = import_by_name(entry.name)
qualname = name.replace(modname + ".", "")
except ImportError as e:
_warn(__('[autosummary] failed to import %r: %s') % (entry.name, e))
continue
try:
# 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 = {}
if app:
Expand Down
3 changes: 2 additions & 1 deletion tests/roots/test-ext-autosummary/autosummary_dummy_module.py
Expand Up @@ -16,7 +16,8 @@ class Bar:
pass

def __init__(self):
pass
#: docstring
self.value = 1

def bar(self):
pass
Expand Down
1 change: 1 addition & 0 deletions tests/roots/test-ext-autosummary/index.rst
Expand Up @@ -10,6 +10,7 @@
autosummary_dummy_module
autosummary_dummy_module.Foo
autosummary_dummy_module.Foo.Bar
autosummary_dummy_module.Foo.value
autosummary_dummy_module.bar
autosummary_dummy_module.qux
autosummary_importfail
13 changes: 10 additions & 3 deletions tests/test_ext_autosummary.py
Expand Up @@ -293,15 +293,17 @@ def test_autosummary_generate(app, status, warning):
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]) == 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][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.bar(x[, y])\n\n'
assert doctree[3][0][0][2][4].astext() == 'autosummary_dummy_module.qux\n\na module-level attribute'
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'

module = (app.srcdir / 'generated' / 'autosummary_dummy_module.rst').read_text()
assert (' .. autosummary::\n'
Expand Down Expand Up @@ -333,6 +335,11 @@ def test_autosummary_generate(app, status, warning):
'\n'
'.. 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()
assert ('.. currentmodule:: autosummary_dummy_module\n'
'\n'
Expand Down

0 comments on commit 03a6028

Please sign in to comment.