diff --git a/doc/usage/extensions/example_google.py b/doc/usage/extensions/example_google.py index 98da3870a..5fde6e226 100644 --- a/doc/usage/extensions/example_google.py +++ b/doc/usage/extensions/example_google.py @@ -300,7 +300,7 @@ class ExamplePEP526Class: If the class has public attributes, they may be documented here in an ``Attributes`` section and follow the same formatting as a - function's ``Args`` section. If ``napoleon_google_attr_annotations`` + function's ``Args`` section. If ``napoleon_attr_annotations`` is True, types can be specified in the class body using ``PEP 526`` annotations. diff --git a/doc/usage/extensions/napoleon.rst b/doc/usage/extensions/napoleon.rst index 550f8b591..cf5b3080f 100644 --- a/doc/usage/extensions/napoleon.rst +++ b/doc/usage/extensions/napoleon.rst @@ -302,7 +302,7 @@ sure that "sphinx.ext.napoleon" is enabled in `conf.py`:: napoleon_use_param = True napoleon_use_rtype = True napoleon_type_aliases = None - napoleon_google_attr_annotations = False + napoleon_attr_annotations = True .. _Google style: https://google.github.io/styleguide/pyguide.html @@ -540,8 +540,10 @@ sure that "sphinx.ext.napoleon" is enabled in `conf.py`:: .. versionadded:: 3.2 -.. confval:: napoleon_google_attr_annotations +.. confval:: napoleon_attr_annotations - True to allow using `PEP 526`_ attributes annotations in classes. - If an attribute is documented in the docstring without a type and - has an annotation in the class body, that type is used. \ No newline at end of file + True to allow using `PEP 526`_ attributes annotations in classes. + If an attribute is documented in the docstring without a type and + has an annotation in the class body, that type is used. + + .. versionadded:: 3.4 \ No newline at end of file diff --git a/sphinx/ext/napoleon/__init__.py b/sphinx/ext/napoleon/__init__.py index febeb0197..0c2a0b60a 100644 --- a/sphinx/ext/napoleon/__init__.py +++ b/sphinx/ext/napoleon/__init__.py @@ -44,7 +44,7 @@ class Config: napoleon_preprocess_types = False napoleon_type_aliases = None napoleon_custom_sections = None - napoleon_google_attr_annotations = False + napoleon_attr_annotations = False .. _Google style: https://google.github.io/styleguide/pyguide.html @@ -258,7 +258,7 @@ class Config: section. If the entry is a tuple/list/indexed container, the first entry is the name of the section, the second is the section key to emulate. - napoleon_google_attr_annotations : :obj:`bool` (Defaults to False) + napoleon_attr_annotations : :obj:`bool` (Defaults to True) Use the type annotations of class attributes that are documented in the docstring but do not have a type in the docstring. @@ -279,7 +279,7 @@ class Config: 'napoleon_preprocess_types': (False, 'env'), 'napoleon_type_aliases': (None, 'env'), 'napoleon_custom_sections': (None, 'env'), - 'napoleon_google_attr_annotations': (False, 'env'), + 'napoleon_attr_annotations': (True, 'env'), } def __init__(self, **settings: Any) -> None: diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index ec99211cc..2e16c40d1 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -14,14 +14,15 @@ import collections import inspect import re from functools import partial -from typing import Any, Callable, Dict, List, Tuple, Union, get_type_hints +from typing import Any, Callable, Dict, List, Tuple, Union from sphinx.application import Sphinx from sphinx.config import Config as SphinxConfig from sphinx.ext.napoleon.iterators import modify_iter from sphinx.locale import _, __ from sphinx.util import logging -from sphinx.util.inspect import safe_getattr, stringify_annotation +from sphinx.util.inspect import stringify_annotation +from sphinx.util.typing import get_type_hints if False: # For type annotation @@ -601,25 +602,15 @@ class GoogleDocstring: def _parse_attributes_section(self, section: str) -> List[str]: lines = [] for _name, _type, _desc in self._consume_fields(): - # code adapted from autodoc.AttributeDocumenter:add_directive_header if not _type and self._what in ('class', 'exception') and self._obj: - if self._config.napoleon_google_attr_annotations: + if self._config.napoleon_attr_annotations: # cache the class annotations if not hasattr(self, "_annotations"): - try: - self._annotations = get_type_hints(self._obj) - except NameError: - # Failed to evaluate ForwardRef (maybe TYPE_CHECKING) - self._annotations = safe_getattr(self._obj, '__annotations__', {}) - except TypeError: - self._annotations = {} - except KeyError: - # a broken class found - # (refs: https://github.com/sphinx-doc/sphinx/issues/8084) - self._annotations = {} - except AttributeError: - # AttributeError is raised on 3.5.2 (fixed by 3.5.3) - self._annotations = {} + localns = getattr(self._config, "autodoc_type_aliases", {}) or {} + localns.update(getattr( + self._config, "napoleon_type_aliases", {} + ) or {}) + self._annotations = get_type_hints(self._obj, None, localns) if _name in self._annotations: _type = stringify_annotation(self._annotations[_name]) if self._config.napoleon_use_ivar: diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py index 9d9355414..222e2edf2 100644 --- a/sphinx/util/typing.py +++ b/sphinx/util/typing.py @@ -66,7 +66,7 @@ def get_type_hints(obj: Any, globalns: Dict = None, localns: Dict = None) -> Dic from sphinx.util.inspect import safe_getattr # lazy loading try: - return typing.get_type_hints(obj, None, localns) + return typing.get_type_hints(obj, globalns, localns) except NameError: # Failed to evaluate ForwardRef (maybe TYPE_CHECKING) return safe_getattr(obj, '__annotations__', {}) diff --git a/tests/ext_napoleon_docstring_data.py b/tests/ext_napoleon_docstring_data.py deleted file mode 100644 index b56d53c04..000000000 --- a/tests/ext_napoleon_docstring_data.py +++ /dev/null @@ -1,10 +0,0 @@ -class PEP526Class: - """Sample class with PEP 526 annotations - - Attributes: - attr1: Attr1 description. - attr2: Attr2 description. - """ - - attr1: int - attr2: str \ No newline at end of file diff --git a/tests/ext_napoleon_pep526_data_google.py b/tests/ext_napoleon_pep526_data_google.py new file mode 100644 index 000000000..95e84f470 --- /dev/null +++ b/tests/ext_napoleon_pep526_data_google.py @@ -0,0 +1,18 @@ +""" +Test module for napoleon PEP 526 compatiblity with google style +""" + +module_level_var: int = 99 +"""This is an example module level variable""" + + +class PEP526GoogleClass: + """Sample class with PEP 526 annotations and google docstring + + Attributes: + attr1: Attr1 description. + attr2: Attr2 description. + """ + + attr1: int + attr2: str diff --git a/tests/ext_napoleon_pep526_data_numpy.py b/tests/ext_napoleon_pep526_data_numpy.py new file mode 100644 index 000000000..eaf81950e --- /dev/null +++ b/tests/ext_napoleon_pep526_data_numpy.py @@ -0,0 +1,22 @@ +""" +Test module for napoleon PEP 526 compatiblity with numpy style +""" + +module_level_var: int = 99 +"""This is an example module level variable""" + + +class PEP526NumpyClass: + """ + Sample class with PEP 526 annotations and numpy docstring + + Attributes + ---------- + attr 1: + Attr1 description + + attr 2: + Attr2 description + """ + attr1: int + attr2: str diff --git a/tests/test_ext_napoleon_docstring.py b/tests/test_ext_napoleon_docstring.py index d95e43350..19f0d3e5a 100644 --- a/tests/test_ext_napoleon_docstring.py +++ b/tests/test_ext_napoleon_docstring.py @@ -25,7 +25,11 @@ from sphinx.ext.napoleon.docstring import (GoogleDocstring, NumpyDocstring, _token_type, _tokenize_type_spec) if sys.version_info >= (3, 6): - from ext_napoleon_docstring_data import PEP526Class + # TODO: enable imports when used + # import ext_napoleon_pep526_data_google + # import ext_napoleon_pep526_data_numpy + from ext_napoleon_pep526_data_google import PEP526GoogleClass + from ext_napoleon_pep526_data_numpy import PEP526NumpyClass class NamedtupleSubclass(namedtuple('NamedtupleSubclass', ('attr1', 'attr2'))): @@ -1095,16 +1099,17 @@ Do as you please :kwtype gotham_is_yours: None """ self.assertEqual(expected, actual) - + def test_pep_526_annotations(self): if sys.version_info >= (3, 6): + # Test class attributes annotations config = Config( - napoleon_google_attr_annotations=True + napoleon_attr_annotations=True ) - actual = str(GoogleDocstring(cleandoc(PEP526Class.__doc__), config, app=None, what="class", - obj=PEP526Class)) + actual = str(GoogleDocstring(cleandoc(PEP526GoogleClass.__doc__), config, app=None, what="class", + obj=PEP526GoogleClass)) expected = """\ -Sample class with PEP 526 annotations +Sample class with PEP 526 annotations and google docstring .. attribute:: attr1 @@ -1120,6 +1125,9 @@ Sample class with PEP 526 annotations """ self.assertEqual(expected, actual) + # test module-level variables documentation + # TODO: use the ext_napoleon_pep526_data_google.module_level_var for that + class NumpyDocstringTest(BaseDocstringTest): docstrings = [( @@ -2430,3 +2438,29 @@ class TestNumpyDocstring: actual = numpy_docstring._escape_args_and_kwargs(name) assert actual == expected + + def test_pep_526_annotations(self): + if sys.version_info >= (3, 6): + # test class attributes annotations + # TODO: change this test after implementation in Numpy doc style + config = Config( + napoleon_attr_annotations=True + ) + actual = str(NumpyDocstring(cleandoc(PEP526NumpyClass.__doc__), config, app=None, what="class", + obj=PEP526NumpyClass)) + expected = """\ +Sample class with PEP 526 annotations and numpy docstring + +.. attribute:: attr 1 + + Attr1 description + +.. attribute:: attr 2 + + Attr2 description +""" + print(actual) + assert expected == actual + + # test module-level variables documentation + # TODO: use the ext_napoleon_pep526_data_google.module_level_var for that