summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.circleci/config.yml2
-rw-r--r--.github/workflows/builddoc.yml21
-rw-r--r--.github/workflows/lint.yml2
-rw-r--r--.github/workflows/main.yml2
-rw-r--r--.travis.yml4
-rw-r--r--CHANGES11
-rw-r--r--sphinx/ext/autodoc/__init__.py27
-rw-r--r--sphinx/ext/autodoc/typehints.py15
-rw-r--r--sphinx/ext/autosummary/generate.py13
-rw-r--r--sphinx/ext/viewcode.py6
-rw-r--r--sphinx/util/__init__.py20
-rw-r--r--tests/roots/test-ext-autodoc/target/name_conflict/__init__.py5
-rw-r--r--tests/roots/test-ext-autodoc/target/name_conflict/foo.py2
-rw-r--r--tests/test_ext_autodoc.py81
-rw-r--r--tests/test_ext_autodoc_autofunction.py4
-rw-r--r--tests/test_ext_autodoc_configs.py8
-rw-r--r--tox.ini12
-rw-r--r--utils/release-checklist4
18 files changed, 170 insertions, 69 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 7f4de4ae0..6b5c7379b 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/builddoc.yml b/.github/workflows/builddoc.yml
new file mode 100644
index 000000000..809fb68e6
--- /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/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 0c09c778b..fea1f17a2 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/.github/workflows/main.yml b/.github/workflows/main.yml
index fa2c1154a..7acfef6d2 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 340022cd8..d73be03ec 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'
@@ -41,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:
diff --git a/CHANGES b/CHANGES
index 2fb8890cc..e69c94369 100644
--- a/CHANGES
+++ b/CHANGES
@@ -53,7 +53,16 @@ Features added
Bugs fixed
----------
-* #7811: sphinx.util.inspect causes circular import problem
+* #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'
+* #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
Testing
--------
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py
index ab75aaf5a..85bea8c43 100644
--- a/sphinx/ext/autodoc/__init__.py
+++ b/sphinx/ext/autodoc/__init__.py
@@ -32,7 +32,6 @@ from sphinx.locale import _, __
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
@@ -826,7 +825,12 @@ class Documenter:
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)
@@ -975,14 +979,8 @@ class ModuleLevelDocumenter(Documenter):
) -> Tuple[str, List[str]]:
if modname is None:
if path:
- stripped = path.rstrip('.')
- modname, qualname = split_full_qualified_name(stripped)
- if qualname:
- parents = qualname.split(".")
- else:
- parents = []
-
- if modname is None:
+ 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')
@@ -1015,13 +1013,8 @@ class ClassLevelDocumenter(Documenter):
# ... if still None, there's no way to know
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(".")
-
+ 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')
diff --git a/sphinx/ext/autodoc/typehints.py b/sphinx/ext/autodoc/typehints.py
index b763bdfc7..4f81a6eae 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/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py
index 7583d9894..d908e2088 100644
--- a/sphinx/ext/autosummary/generate.py
+++ b/sphinx/ext/autosummary/generate.py
@@ -230,7 +230,8 @@ class ModuleScanner:
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 generate_autosummary_content(name: str, obj: Any, parent: Any,
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/ext/viewcode.py b/sphinx/ext/viewcode.py
index a2eeb7891..dc24a1993 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)
diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py
index 286c91714..a864bb97a 100644
--- a/sphinx/util/__init__.py
+++ b/sphinx/util/__init__.py
@@ -615,26 +615,16 @@ 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:
- 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/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 000000000..7ad521fc2
--- /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 000000000..bb83ca0d4
--- /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 e4ec4a815..652c7f10b 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'
- verify('method', 'util.SphinxTestApp.cleanup',
- ('foo', ['util', 'SphinxTestApp', 'cleanup'], None, None))
- directive.env.ref_context['py:module'] = 'util'
+ 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'
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):
@@ -800,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',
'',
@@ -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.classes'
+ actual = do_autodoc(app, 'class', '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.methods'
+ actual = do_autodoc(app, 'method', '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()
@@ -1899,3 +1937,26 @@ my_name
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 579ad9f48..da090d83a 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.',
'',
diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py
index 674620df0..88519b5fd 100644
--- a/tests/test_ext_autodoc_configs.py
+++ b/tests/test_ext_autodoc_configs.py
@@ -13,6 +13,8 @@ import sys
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):
diff --git a/tox.ini b/tox.ini
index d9f040544..ccfd60f84 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 5a60e59c8..582d26685 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``