From 7167b689b998d8822a40085a9395e3e4fc375b9c Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 14 Jun 2020 13:38:55 +0900 Subject: [PATCH 01/10] Do "twine check" on CI process --- .github/workflows/lint.yml | 2 +- tox.ini | 12 +++++++++++- utils/release-checklist | 4 ---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0c09c778bd8..fea1f17a2ac 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false matrix: - tool: [docslint, flake8, mypy] + tool: [docslint, flake8, mypy, twine] steps: - uses: actions/checkout@v2 diff --git a/tox.ini b/tox.ini index d9f04054406..ccfd60f8462 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 2.4.0 -envlist = docs,flake8,mypy,coverage,py{35,36,37,38,39},du{12,13,14,15} +envlist = docs,flake8,mypy,twine,coverage,py{35,36,37,38,39},du{12,13,14,15} [testenv] usedevelop = True @@ -88,6 +88,16 @@ extras = commands = python utils/doclinter.py CHANGES CONTRIBUTING.rst README.rst doc/ +[testenv:twine] +basepython = python3 +description = + Lint package. +deps = + twine +commands = + python setup.py release bdist_wheel sdist + twine check dist/* + [testenv:bindep] description = Install binary dependencies. diff --git a/utils/release-checklist b/utils/release-checklist index 5a60e59c84c..582d2668566 100644 --- a/utils/release-checklist +++ b/utils/release-checklist @@ -11,7 +11,6 @@ for stable releases * ``git commit -am 'Bump to X.Y.Z final'`` * ``make clean`` * ``python setup.py release bdist_wheel sdist`` -* ``twine check dist/Sphinx-*`` * ``twine upload dist/Sphinx-* --sign --identity [your GPG key]`` * open https://pypi.org/project/Sphinx/ and check there are no obvious errors * ``sh utils/bump_docker.sh X.Y.Z`` @@ -38,7 +37,6 @@ for first beta releases * ``git commit -am 'Bump to X.Y.0 beta1'`` * ``make clean`` * ``python setup.py release bdist_wheel sdist`` -* ``twine check dist/Sphinx-*`` * ``twine upload dist/Sphinx-* --sign --identity [your GPG key]`` * open https://pypi.org/project/Sphinx/ and check there are no obvious errors * ``git tag vX.Y.0b1`` @@ -67,7 +65,6 @@ for other beta releases * ``git commit -am 'Bump to X.Y.0 betaN'`` * ``make clean`` * ``python setup.py release bdist_wheel sdist`` -* ``twine check dist/Sphinx-*`` * ``twine upload dist/Sphinx-* --sign --identity [your GPG key]`` * open https://pypi.org/project/Sphinx/ and check there are no obvious errors * ``git tag vX.Y.0bN`` @@ -95,7 +92,6 @@ for major releases * ``git commit -am 'Bump to X.Y.0 final'`` * ``make clean`` * ``python setup.py release bdist_wheel sdist`` -* ``twine check dist/Sphinx-*`` * ``twine upload dist/Sphinx-* --sign --identity [your GPG key]`` * open https://pypi.org/project/Sphinx/ and check there are no obvious errors * ``sh utils/bump_docker.sh X.Y.Z`` From e860903cd82e93fc40abff0fc109388e08f90f15 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 22 Jun 2020 01:33:41 +0900 Subject: [PATCH 02/10] 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 1d7b119b45e..c1b97b58da5 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 ab75aaf5a9e..23245ad9c3e 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -977,19 +977,29 @@ def resolve_name(self, modname: str, parents: Any, path: str, base: Any 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 @@ def resolve_name(self, modname: str, parents: Any, path: str, base: Any 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 b28c62fc327..aa77c344c07 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 e4ec4a8152c..8c34c4e9fff 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -121,15 +121,16 @@ def verify(objtype, name, result): 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() From 0be08012cd8d9ba16d15ffa4d3540f56d3ac9dfc Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 21 Jun 2020 16:11:55 +0900 Subject: [PATCH 03/10] Fix #7856: autodoc: AttributeError is raised for non-class object --- CHANGES | 2 ++ sphinx/ext/autodoc/__init__.py | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index c1b97b58da5..f85970ffe08 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,8 @@ Bugs fixed ---------- * #7844: autodoc: Failed to detect module when relative module name given +* #7856: autodoc: AttributeError is raised when non-class object is given to + the autoclass directive Testing -------- diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 23245ad9c3e..bb89920bee7 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -826,7 +826,12 @@ def generate(self, more_content: Any = None, real_modname: str = None, self.add_line('', sourcename) # format the object's signature, if any - sig = self.format_signature() + try: + sig = self.format_signature() + except Exception as exc: + logger.warning(__('error while formatting signature for %s: %s'), + self.fullname, exc, type='autodoc') + return # generate the directive header and options, if applicable self.add_directive_header(sig) From 4e739b85eeb36b942efed0dff94d4962803ac9ca Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 21 Jun 2020 17:53:04 +0900 Subject: [PATCH 04/10] Fix #7850: autodoc: KeyError is raised for invalid mark up --- CHANGES | 2 ++ sphinx/ext/autodoc/typehints.py | 15 ++++++++++----- tests/test_ext_autodoc_configs.py | 8 ++++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index f85970ffe08..0dcdae502aa 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,8 @@ Bugs fixed * #7844: autodoc: Failed to detect module when relative module name given * #7856: autodoc: AttributeError is raised when non-class object is given to the autoclass directive +* #7850: autodoc: KeyError is raised for invalid mark up when autodoc_typehints + is 'description' Testing -------- diff --git a/sphinx/ext/autodoc/typehints.py b/sphinx/ext/autodoc/typehints.py index b763bdfc780..4f81a6eaeb8 100644 --- a/sphinx/ext/autodoc/typehints.py +++ b/sphinx/ext/autodoc/typehints.py @@ -46,11 +46,16 @@ def merge_typehints(app: Sphinx, domain: str, objtype: str, contentnode: Element if objtype == 'class' and app.config.autoclass_content not in ('init', 'both'): return - signature = cast(addnodes.desc_signature, contentnode.parent[0]) - if signature['module']: - fullname = '.'.join([signature['module'], signature['fullname']]) - else: - fullname = signature['fullname'] + try: + signature = cast(addnodes.desc_signature, contentnode.parent[0]) + if signature['module']: + fullname = '.'.join([signature['module'], signature['fullname']]) + else: + fullname = signature['fullname'] + except KeyError: + # signature node does not have valid context info for the target object + return + annotations = app.env.temp_data.get('annotations', {}) if annotations.get(fullname, {}): field_lists = [n for n in contentnode if isinstance(n, nodes.field_list)] diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py index 674620df043..88519b5fdf6 100644 --- a/tests/test_ext_autodoc_configs.py +++ b/tests/test_ext_autodoc_configs.py @@ -13,6 +13,8 @@ import pytest +from sphinx.testing import restructuredtext + from test_ext_autodoc import do_autodoc IS_PYPY = platform.python_implementation() == 'PyPy' @@ -633,6 +635,12 @@ def test_autodoc_typehints_description(app): in context) +@pytest.mark.sphinx('text', testroot='ext-autodoc', + confoverrides={'autodoc_typehints': "description"}) +def test_autodoc_typehints_description_for_invalid_node(app): + text = ".. py:function:: hello; world" + restructuredtext.parse(app, text) # raises no error + @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_default_options(app): From 358611970dcc0a22f0be0ee75153806e736ff427 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 26 Jun 2020 23:31:00 +0900 Subject: [PATCH 05/10] Revert "viewcode: Fix viewcode raises NoUri error on resolving phase except on HTML builders" This reverts commit c2ef1ad7e507c86442eae76e1ad7182383e13c8d. The error was completely resolved by #7683. So this is no longer needed. --- sphinx/ext/viewcode.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py index a2eeb78915a..dc24a1993e0 100644 --- a/sphinx/ext/viewcode.py +++ b/sphinx/ext/viewcode.py @@ -131,10 +131,8 @@ def env_merge_info(app: Sphinx, env: BuildEnvironment, docnames: Iterable[str], def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnode: Node ) -> Node: - if app.builder.format != 'html': - return None - elif node['reftype'] == 'viewcode': - # resolve our "viewcode" reference nodes -- they need special treatment + # resolve our "viewcode" reference nodes -- they need special treatment + if node['reftype'] == 'viewcode': return make_refnode(app.builder, node['refdoc'], node['reftarget'], node['refid'], contnode) From d50c7ff31944ad63e94b039b5465fdbe26bb04f9 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 26 Jun 2020 23:33:04 +0900 Subject: [PATCH 06/10] Fix #7806: viewcode: Failed to resolve viewcode references on 3rd party builders --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 0dcdae502aa..53a566d0f37 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,7 @@ Bugs fixed the autoclass directive * #7850: autodoc: KeyError is raised for invalid mark up when autodoc_typehints is 'description' +* #7806: viewcode: Failed to resolve viewcode references on 3rd party builders Testing -------- From 25e8171a0a8636eec93b6b60d87822e1bd3ffb74 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 28 Jun 2020 15:22:23 +0900 Subject: [PATCH 07/10] Fix #7812: autosummary: generates broken stub files (again) --- CHANGES | 2 ++ sphinx/ext/autosummary/generate.py | 13 +++++++++---- sphinx/util/__init__.py | 15 +-------------- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/CHANGES b/CHANGES index 0dcdae502aa..86f31a87561 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,8 @@ Bugs fixed the autoclass directive * #7850: autodoc: KeyError is raised for invalid mark up when autodoc_typehints is 'description' +* #7812: autosummary: generates broken stub files if the target code contains + an attribute and module that are same name Testing -------- diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index fe7daf21401..40fdb5181df 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -230,7 +230,8 @@ def scan(self, imported_members: bool) -> List[str]: def generate_autosummary_content(name: str, obj: Any, parent: Any, template: AutosummaryRenderer, template_name: str, imported_members: bool, app: Any, - recursive: bool, context: Dict) -> str: + recursive: bool, context: Dict, + modname: str = None, qualname: str = None) -> str: doc = get_documenter(app, obj, parent) def skip_member(obj: Any, name: str, objtype: str) -> bool: @@ -319,7 +320,9 @@ def get_modules(obj: Any) -> Tuple[List[str], List[str]]: ns['attributes'], ns['all_attributes'] = \ get_members(obj, {'attribute', 'property'}) - modname, qualname = split_full_qualified_name(name) + if modname is None or qualname is None: + modname, qualname = split_full_qualified_name(name) + if doc.objtype in ('method', 'attribute', 'property'): ns['class'] = qualname.rsplit(".", 1)[0] @@ -401,7 +404,8 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, ensuredir(path) try: - name, obj, parent, mod_name = import_by_name(entry.name) + 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 @@ -411,7 +415,8 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, context.update(app.config.autosummary_context) content = generate_autosummary_content(name, obj, parent, template, entry.template, - imported_members, app, entry.recursive, context) + imported_members, app, entry.recursive, context, + modname, qualname) filename = os.path.join(path, name + suffix) if os.path.isfile(filename): diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index aa77c344c07..1ca82ad21a1 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -615,24 +615,11 @@ def split_full_qualified_name(name: str) -> Tuple[str, str]: Therefore you need to mock 3rd party modules if needed before calling this function. """ - from sphinx.util import inspect - parts = name.split('.') for i, part in enumerate(parts, 1): try: modname = ".".join(parts[:i]) - module = import_module(modname) - - # check the module has a member named as attrname - # - # Note: This is needed to detect the attribute having the same name - # as the module. - # ref: https://github.com/sphinx-doc/sphinx/issues/7812 - attrname = parts[i] - if hasattr(module, attrname): - value = inspect.safe_getattr(module, attrname) - if not inspect.ismodule(value): - return ".".join(parts[:i]), ".".join(parts[i:]) + import_module(modname) except ImportError: if parts[:i - 1]: return ".".join(parts[:i - 1]), ".".join(parts[i - 1:]) From 67cb8e81b2ba5fecc576db3aff52c172d44fab84 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 29 Jun 2020 02:08:22 +0900 Subject: [PATCH 08/10] CI: Build document on GitHub Actions --- .github/workflows/builddoc.yml | 21 +++++++++++++++++++++ .travis.yml | 2 -- 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/builddoc.yml diff --git a/.github/workflows/builddoc.yml b/.github/workflows/builddoc.yml new file mode 100644 index 00000000000..809fb68e6d7 --- /dev/null +++ b/.github/workflows/builddoc.yml @@ -0,0 +1,21 @@ +name: Build document + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: 3.6 + - name: Install dependencies + run: | + sudo apt update + sudo apt install -y graphviz + pip install -U tox + - name: Run Tox + run: tox -e docs diff --git a/.travis.yml b/.travis.yml index 340022cd81b..d4cbfd5822a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,8 +27,6 @@ jobs: - python: 'nightly' env: - TOXENV=du16 - - python: '3.6' - env: TOXENV=docs - language: node_js node_js: '10.7' From df6333a25003f5fa9f8cdfc0a14c953a9ac0d663 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 4 Jul 2020 20:46:01 +0900 Subject: [PATCH 09/10] CI: Do testing more verbose --- .circleci/config.yml | 2 +- .github/workflows/main.yml | 2 +- .travis.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7f4de4ae05d..6b5c7379b6d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,6 +9,6 @@ jobs: - run: /python3.6/bin/pip install -U pip setuptools - run: /python3.6/bin/pip install -U .[test] - run: mkdir -p test-reports/pytest - - run: make test PYTHON=/python3.6/bin/python TEST=--junitxml=test-reports/pytest/results.xml + - run: make test PYTHON=/python3.6/bin/python TEST="--junitxml=test-reports/pytest/results.xml -vv" - store_test_results: path: test-reports diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fa2c1154a0d..7acfef6d255 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,4 +18,4 @@ jobs: - name: Install dependencies run: pip install -U tox - name: Run Tox - run: tox -e py + run: tox -e py -- -vv diff --git a/.travis.yml b/.travis.yml index d4cbfd5822a..d73be03ec50 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,7 +39,7 @@ install: - if [ $IS_PYTHON = false ]; then npm install; fi script: - - if [ $IS_PYTHON = true ]; then tox -- -v; fi + - if [ $IS_PYTHON = true ]; then tox -- -vv; fi - if [ $IS_PYTHON = false ]; then npm test; fi after_success: From 659846b8054dbb1517874fc2d08b74eaded14bf3 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 27 Jun 2020 22:05:02 +0900 Subject: [PATCH 10/10] Fix #7812: autodoc: crashed when given name is conflicted Note: this partially reverts #7594 to avoid errors. --- CHANGES | 2 + sphinx/ext/autodoc/__init__.py | 52 +++++-------------- .../target/name_conflict/__init__.py | 5 ++ .../target/name_conflict/foo.py | 2 + tests/test_ext_autodoc.py | 43 +++++++++++---- tests/test_ext_autodoc_autofunction.py | 4 +- 6 files changed, 56 insertions(+), 52 deletions(-) create mode 100644 tests/roots/test-ext-autodoc/target/name_conflict/__init__.py create mode 100644 tests/roots/test-ext-autodoc/target/name_conflict/foo.py diff --git a/CHANGES b/CHANGES index 76dcd673dc8..985116851d1 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,8 @@ Bugs fixed the autoclass directive * #7850: autodoc: KeyError is raised for invalid mark up when autodoc_typehints 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 an attribute and module that are same name * #7806: viewcode: Failed to resolve viewcode references on 3rd party builders diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index bb89920bee7..85bea8c4336 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -32,7 +32,6 @@ from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.util import inspect 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.inspect import getdoc, object_description, safe_getattr, stringify_signature from sphinx.util.typing import stringify as stringify_typehint @@ -980,31 +979,15 @@ def resolve_name(self, modname: str, parents: Any, path: str, base: Any ) -> Tuple[str, List[str]]: if modname is None: 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 = [] + modname = path.rstrip('.') 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') - parents = [] - + # ... or in the scope of a module directive if not modname: modname = self.env.ref_context.get('py:module') - + # ... else, it stays None, which means invalid return modname, parents + [base] @@ -1030,25 +1013,14 @@ def resolve_name(self, modname: str, parents: Any, path: str, base: Any # ... if still None, there's no way to know if mod_cls is None: return None, [] - - 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, sep, cls = mod_cls.rpartition('.') + parents = [cls] + # if the module name is still missing, get it like above + if not modname: 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, mod_cls]) - modname, qualname = split_full_qualified_name(stripped) - - if qualname: - parents = qualname.split(".") - else: - parents = [] - + if not modname: + modname = self.env.ref_context.get('py:module') + # ... else, it stays None, which means invalid return modname, parents + [base] diff --git a/tests/roots/test-ext-autodoc/target/name_conflict/__init__.py b/tests/roots/test-ext-autodoc/target/name_conflict/__init__.py new file mode 100644 index 00000000000..7ad521fc25a --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/name_conflict/__init__.py @@ -0,0 +1,5 @@ +from .foo import bar + +class foo: + """docstring of target.name_conflict::foo.""" + pass diff --git a/tests/roots/test-ext-autodoc/target/name_conflict/foo.py b/tests/roots/test-ext-autodoc/target/name_conflict/foo.py new file mode 100644 index 00000000000..bb83ca0d4b7 --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/name_conflict/foo.py @@ -0,0 +1,2 @@ +class bar: + """docstring of target.name_conflict.foo::bar.""" diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index 8c34c4e9fff..652c7f10bca 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -121,8 +121,8 @@ def verify(objtype, name, result): verify('class', 'Base', ('test_ext_autodoc', ['Base'], None, None)) # for members - directive.env.ref_context['py:module'] = 'sphinx.testing' - verify('method', 'util.SphinxTestApp.cleanup', + directive.env.ref_context['py:module'] = 'sphinx.testing.util' + verify('method', 'SphinxTestApp.cleanup', ('sphinx.testing.util', ['SphinxTestApp', 'cleanup'], None, None)) directive.env.ref_context['py:module'] = 'sphinx.testing.util' 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) assert list(actual) == [ '', - '.. py:class:: Outer.Inner()', - ' :module: target', + '.. py:class:: Inner()', + ' :module: target.Outer', '', ' Foo', '', '', - ' .. py:method:: Outer.Inner.meth()', - ' :module: target', + ' .. py:method:: Inner.meth()', + ' :module: target.Outer', '', ' Foo', '', @@ -1884,8 +1884,8 @@ 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') + app.env.ref_context['py:module'] = 'target.classes' + actual = do_autodoc(app, 'class', 'Foo') assert list(actual) == [ '', '.. py:class:: Foo()', @@ -1896,8 +1896,8 @@ def test_pymodule_for_ModuleLevelDocumenter(app): @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') + app.env.ref_context['py:module'] = 'target.methods' + actual = do_autodoc(app, 'method', 'Base.meth') assert list(actual) == [ '', '.. py:method:: Base.meth()', @@ -1937,3 +1937,26 @@ def test_autodoc(app, status, warning): alias of bug2437.autodoc_dummy_foo.Foo""" 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.', + '', + ] diff --git a/tests/test_ext_autodoc_autofunction.py b/tests/test_ext_autodoc_autofunction.py index 579ad9f488e..da090d83a90 100644 --- a/tests/test_ext_autodoc_autofunction.py +++ b/tests/test_ext_autodoc_autofunction.py @@ -85,8 +85,8 @@ def test_methoddescriptor(app): actual = do_autodoc(app, 'function', 'builtins.int.__add__') assert list(actual) == [ '', - '.. py:function:: int.__add__(self, value, /)', - ' :module: builtins', + '.. py:function:: __add__(self, value, /)', + ' :module: builtins.int', '', ' Return self+value.', '',