From e860903cd82e93fc40abff0fc109388e08f90f15 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 22 Jun 2020 01:33:41 +0900 Subject: [PATCH] Fix #7844: autodoc: Failed to detect module when relative module name given --- CHANGES | 2 ++ sphinx/ext/autodoc/__init__.py | 50 ++++++++++++++++++++++------------ sphinx/util/__init__.py | 5 +++- tests/test_ext_autodoc.py | 48 ++++++++++++++++++++++++++++---- 4 files changed, 82 insertions(+), 23 deletions(-) diff --git a/CHANGES b/CHANGES index 1d7b119b4..c1b97b58d 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,8 @@ Features added Bugs fixed ---------- +* #7844: autodoc: Failed to detect module when relative module name given + Testing -------- diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index ab75aaf5a..23245ad9c 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -977,19 +977,29 @@ class ModuleLevelDocumenter(Documenter): if path: stripped = path.rstrip('.') modname, qualname = split_full_qualified_name(stripped) + 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') + # ... or in the scope of a module directive + if not modname: + modname = self.env.ref_context.get('py:module') + + if modname: + stripped = ".".join([modname, stripped]) + 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 ... + else: modname = self.env.temp_data.get('autodoc:module') - # ... or in the scope of a module directive + parents = [] + if not modname: modname = self.env.ref_context.get('py:module') - # ... else, it stays None, which means invalid + return modname, parents + [base] @@ -1016,18 +1026,24 @@ class ClassLevelDocumenter(Documenter): if mod_cls is None: return None, [] - try: - modname, qualname = split_full_qualified_name(mod_cls) - parents = qualname.split(".") if qualname else [] - except ImportError: - parents = mod_cls.split(".") - - # if the module name is still missing, get it like above - if not modname: + modname, qualname = split_full_qualified_name(mod_cls) + 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') - if not modname: - modname = self.env.ref_context.get('py:module') - # ... else, it stays None, which means invalid + # ... or in the scope of a module directive + if not modname: + modname = self.env.ref_context.get('py:module') + + if modname: + stripped = ".".join([modname, mod_cls]) + modname, qualname = split_full_qualified_name(stripped) + + if qualname: + parents = qualname.split(".") + else: + parents = [] + return modname, parents + [base] diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index b28c62fc3..aa77c344c 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -634,7 +634,10 @@ def split_full_qualified_name(name: str) -> Tuple[str, str]: if not inspect.ismodule(value): return ".".join(parts[:i]), ".".join(parts[i:]) except ImportError: - return ".".join(parts[:i - 1]), ".".join(parts[i - 1:]) + if parts[:i - 1]: + return ".".join(parts[:i - 1]), ".".join(parts[i - 1:]) + else: + return None, ".".join(parts) except IndexError: pass diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index e4ec4a815..8c34c4e9f 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -121,15 +121,16 @@ def test_parse_name(app): verify('class', 'Base', ('test_ext_autodoc', ['Base'], None, None)) # for members - directive.env.ref_context['py:module'] = 'foo' + directive.env.ref_context['py:module'] = 'sphinx.testing' verify('method', 'util.SphinxTestApp.cleanup', - ('foo', ['util', 'SphinxTestApp', 'cleanup'], None, None)) - directive.env.ref_context['py:module'] = 'util' + ('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None)) + directive.env.ref_context['py:module'] = 'sphinx.testing.util' directive.env.ref_context['py:class'] = 'Foo' directive.env.temp_data['autodoc:class'] = 'SphinxTestApp' - verify('method', 'cleanup', ('util', ['SphinxTestApp', 'cleanup'], None, None)) + verify('method', 'cleanup', + ('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None)) verify('method', 'SphinxTestApp.cleanup', - ('util', ['SphinxTestApp', 'cleanup'], None, None)) + ('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None)) def test_format_signature(app): @@ -1881,6 +1882,43 @@ def test_overload(app): ] +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_pymodule_for_ModuleLevelDocumenter(app): + app.env.ref_context['py:module'] = 'target' + actual = do_autodoc(app, 'class', 'classes.Foo') + assert list(actual) == [ + '', + '.. py:class:: Foo()', + ' :module: target.classes', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_pymodule_for_ClassLevelDocumenter(app): + app.env.ref_context['py:module'] = 'target' + actual = do_autodoc(app, 'method', 'methods.Base.meth') + assert list(actual) == [ + '', + '.. py:method:: Base.meth()', + ' :module: target.methods', + '', + ] + + +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_pyclass_for_ClassLevelDocumenter(app): + app.env.ref_context['py:module'] = 'target.methods' + app.env.ref_context['py:class'] = 'Base' + actual = do_autodoc(app, 'method', 'meth') + assert list(actual) == [ + '', + '.. py:method:: Base.meth()', + ' :module: target.methods', + '', + ] + + @pytest.mark.sphinx('dummy', testroot='ext-autodoc') def test_autodoc(app, status, warning): app.builder.build_all()