Fix #7812: autodoc: crashed when given name is conflicted

Note: this partially reverts #7594 to avoid errors.
This commit is contained in:
Takeshi KOMIYA 2020-06-27 22:05:02 +09:00
parent 5afc77ee27
commit 659846b805
6 changed files with 56 additions and 52 deletions

View File

@ -21,6 +21,8 @@ Bugs fixed
the autoclass directive the autoclass directive
* #7850: autodoc: KeyError is raised for invalid mark up when autodoc_typehints * #7850: autodoc: KeyError is raised for invalid mark up when autodoc_typehints
is 'description' is 'description'
* #7812: autodoc: crashed if the target name matches to both an attribute and
module that are same name
* #7812: autosummary: generates broken stub files if the target code contains * #7812: autosummary: generates broken stub files if the target code contains
an attribute and module that are same name an attribute and module that are same name
* #7806: viewcode: Failed to resolve viewcode references on 3rd party builders * #7806: viewcode: Failed to resolve viewcode references on 3rd party builders

View File

@ -32,7 +32,6 @@ from sphinx.locale import _, __
from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.util import inspect from sphinx.util import inspect
from sphinx.util import logging from sphinx.util import logging
from sphinx.util import split_full_qualified_name
from sphinx.util.docstrings import extract_metadata, prepare_docstring from sphinx.util.docstrings import extract_metadata, prepare_docstring
from sphinx.util.inspect import getdoc, object_description, safe_getattr, stringify_signature from sphinx.util.inspect import getdoc, object_description, safe_getattr, stringify_signature
from sphinx.util.typing import stringify as stringify_typehint from sphinx.util.typing import stringify as stringify_typehint
@ -980,31 +979,15 @@ class ModuleLevelDocumenter(Documenter):
) -> Tuple[str, List[str]]: ) -> Tuple[str, List[str]]:
if modname is None: if modname is None:
if path: if path:
stripped = path.rstrip('.') modname = 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 = []
else: else:
# if documenting a toplevel object without explicit module,
# it can be contained in another auto directive ...
modname = self.env.temp_data.get('autodoc:module') modname = self.env.temp_data.get('autodoc:module')
parents = [] # ... or in the scope of a module directive
if not modname: if not modname:
modname = self.env.ref_context.get('py:module') modname = self.env.ref_context.get('py:module')
# ... else, it stays None, which means invalid
return modname, parents + [base] return modname, parents + [base]
@ -1030,25 +1013,14 @@ class ClassLevelDocumenter(Documenter):
# ... if still None, there's no way to know # ... if still None, there's no way to know
if mod_cls is None: if mod_cls is None:
return None, [] return None, []
modname, sep, cls = mod_cls.rpartition('.')
modname, qualname = split_full_qualified_name(mod_cls) parents = [cls]
if modname is None: # if the module name is still missing, get it like above
# if documenting a toplevel object without explicit module, if not modname:
# it can be contained in another auto directive ...
modname = self.env.temp_data.get('autodoc:module') modname = self.env.temp_data.get('autodoc:module')
# ... or in the scope of a module directive if not modname:
if not modname: modname = self.env.ref_context.get('py:module')
modname = self.env.ref_context.get('py:module') # ... else, it stays None, which means invalid
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] return modname, parents + [base]

View File

@ -0,0 +1,5 @@
from .foo import bar
class foo:
"""docstring of target.name_conflict::foo."""
pass

View File

@ -0,0 +1,2 @@
class bar:
"""docstring of target.name_conflict.foo::bar."""

View File

@ -121,8 +121,8 @@ def test_parse_name(app):
verify('class', 'Base', ('test_ext_autodoc', ['Base'], None, None)) verify('class', 'Base', ('test_ext_autodoc', ['Base'], None, None))
# for members # for members
directive.env.ref_context['py:module'] = 'sphinx.testing' directive.env.ref_context['py:module'] = 'sphinx.testing.util'
verify('method', 'util.SphinxTestApp.cleanup', verify('method', 'SphinxTestApp.cleanup',
('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None)) ('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None))
directive.env.ref_context['py:module'] = 'sphinx.testing.util' directive.env.ref_context['py:module'] = 'sphinx.testing.util'
directive.env.ref_context['py:class'] = 'Foo' directive.env.ref_context['py:class'] = 'Foo'
@ -801,14 +801,14 @@ def test_autodoc_inner_class(app):
actual = do_autodoc(app, 'class', 'target.Outer.Inner', options) actual = do_autodoc(app, 'class', 'target.Outer.Inner', options)
assert list(actual) == [ assert list(actual) == [
'', '',
'.. py:class:: Outer.Inner()', '.. py:class:: Inner()',
' :module: target', ' :module: target.Outer',
'', '',
' Foo', ' Foo',
'', '',
'', '',
' .. py:method:: Outer.Inner.meth()', ' .. py:method:: Inner.meth()',
' :module: target', ' :module: target.Outer',
'', '',
' Foo', ' Foo',
'', '',
@ -1884,8 +1884,8 @@ def test_overload(app):
@pytest.mark.sphinx('html', testroot='ext-autodoc') @pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_pymodule_for_ModuleLevelDocumenter(app): def test_pymodule_for_ModuleLevelDocumenter(app):
app.env.ref_context['py:module'] = 'target' app.env.ref_context['py:module'] = 'target.classes'
actual = do_autodoc(app, 'class', 'classes.Foo') actual = do_autodoc(app, 'class', 'Foo')
assert list(actual) == [ assert list(actual) == [
'', '',
'.. py:class:: Foo()', '.. py:class:: Foo()',
@ -1896,8 +1896,8 @@ def test_pymodule_for_ModuleLevelDocumenter(app):
@pytest.mark.sphinx('html', testroot='ext-autodoc') @pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_pymodule_for_ClassLevelDocumenter(app): def test_pymodule_for_ClassLevelDocumenter(app):
app.env.ref_context['py:module'] = 'target' app.env.ref_context['py:module'] = 'target.methods'
actual = do_autodoc(app, 'method', 'methods.Base.meth') actual = do_autodoc(app, 'method', 'Base.meth')
assert list(actual) == [ assert list(actual) == [
'', '',
'.. py:method:: Base.meth()', '.. py:method:: Base.meth()',
@ -1937,3 +1937,26 @@ my_name
alias of bug2437.autodoc_dummy_foo.Foo""" alias of bug2437.autodoc_dummy_foo.Foo"""
assert warning.getvalue() == '' assert warning.getvalue() == ''
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_name_conflict(app):
actual = do_autodoc(app, 'class', 'target.name_conflict.foo')
assert list(actual) == [
'',
'.. py:class:: foo()',
' :module: target.name_conflict',
'',
' docstring of target.name_conflict::foo.',
'',
]
actual = do_autodoc(app, 'class', 'target.name_conflict.foo.bar')
assert list(actual) == [
'',
'.. py:class:: bar()',
' :module: target.name_conflict.foo',
'',
' docstring of target.name_conflict.foo::bar.',
'',
]

View File

@ -85,8 +85,8 @@ def test_methoddescriptor(app):
actual = do_autodoc(app, 'function', 'builtins.int.__add__') actual = do_autodoc(app, 'function', 'builtins.int.__add__')
assert list(actual) == [ assert list(actual) == [
'', '',
'.. py:function:: int.__add__(self, value, /)', '.. py:function:: __add__(self, value, /)',
' :module: builtins', ' :module: builtins.int',
'', '',
' Return self+value.', ' Return self+value.',
'', '',