diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index f90d4aeb7..89dc8fde9 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -42,12 +42,10 @@ jobs:
with:
python-version: ${{ matrix.python }}
- name: Set up Python ${{ matrix.python }} (deadsnakes)
- uses: deadsnakes/action@v1.0.0
+ uses: deadsnakes/action@v2.0.1
if: endsWith(matrix.python, '-dev')
with:
python-version: ${{ matrix.python }}
- env:
- ACTIONS_ALLOW_UNSECURE_COMMANDS: true
- name: Check Python version
run: python --version
- name: Install graphviz
diff --git a/CHANGES b/CHANGES
index 59f2b0aff..5ad0d36a5 100644
--- a/CHANGES
+++ b/CHANGES
@@ -14,6 +14,7 @@ Deprecated
----------
* The ``follow_wrapped`` argument of ``sphinx.util.inspect.signature()``
+* ``sphinx.ext.autodoc.DataDeclarationDocumenter``
* ``sphinx.util.requests.is_ssl_error()``
Features added
@@ -42,6 +43,12 @@ Bugs fixed
is decorated
* #8434: autodoc: :confval:`autodoc_type_aliases` does not effect to variables
and attributes
+* #8443: autodoc: autodata directive can't create document for PEP-526 based
+ type annotated variables
+* #8443: autodoc: autoattribute directive can't create document for PEP-526
+ based uninitalized variables
+* #8419: html search: Do not load ``language_data.js`` in non-search pages
+* #8454: graphviz: The layout option for graph and digraph directives don't work
* #8437: Makefile: ``make clean`` with empty BUILDDIR is dangerous
Testing
diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst
index 392325246..31de41a10 100644
--- a/doc/extdev/deprecated.rst
+++ b/doc/extdev/deprecated.rst
@@ -31,6 +31,11 @@ The following is a list of deprecated interfaces.
- 5.0
- N/A
+ * - ``sphinx.ext.autodoc.DataDeclarationDocumenter``
+ - 3.4
+ - 5.0
+ - ``sphinx.ext.autodoc.DataDocumenter``
+
* - ``sphinx.util.requests.is_ssl_error()``
- 3.4
- 5.0
diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py
index d7986e7dc..6d263a9ed 100644
--- a/sphinx/builders/html/__init__.py
+++ b/sphinx/builders/html/__init__.py
@@ -301,7 +301,6 @@ class StandaloneHTMLBuilder(Builder):
self.add_js_file('jquery.js')
self.add_js_file('underscore.js')
self.add_js_file('doctools.js')
- self.add_js_file('language_data.js')
for filename, attrs in self.app.registry.js_files:
self.add_js_file(filename, **attrs)
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py
index 1bd49fc38..243d13572 100644
--- a/sphinx/ext/autodoc/__init__.py
+++ b/sphinx/ext/autodoc/__init__.py
@@ -961,7 +961,7 @@ class ModuleDocumenter(Documenter):
def __init__(self, *args: Any) -> None:
super().__init__(*args)
merge_members_option(self.options)
- self.__all__ = None
+ self.__all__ = None # type: Optional[Sequence[str]]
@classmethod
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
@@ -985,26 +985,16 @@ class ModuleDocumenter(Documenter):
return ret
def import_object(self, raiseerror: bool = False) -> bool:
- def is_valid_module_all(__all__: Any) -> bool:
- """Check the given *__all__* is valid for a module."""
- if (isinstance(__all__, (list, tuple)) and
- all(isinstance(e, str) for e in __all__)):
- return True
- else:
- return False
-
ret = super().import_object(raiseerror)
- if not self.options.ignore_module_all:
- __all__ = getattr(self.object, '__all__', None)
- if is_valid_module_all(__all__):
- # valid __all__ found. copy it to self.__all__
- self.__all__ = __all__
- elif __all__:
- # invalid __all__ found.
- logger.warning(__('__all__ should be a list of strings, not %r '
- '(in module %s) -- ignoring __all__') %
- (__all__, self.fullname), type='autodoc')
+ try:
+ if not self.options.ignore_module_all:
+ self.__all__ = inspect.getall(self.object)
+ except ValueError as exc:
+ # invalid __all__ found.
+ logger.warning(__('__all__ should be a list of strings, not %r '
+ '(in module %s) -- ignoring __all__') %
+ (exc.args[0], self.fullname), type='autodoc')
return ret
@@ -1697,6 +1687,28 @@ class DataDocumenter(ModuleLevelDocumenter):
) -> bool:
return isinstance(parent, ModuleDocumenter) and isattr
+ def import_object(self, raiseerror: bool = False) -> bool:
+ try:
+ return super().import_object(raiseerror=True)
+ except ImportError as exc:
+ # annotation only instance variable (PEP-526)
+ try:
+ self.parent = importlib.import_module(self.modname)
+ annotations = get_type_hints(self.parent, None,
+ self.config.autodoc_type_aliases)
+ if self.objpath[-1] in annotations:
+ self.object = UNINITIALIZED_ATTR
+ return True
+ except ImportError:
+ pass
+
+ if raiseerror:
+ raise
+ else:
+ logger.warning(exc.args[0], type='autodoc', subtype='import_object')
+ self.env.note_reread()
+ return False
+
def add_directive_header(self, sig: str) -> None:
super().add_directive_header(sig)
sourcename = self.get_sourcename()
@@ -1733,6 +1745,13 @@ class DataDocumenter(ModuleLevelDocumenter):
return self.get_attr(self.parent or self.object, '__module__', None) \
or self.modname
+ def add_content(self, more_content: Any, no_docstring: bool = False) -> None:
+ if self.object is UNINITIALIZED_ATTR:
+ # suppress docstring of the value
+ super().add_content(more_content, no_docstring=True)
+ else:
+ super().add_content(more_content, no_docstring=no_docstring)
+
class DataDeclarationDocumenter(DataDocumenter):
"""
@@ -1746,30 +1765,10 @@ class DataDeclarationDocumenter(DataDocumenter):
# must be higher than AttributeDocumenter
priority = 11
- @classmethod
- def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
- ) -> bool:
- """This documents only INSTANCEATTR members."""
- return (isinstance(parent, ModuleDocumenter) and
- isattr and
- member is INSTANCEATTR)
-
- def import_object(self, raiseerror: bool = False) -> bool:
- """Never import anything."""
- # disguise as a data
- self.objtype = 'data'
- self.object = UNINITIALIZED_ATTR
- try:
- # import module to obtain type annotation
- self.parent = importlib.import_module(self.modname)
- except ImportError:
- pass
-
- return True
-
- def add_content(self, more_content: Any, no_docstring: bool = False) -> None:
- """Never try to get a docstring from the object."""
- super().add_content(more_content, no_docstring=True)
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
+ warnings.warn("%s is deprecated." % self.__class__.__name__,
+ RemovedInSphinx50Warning, stacklevel=2)
+ super().__init__(*args, **kwargs)
class GenericAliasDocumenter(DataDocumenter):
@@ -2036,6 +2035,22 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
def isinstanceattribute(self) -> bool:
"""Check the subject is an instance attribute."""
+ # uninitialized instance variable (PEP-526)
+ with mock(self.config.autodoc_mock_imports):
+ try:
+ ret = import_object(self.modname, self.objpath[:-1], 'class',
+ attrgetter=self.get_attr,
+ warningiserror=self.config.autodoc_warningiserror)
+ self.parent = ret[3]
+ annotations = get_type_hints(self.parent, None,
+ self.config.autodoc_type_aliases)
+ if self.objpath[-1] in annotations:
+ self.object = UNINITIALIZED_ATTR
+ return True
+ except ImportError:
+ pass
+
+ # An instance variable defined inside __init__().
try:
analyzer = ModuleAnalyzer.for_module(self.modname)
attr_docs = analyzer.find_attr_docs()
@@ -2046,7 +2061,9 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
return False
except PycodeError:
- return False
+ pass
+
+ return False
def import_object(self, raiseerror: bool = False) -> bool:
try:
@@ -2282,7 +2299,6 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_autodocumenter(ClassDocumenter)
app.add_autodocumenter(ExceptionDocumenter)
app.add_autodocumenter(DataDocumenter)
- app.add_autodocumenter(DataDeclarationDocumenter)
app.add_autodocumenter(GenericAliasDocumenter)
app.add_autodocumenter(TypeVarDocumenter)
app.add_autodocumenter(FunctionDocumenter)
diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py
index 7e187e36a..f6a84b4e4 100644
--- a/sphinx/ext/autosummary/generate.py
+++ b/sphinx/ext/autosummary/generate.py
@@ -85,8 +85,7 @@ AutosummaryEntry = NamedTuple('AutosummaryEntry', [('name', str),
def setup_documenters(app: Any) -> None:
- from sphinx.ext.autodoc import (AttributeDocumenter, ClassDocumenter,
- DataDeclarationDocumenter, DataDocumenter,
+ from sphinx.ext.autodoc import (AttributeDocumenter, ClassDocumenter, DataDocumenter,
DecoratorDocumenter, ExceptionDocumenter,
FunctionDocumenter, GenericAliasDocumenter,
InstanceAttributeDocumenter, MethodDocumenter,
@@ -96,8 +95,7 @@ def setup_documenters(app: Any) -> None:
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
FunctionDocumenter, MethodDocumenter, AttributeDocumenter,
InstanceAttributeDocumenter, DecoratorDocumenter, PropertyDocumenter,
- SlotsAttributeDocumenter, DataDeclarationDocumenter, GenericAliasDocumenter,
- SingledispatchFunctionDocumenter,
+ SlotsAttributeDocumenter, GenericAliasDocumenter, SingledispatchFunctionDocumenter,
] # type: List[Type[Documenter]]
for documenter in documenters:
app.registry.add_documenter(documenter.objtype, documenter)
diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py
index 62375f95b..dc1cbb686 100644
--- a/sphinx/ext/graphviz.py
+++ b/sphinx/ext/graphviz.py
@@ -182,7 +182,8 @@ class GraphvizSimple(SphinxDirective):
'alt': directives.unchanged,
'align': align_spec,
'caption': directives.unchanged,
- 'graphviz_dot': directives.unchanged,
+ 'layout': directives.unchanged,
+ 'graphviz_dot': directives.unchanged, # an old alias of `layout` option
'name': directives.unchanged,
'class': directives.class_option,
}
@@ -194,6 +195,8 @@ class GraphvizSimple(SphinxDirective):
node['options'] = {'docname': self.env.docname}
if 'graphviz_dot' in self.options:
node['options']['graphviz_dot'] = self.options['graphviz_dot']
+ if 'layout' in self.options:
+ node['options']['graphviz_dot'] = self.options['layout']
if 'alt' in self.options:
node['alt'] = self.options['alt']
if 'align' in self.options:
diff --git a/sphinx/themes/basic/search.html b/sphinx/themes/basic/search.html
index 2673369f2..cf574f8d5 100644
--- a/sphinx/themes/basic/search.html
+++ b/sphinx/themes/basic/search.html
@@ -12,6 +12,7 @@
{%- block scripts %}
{{ super() }}
+
{%- endblock %}
{% block extrahead %}
diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py
index 4f4f12e1d..c7375aa57 100644
--- a/sphinx/util/inspect.py
+++ b/sphinx/util/inspect.py
@@ -20,7 +20,7 @@ import warnings
from functools import partial, partialmethod
from inspect import Parameter, isclass, ismethod, ismethoddescriptor, ismodule # NOQA
from io import StringIO
-from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, cast
+from typing import Any, Callable, Dict, List, Mapping, Optional, Sequence, Tuple, cast
from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning
from sphinx.pycode.ast import ast # for py35-37
@@ -137,6 +137,22 @@ def unwrap_all(obj: Any, *, stop: Callable = None) -> Any:
return obj
+def getall(obj: Any) -> Optional[Sequence[str]]:
+ """Get __all__ attribute of the module as dict.
+
+ Return None if given *obj* does not have __all__.
+ Raises ValueError if given *obj* have invalid __all__.
+ """
+ __all__ = safe_getattr(obj, '__all__', None)
+ if __all__ is None:
+ return None
+ else:
+ if (isinstance(__all__, (list, tuple)) and all(isinstance(e, str) for e in __all__)):
+ return __all__
+ else:
+ raise ValueError(__all__)
+
+
def getslots(obj: Any) -> Optional[Dict]:
"""Get __slots__ attribute of the class as dict.
diff --git a/tests/test_ext_autodoc_autoattribute.py b/tests/test_ext_autodoc_autoattribute.py
index ca74f9aeb..31dccbd03 100644
--- a/tests/test_ext_autodoc_autoattribute.py
+++ b/tests/test_ext_autodoc_autoattribute.py
@@ -9,6 +9,8 @@
:license: BSD, see LICENSE for details.
"""
+import sys
+
import pytest
from test_ext_autodoc import do_autodoc
@@ -39,3 +41,31 @@ def test_autoattribute_novalue(app):
' should be documented -- süß',
'',
]
+
+
+@pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.')
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_autoattribute_typed_variable(app):
+ actual = do_autodoc(app, 'attribute', 'target.typed_vars.Class.attr2')
+ assert list(actual) == [
+ '',
+ '.. py:attribute:: Class.attr2',
+ ' :module: target.typed_vars',
+ ' :type: int',
+ '',
+ ]
+
+
+@pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.')
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_autoattribute_instance_variable(app):
+ actual = do_autodoc(app, 'attribute', 'target.typed_vars.Class.attr4')
+ assert list(actual) == [
+ '',
+ '.. py:attribute:: Class.attr4',
+ ' :module: target.typed_vars',
+ ' :type: int',
+ '',
+ ' attr4',
+ '',
+ ]
diff --git a/tests/test_ext_autodoc_autodata.py b/tests/test_ext_autodoc_autodata.py
index 7e4471db9..72665cdba 100644
--- a/tests/test_ext_autodoc_autodata.py
+++ b/tests/test_ext_autodoc_autodata.py
@@ -9,6 +9,8 @@
:license: BSD, see LICENSE for details.
"""
+import sys
+
import pytest
from test_ext_autodoc import do_autodoc
@@ -39,3 +41,34 @@ def test_autodata_novalue(app):
' documentation for the integer',
'',
]
+
+
+@pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.')
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_autodata_typed_variable(app):
+ actual = do_autodoc(app, 'data', 'target.typed_vars.attr2')
+ assert list(actual) == [
+ '',
+ '.. py:data:: attr2',
+ ' :module: target.typed_vars',
+ ' :type: str',
+ '',
+ ' attr2',
+ '',
+ ]
+
+
+@pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.')
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_autodata_type_comment(app):
+ actual = do_autodoc(app, 'data', 'target.typed_vars.attr3')
+ assert list(actual) == [
+ '',
+ '.. py:data:: attr3',
+ ' :module: target.typed_vars',
+ ' :type: str',
+ " :value: ''",
+ '',
+ ' attr3',
+ '',
+ ]