diff --git a/CHANGES b/CHANGES index 9db991af8..52f2abdfe 100644 --- a/CHANGES +++ b/CHANGES @@ -85,6 +85,7 @@ Bugs fixed * #6857: autodoc: failed to detect a classmethod on Enum class * #7562: autodoc: a typehint contains spaces is wrongly rendered under autodoc_typehints='description' mode +* #7551: autodoc: failed to import nested class * #7551: autosummary: a nested class is indexed as non-nested class * #7535: sphinx-autogen: crashes when custom template uses inheritance * #7536: sphinx-autogen: crashes when template uses i18n feature diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 7cf8b584b..8bf2e82a2 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -31,7 +31,7 @@ from sphinx.locale import _, __ from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.util import inspect from sphinx.util import logging -from sphinx.util import rpartition +from sphinx.util import split_full_qualified_name from sphinx.util.docstrings import extract_metadata, prepare_docstring from sphinx.util.inspect import getdoc, object_description, safe_getattr, stringify_signature from sphinx.util.typing import stringify as stringify_typehint @@ -312,7 +312,8 @@ class Documenter: modname = None parents = [] - self.modname, self.objpath = self.resolve_name(modname, parents, path, base) + with mock(self.env.config.autodoc_mock_imports): + self.modname, self.objpath = self.resolve_name(modname, parents, path, base) if not self.modname: return False @@ -898,8 +899,14 @@ class ModuleLevelDocumenter(Documenter): ) -> Tuple[str, List[str]]: if modname is None: if path: - modname = path.rstrip('.') - else: + stripped = path.rstrip('.') + modname, qualname = split_full_qualified_name(stripped) + if qualname: + parents = qualname.split(".") + else: + parents = [] + + if modname is None: # if documenting a toplevel object without explicit module, # it can be contained in another auto directive ... modname = self.env.temp_data.get('autodoc:module') @@ -932,8 +939,13 @@ class ClassLevelDocumenter(Documenter): # ... if still None, there's no way to know if mod_cls is None: return None, [] - modname, cls = rpartition(mod_cls, '.') - parents = [cls] + + try: + modname, qualname = split_full_qualified_name(mod_cls) + parents = qualname.split(".") + except ImportError: + parents = mod_cls.split(".") + # if the module name is still missing, get it like above if not modname: modname = self.env.temp_data.get('autodoc:module') diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index 23ba2e733..4833daa8b 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -103,27 +103,27 @@ def test_parse_name(app): directive = make_directive_bridge(app.env) # for modules - verify('module', 'test_autodoc', ('test_autodoc', [], None, None)) - verify('module', 'test.test_autodoc', ('test.test_autodoc', [], None, None)) + verify('module', 'test_ext_autodoc', ('test_ext_autodoc', [], None, None)) + verify('module', 'test.test_ext_autodoc', ('test.test_ext_autodoc', [], None, None)) verify('module', 'test(arg)', ('test', [], 'arg', None)) assert 'signature arguments' in app._warning.getvalue() # for functions/classes - verify('function', 'test_autodoc.raises', - ('test_autodoc', ['raises'], None, None)) - verify('function', 'test_autodoc.raises(exc) -> None', - ('test_autodoc', ['raises'], 'exc', 'None')) - directive.env.temp_data['autodoc:module'] = 'test_autodoc' - verify('function', 'raises', ('test_autodoc', ['raises'], None, None)) + verify('function', 'test_ext_autodoc.raises', + ('test_ext_autodoc', ['raises'], None, None)) + verify('function', 'test_ext_autodoc.raises(exc) -> None', + ('test_ext_autodoc', ['raises'], 'exc', 'None')) + directive.env.temp_data['autodoc:module'] = 'test_ext_autodoc' + verify('function', 'raises', ('test_ext_autodoc', ['raises'], None, None)) del directive.env.temp_data['autodoc:module'] - directive.env.ref_context['py:module'] = 'test_autodoc' - verify('function', 'raises', ('test_autodoc', ['raises'], None, None)) - verify('class', 'Base', ('test_autodoc', ['Base'], None, None)) + directive.env.ref_context['py:module'] = 'test_ext_autodoc' + verify('function', 'raises', ('test_ext_autodoc', ['raises'], None, None)) + verify('class', 'Base', ('test_ext_autodoc', ['Base'], None, None)) # for members directive.env.ref_context['py:module'] = 'foo' verify('method', 'util.SphinxTestApp.cleanup', - ('util', ['SphinxTestApp', 'cleanup'], None, None)) + ('foo', ['util', 'SphinxTestApp', 'cleanup'], None, None)) directive.env.ref_context['py:module'] = 'util' directive.env.ref_context['py:class'] = 'Foo' directive.env.temp_data['autodoc:class'] = 'SphinxTestApp' @@ -735,14 +735,14 @@ def test_autodoc_inner_class(app): actual = do_autodoc(app, 'class', 'target.Outer.Inner', options) assert list(actual) == [ '', - '.. py:class:: Inner', - ' :module: target.Outer', + '.. py:class:: Outer.Inner', + ' :module: target', '', ' Foo', '', '', - ' .. py:method:: Inner.meth()', - ' :module: target.Outer', + ' .. py:method:: Outer.Inner.meth()', + ' :module: target', '', ' Foo', '',