mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch '3.1.x' into 3.x
This commit is contained in:
@@ -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
|
||||
|
||||
21
.github/workflows/builddoc.yml
vendored
Normal file
21
.github/workflows/builddoc.yml
vendored
Normal file
@@ -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
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
tool: [docslint, flake8, mypy]
|
||||
tool: [docslint, flake8, mypy, twine]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
11
CHANGES
11
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
|
||||
--------
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
from .foo import bar
|
||||
|
||||
class foo:
|
||||
"""docstring of target.name_conflict::foo."""
|
||||
pass
|
||||
2
tests/roots/test-ext-autodoc/target/name_conflict/foo.py
Normal file
2
tests/roots/test-ext-autodoc/target/name_conflict/foo.py
Normal file
@@ -0,0 +1,2 @@
|
||||
class bar:
|
||||
"""docstring of target.name_conflict.foo::bar."""
|
||||
@@ -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.',
|
||||
'',
|
||||
]
|
||||
|
||||
@@ -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.',
|
||||
'',
|
||||
|
||||
@@ -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):
|
||||
|
||||
12
tox.ini
12
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.
|
||||
|
||||
@@ -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``
|
||||
|
||||
Reference in New Issue
Block a user