added napoleon_google_attr_annotations option to use PEP 526 on google style

This commit is contained in:
Quentin Soubeyran 2020-10-05 14:15:01 +02:00
parent c941b9cb14
commit f268665292
5 changed files with 117 additions and 3 deletions

View File

@ -294,3 +294,21 @@ class ExampleClass:
def _private_without_docstring(self): def _private_without_docstring(self):
pass pass
class ExamplePEP526Class:
"""The summary line for a class docstring should fit on one line.
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``
is True, types can be specified in the class body using ``PEP 526``
annotations.
Attributes:
attr1: Description of `attr1`.
attr2: Description of `attr2`.
"""
attr1: str
attr2: int

View File

@ -203,7 +203,8 @@ Type Annotations
This is an alternative to expressing types directly in docstrings. This is an alternative to expressing types directly in docstrings.
One benefit of expressing types according to `PEP 484`_ is that One benefit of expressing types according to `PEP 484`_ is that
type checkers and IDEs can take advantage of them for static code type checkers and IDEs can take advantage of them for static code
analysis. analysis. `PEP 484`_ was then extended by `PEP 526`_ which introduced
a similar way to annotate variables (and attributes).
Google style with Python 3 type annotations:: Google style with Python 3 type annotations::
@ -222,6 +223,19 @@ Google style with Python 3 type annotations::
""" """
return True return True
class Class:
"""Summary line.
Extended description of class
Attributes:
attr1: Description of attr1
attr2: Description of attr2
"""
attr1: int
attr2: str
Google style with types in docstrings:: Google style with types in docstrings::
def func(arg1, arg2): def func(arg1, arg2):
@ -239,6 +253,16 @@ Google style with types in docstrings::
""" """
return True return True
class Class:
"""Summary line.
Extended description of class
Attributes:
attr1 (int): Description of attr1
attr2 (str): Description of attr2
"""
.. Note:: .. Note::
`Python 2/3 compatible annotations`_ aren't currently `Python 2/3 compatible annotations`_ aren't currently
supported by Sphinx and won't show up in the docs. supported by Sphinx and won't show up in the docs.
@ -246,6 +270,9 @@ Google style with types in docstrings::
.. _PEP 484: .. _PEP 484:
https://www.python.org/dev/peps/pep-0484/ https://www.python.org/dev/peps/pep-0484/
.. _PEP 526:
https://www.python.org/dev/peps/pep-0526/
.. _Python 2/3 compatible annotations: .. _Python 2/3 compatible annotations:
https://www.python.org/dev/peps/pep-0484/#suggested-syntax-for-python-2-7-and-straddling-code https://www.python.org/dev/peps/pep-0484/#suggested-syntax-for-python-2-7-and-straddling-code
@ -275,6 +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
.. _Google style: .. _Google style:
https://google.github.io/styleguide/pyguide.html https://google.github.io/styleguide/pyguide.html
@ -511,3 +539,9 @@ sure that "sphinx.ext.napoleon" is enabled in `conf.py`::
:type arg2: :term:`dict-like <mapping>` :type arg2: :term:`dict-like <mapping>`
.. versionadded:: 3.2 .. versionadded:: 3.2
.. confval:: napoleon_google_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.

View File

@ -44,6 +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
.. _Google style: .. _Google style:
https://google.github.io/styleguide/pyguide.html https://google.github.io/styleguide/pyguide.html
@ -257,6 +258,9 @@ 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)
Use the type annotations of class attributes that are documented in the docstring
but do not have a type in the docstring.
""" """
_config_values = { _config_values = {
@ -274,7 +278,8 @@ class Config:
'napoleon_use_keyword': (True, 'env'), 'napoleon_use_keyword': (True, 'env'),
'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'),
} }
def __init__(self, **settings: Any) -> None: def __init__(self, **settings: Any) -> None:

View File

@ -14,13 +14,14 @@ 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 from typing import Any, Callable, Dict, List, Tuple, Union, get_type_hints
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
if False: if False:
# For type annotation # For type annotation
@ -600,6 +601,26 @@ 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 == 'class' and self._obj:
if self._config.napoleon_google_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 = {}
if _name in self._annotations:
_type = stringify_annotation(self._annotations[_name])
if self._config.napoleon_use_ivar: if self._config.napoleon_use_ivar:
_name = self._qualify_name(_name, self._obj) _name = self._qualify_name(_name, self._obj)
field = ':ivar %s: ' % _name field = ':ivar %s: ' % _name

View File

@ -45,6 +45,18 @@ class NamedtupleSubclass(namedtuple('NamedtupleSubclass', ('attr1', 'attr2'))):
return super().__new__(cls, attr1, attr2) return super().__new__(cls, attr1, attr2)
class PEP526Class:
"""Sample class with PEP 526 annotations
Attributes:
attr1: Attr1 description.
attr2: Attr2 description.
"""
attr1: int
attr2: str
class BaseDocstringTest(TestCase): class BaseDocstringTest(TestCase):
pass pass
@ -1092,6 +1104,30 @@ Do as you please
""" """
self.assertEqual(expected, actual) self.assertEqual(expected, actual)
def test_pep_526_annotations(self):
config = Config(
napoleon_google_attr_annotations=True
)
actual = str(GoogleDocstring(cleandoc(PEP526Class.__doc__), config, app=None, what="class",
obj=PEP526Class))
print(actual)
expected = """\
Sample class with PEP 526 annotations
.. attribute:: attr1
Attr1 description.
:type: int
.. attribute:: attr2
Attr2 description.
:type: str
"""
self.assertEqual(expected, actual)
class NumpyDocstringTest(BaseDocstringTest): class NumpyDocstringTest(BaseDocstringTest):
docstrings = [( docstrings = [(