refactored code and tests, prepared for numpy feature

This commit is contained in:
Quentin Soubeyran 2020-11-18 11:18:52 +01:00
parent a60e1c10b7
commit ec30f77712
9 changed files with 101 additions and 44 deletions

View File

@ -300,7 +300,7 @@ class ExamplePEP526Class:
If the class has public attributes, they may be documented here If the class has public attributes, they may be documented here
in an ``Attributes`` section and follow the same formatting as a 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`` is True, types can be specified in the class body using ``PEP 526``
annotations. annotations.

View File

@ -302,7 +302,7 @@ sure that "sphinx.ext.napoleon" is enabled in `conf.py`::
napoleon_use_param = True napoleon_use_param = True
napoleon_use_rtype = True napoleon_use_rtype = True
napoleon_type_aliases = None napoleon_type_aliases = None
napoleon_google_attr_annotations = False napoleon_attr_annotations = True
.. _Google style: .. _Google style:
https://google.github.io/styleguide/pyguide.html https://google.github.io/styleguide/pyguide.html
@ -540,8 +540,10 @@ sure that "sphinx.ext.napoleon" is enabled in `conf.py`::
.. versionadded:: 3.2 .. versionadded:: 3.2
.. confval:: napoleon_google_attr_annotations .. confval:: napoleon_attr_annotations
True to allow using `PEP 526`_ attributes annotations in classes. True to allow using `PEP 526`_ attributes annotations in classes.
If an attribute is documented in the docstring without a type and If an attribute is documented in the docstring without a type and
has an annotation in the class body, that type is used. has an annotation in the class body, that type is used.
.. versionadded:: 3.4

View File

@ -44,7 +44,7 @@ class Config:
napoleon_preprocess_types = False napoleon_preprocess_types = False
napoleon_type_aliases = None napoleon_type_aliases = None
napoleon_custom_sections = None napoleon_custom_sections = None
napoleon_google_attr_annotations = False napoleon_attr_annotations = False
.. _Google style: .. _Google style:
https://google.github.io/styleguide/pyguide.html 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 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. 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 Use the type annotations of class attributes that are documented in the docstring
but do not have a type in the docstring. but do not have a type in the docstring.
@ -279,7 +279,7 @@ class Config:
'napoleon_preprocess_types': (False, 'env'), 'napoleon_preprocess_types': (False, 'env'),
'napoleon_type_aliases': (None, 'env'), 'napoleon_type_aliases': (None, 'env'),
'napoleon_custom_sections': (None, 'env'), 'napoleon_custom_sections': (None, 'env'),
'napoleon_google_attr_annotations': (False, 'env'), 'napoleon_attr_annotations': (True, 'env'),
} }
def __init__(self, **settings: Any) -> None: def __init__(self, **settings: Any) -> None:

View File

@ -14,14 +14,15 @@ import collections
import inspect import inspect
import re import re
from functools import partial 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.application import Sphinx
from sphinx.config import Config as SphinxConfig from sphinx.config import Config as SphinxConfig
from sphinx.ext.napoleon.iterators import modify_iter from sphinx.ext.napoleon.iterators import modify_iter
from sphinx.locale import _, __ from sphinx.locale import _, __
from sphinx.util import logging 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: if False:
# For type annotation # For type annotation
@ -601,25 +602,15 @@ class GoogleDocstring:
def _parse_attributes_section(self, section: str) -> List[str]: def _parse_attributes_section(self, section: str) -> List[str]:
lines = [] lines = []
for _name, _type, _desc in self._consume_fields(): 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 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 # cache the class annotations
if not hasattr(self, "_annotations"): if not hasattr(self, "_annotations"):
try: localns = getattr(self._config, "autodoc_type_aliases", {}) or {}
self._annotations = get_type_hints(self._obj) localns.update(getattr(
except NameError: self._config, "napoleon_type_aliases", {}
# Failed to evaluate ForwardRef (maybe TYPE_CHECKING) ) or {})
self._annotations = safe_getattr(self._obj, '__annotations__', {}) self._annotations = get_type_hints(self._obj, None, localns)
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 = {}
if _name in self._annotations: if _name in self._annotations:
_type = stringify_annotation(self._annotations[_name]) _type = stringify_annotation(self._annotations[_name])
if self._config.napoleon_use_ivar: if self._config.napoleon_use_ivar:

View File

@ -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 from sphinx.util.inspect import safe_getattr # lazy loading
try: try:
return typing.get_type_hints(obj, None, localns) return typing.get_type_hints(obj, globalns, localns)
except NameError: except NameError:
# Failed to evaluate ForwardRef (maybe TYPE_CHECKING) # Failed to evaluate ForwardRef (maybe TYPE_CHECKING)
return safe_getattr(obj, '__annotations__', {}) return safe_getattr(obj, '__annotations__', {})

View File

@ -1,10 +0,0 @@
class PEP526Class:
"""Sample class with PEP 526 annotations
Attributes:
attr1: Attr1 description.
attr2: Attr2 description.
"""
attr1: int
attr2: str

View File

@ -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

View File

@ -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

View File

@ -25,7 +25,11 @@ from sphinx.ext.napoleon.docstring import (GoogleDocstring, NumpyDocstring,
_token_type, _tokenize_type_spec) _token_type, _tokenize_type_spec)
if sys.version_info >= (3, 6): 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'))): class NamedtupleSubclass(namedtuple('NamedtupleSubclass', ('attr1', 'attr2'))):
@ -1095,16 +1099,17 @@ Do as you please
:kwtype gotham_is_yours: None :kwtype gotham_is_yours: None
""" """
self.assertEqual(expected, actual) self.assertEqual(expected, actual)
def test_pep_526_annotations(self): def test_pep_526_annotations(self):
if sys.version_info >= (3, 6): if sys.version_info >= (3, 6):
# Test class attributes annotations
config = Config( config = Config(
napoleon_google_attr_annotations=True napoleon_attr_annotations=True
) )
actual = str(GoogleDocstring(cleandoc(PEP526Class.__doc__), config, app=None, what="class", actual = str(GoogleDocstring(cleandoc(PEP526GoogleClass.__doc__), config, app=None, what="class",
obj=PEP526Class)) obj=PEP526GoogleClass))
expected = """\ expected = """\
Sample class with PEP 526 annotations Sample class with PEP 526 annotations and google docstring
.. attribute:: attr1 .. attribute:: attr1
@ -1120,6 +1125,9 @@ Sample class with PEP 526 annotations
""" """
self.assertEqual(expected, actual) 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): class NumpyDocstringTest(BaseDocstringTest):
docstrings = [( docstrings = [(
@ -2430,3 +2438,29 @@ class TestNumpyDocstring:
actual = numpy_docstring._escape_args_and_kwargs(name) actual = numpy_docstring._escape_args_and_kwargs(name)
assert actual == expected 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