Migrate to py3 style type annotation: sphinx.ext.napoleon.docstring

This commit is contained in:
Takeshi KOMIYA 2019-07-03 01:42:55 +09:00
parent c270efccd7
commit 0a98664866

View File

@ -13,16 +13,13 @@
import inspect
import re
from functools import partial
from typing import Any, Callable, Dict, List, Tuple, Type, 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 _
if False:
# For type annotation
from typing import Any, Callable, Dict, List, Tuple, Type, Union # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.config import Config as SphinxConfig # NOQA
_directive_regex = re.compile(r'\.\. \S+::')
_google_section_regex = re.compile(r'^(\s|\w)+:\s*$')
@ -103,9 +100,9 @@ class GoogleDocstring:
_name_rgx = re.compile(r"^\s*((?::(?P<role>\S+):)?`(?P<name>[a-zA-Z0-9_.-]+)`|"
r" (?P<name2>[a-zA-Z0-9_.-]+))\s*", re.X)
def __init__(self, docstring, config=None, app=None, what='', name='',
obj=None, options=None):
# type: (Union[str, List[str]], SphinxConfig, Sphinx, str, str, Any, Any) -> None
def __init__(self, docstring: Union[str, List[str]], config: SphinxConfig = None,
app: Sphinx = None, what: str = '', name: str = '',
obj: Any = None, options: Any = None) -> None:
self._config = config
self._app = app
@ -175,8 +172,7 @@ class GoogleDocstring:
self._parse()
def __str__(self):
# type: () -> str
def __str__(self) -> str:
"""Return the parsed docstring in reStructuredText format.
Returns
@ -187,8 +183,7 @@ class GoogleDocstring:
"""
return '\n'.join(self.lines())
def lines(self):
# type: () -> List[str]
def lines(self) -> List[str]:
"""Return the parsed lines of the docstring in reStructuredText format.
Returns
@ -199,8 +194,7 @@ class GoogleDocstring:
"""
return self._parsed_lines
def _consume_indented_block(self, indent=1):
# type: (int) -> List[str]
def _consume_indented_block(self, indent: int = 1) -> List[str]:
lines = []
line = self._line_iter.peek()
while(not self._is_section_break() and
@ -209,8 +203,7 @@ class GoogleDocstring:
line = self._line_iter.peek()
return lines
def _consume_contiguous(self):
# type: () -> List[str]
def _consume_contiguous(self) -> List[str]:
lines = []
while (self._line_iter.has_next() and
self._line_iter.peek() and
@ -218,8 +211,7 @@ class GoogleDocstring:
lines.append(next(self._line_iter))
return lines
def _consume_empty(self):
# type: () -> List[str]
def _consume_empty(self) -> List[str]:
lines = []
line = self._line_iter.peek()
while self._line_iter.has_next() and not line:
@ -227,8 +219,8 @@ class GoogleDocstring:
line = self._line_iter.peek()
return lines
def _consume_field(self, parse_type=True, prefer_type=False):
# type: (bool, bool) -> Tuple[str, str, List[str]]
def _consume_field(self, parse_type: bool = True, prefer_type: bool = False
) -> Tuple[str, str, List[str]]:
line = next(self._line_iter)
before, colon, after = self._partition_field_on_colon(line)
@ -249,8 +241,8 @@ class GoogleDocstring:
_descs = self.__class__(_descs, self._config).lines()
return _name, _type, _descs
def _consume_fields(self, parse_type=True, prefer_type=False):
# type: (bool, bool) -> List[Tuple[str, str, List[str]]]
def _consume_fields(self, parse_type: bool = True, prefer_type: bool = False
) -> List[Tuple[str, str, List[str]]]:
self._consume_empty()
fields = []
while not self._is_section_break():
@ -259,8 +251,7 @@ class GoogleDocstring:
fields.append((_name, _type, _desc,))
return fields
def _consume_inline_attribute(self):
# type: () -> Tuple[str, List[str]]
def _consume_inline_attribute(self) -> Tuple[str, List[str]]:
line = next(self._line_iter)
_type, colon, _desc = self._partition_field_on_colon(line)
if not colon or not _desc:
@ -270,8 +261,7 @@ class GoogleDocstring:
_descs = self.__class__(_descs, self._config).lines()
return _type, _descs
def _consume_returns_section(self):
# type: () -> List[Tuple[str, str, List[str]]]
def _consume_returns_section(self) -> List[Tuple[str, str, List[str]]]:
lines = self._dedent(self._consume_to_next_section())
if lines:
before, colon, after = self._partition_field_on_colon(lines[0])
@ -290,44 +280,38 @@ class GoogleDocstring:
else:
return []
def _consume_usage_section(self):
# type: () -> List[str]
def _consume_usage_section(self) -> List[str]:
lines = self._dedent(self._consume_to_next_section())
return lines
def _consume_section_header(self):
# type: () -> str
def _consume_section_header(self) -> str:
section = next(self._line_iter)
stripped_section = section.strip(':')
if stripped_section.lower() in self._sections:
section = stripped_section
return section
def _consume_to_end(self):
# type: () -> List[str]
def _consume_to_end(self) -> List[str]:
lines = []
while self._line_iter.has_next():
lines.append(next(self._line_iter))
return lines
def _consume_to_next_section(self):
# type: () -> List[str]
def _consume_to_next_section(self) -> List[str]:
self._consume_empty()
lines = []
while not self._is_section_break():
lines.append(next(self._line_iter))
return lines + self._consume_empty()
def _dedent(self, lines, full=False):
# type: (List[str], bool) -> List[str]
def _dedent(self, lines: List[str], full: bool = False) -> List[str]:
if full:
return [line.lstrip() for line in lines]
else:
min_indent = self._get_min_indent(lines)
return [line[min_indent:] for line in lines]
def _escape_args_and_kwargs(self, name):
# type: (str) -> str
def _escape_args_and_kwargs(self, name: str) -> str:
if name.endswith('_'):
name = name[:-1] + r'\_'
@ -338,8 +322,7 @@ class GoogleDocstring:
else:
return name
def _fix_field_desc(self, desc):
# type: (List[str]) -> List[str]
def _fix_field_desc(self, desc: List[str]) -> List[str]:
if self._is_list(desc):
desc = [''] + desc
elif desc[0].endswith('::'):
@ -352,8 +335,7 @@ class GoogleDocstring:
desc = ['', desc[0]] + self._indent(desc_block, 4)
return desc
def _format_admonition(self, admonition, lines):
# type: (str, List[str]) -> List[str]
def _format_admonition(self, admonition: str, lines: List[str]) -> List[str]:
lines = self._strip_empty(lines)
if len(lines) == 1:
return ['.. %s:: %s' % (admonition, lines[0].strip()), '']
@ -363,8 +345,7 @@ class GoogleDocstring:
else:
return ['.. %s::' % admonition, '']
def _format_block(self, prefix, lines, padding=None):
# type: (str, List[str], str) -> List[str]
def _format_block(self, prefix: str, lines: List[str], padding: str = None) -> List[str]:
if lines:
if padding is None:
padding = ' ' * len(prefix)
@ -380,9 +361,9 @@ class GoogleDocstring:
else:
return [prefix]
def _format_docutils_params(self, fields, field_role='param',
type_role='type'):
# type: (List[Tuple[str, str, List[str]]], str, str) -> List[str]
def _format_docutils_params(self, fields: List[Tuple[str, str, List[str]]],
field_role: str = 'param', type_role: str = 'type'
) -> List[str]:
lines = []
for _name, _type, _desc in fields:
_desc = self._strip_empty(_desc)
@ -397,8 +378,7 @@ class GoogleDocstring:
lines.append(':%s %s: %s' % (type_role, _name, _type))
return lines + ['']
def _format_field(self, _name, _type, _desc):
# type: (str, str, List[str]) -> List[str]
def _format_field(self, _name: str, _type: str, _desc: List[str]) -> List[str]:
_desc = self._strip_empty(_desc)
has_desc = any(_desc)
separator = has_desc and ' -- ' or ''
@ -427,8 +407,8 @@ class GoogleDocstring:
else:
return [field]
def _format_fields(self, field_type, fields):
# type: (str, List[Tuple[str, str, List[str]]]) -> List[str]
def _format_fields(self, field_type: str, fields: List[Tuple[str, str, List[str]]]
) -> List[str]:
field_type = ':%s:' % field_type.strip()
padding = ' ' * len(field_type)
multi = len(fields) > 1
@ -446,8 +426,7 @@ class GoogleDocstring:
lines.append('')
return lines
def _get_current_indent(self, peek_ahead=0):
# type: (int) -> int
def _get_current_indent(self, peek_ahead: int = 0) -> int:
line = self._line_iter.peek(peek_ahead + 1)[peek_ahead]
while line != self._line_iter.sentinel:
if line:
@ -456,22 +435,19 @@ class GoogleDocstring:
line = self._line_iter.peek(peek_ahead + 1)[peek_ahead]
return 0
def _get_indent(self, line):
# type: (str) -> int
def _get_indent(self, line: str) -> int:
for i, s in enumerate(line):
if not s.isspace():
return i
return len(line)
def _get_initial_indent(self, lines):
# type: (List[str]) -> int
def _get_initial_indent(self, lines: List[str]) -> int:
for line in lines:
if line:
return self._get_indent(line)
return 0
def _get_min_indent(self, lines):
# type: (List[str]) -> int
def _get_min_indent(self, lines: List[str]) -> int:
min_indent = None
for line in lines:
if line:
@ -482,12 +458,10 @@ class GoogleDocstring:
min_indent = indent
return min_indent or 0
def _indent(self, lines, n=4):
# type: (List[str], int) -> List[str]
def _indent(self, lines: List[str], n: int = 4) -> List[str]:
return [(' ' * n) + line for line in lines]
def _is_indented(self, line, indent=1):
# type: (str, int) -> bool
def _is_indented(self, line: str, indent: int = 1) -> bool:
for i, s in enumerate(line):
if i >= indent:
return True
@ -495,8 +469,7 @@ class GoogleDocstring:
return False
return False
def _is_list(self, lines):
# type: (List[str]) -> bool
def _is_list(self, lines: List[str]) -> bool:
if not lines:
return False
if _bullet_list_regex.match(lines[0]):
@ -513,8 +486,7 @@ class GoogleDocstring:
break
return next_indent > indent
def _is_section_header(self):
# type: () -> bool
def _is_section_header(self) -> bool:
section = self._line_iter.peek().lower()
match = _google_section_regex.match(section)
if match and section.strip(':') in self._sections:
@ -528,8 +500,7 @@ class GoogleDocstring:
return True
return False
def _is_section_break(self):
# type: () -> bool
def _is_section_break(self) -> bool:
line = self._line_iter.peek()
return (not self._line_iter.has_next() or
self._is_section_header() or
@ -537,9 +508,7 @@ class GoogleDocstring:
line and
not self._is_indented(line, self._section_indent)))
def _load_custom_sections(self):
# type: () -> None
def _load_custom_sections(self) -> None:
if self._config.napoleon_custom_sections is not None:
for entry in self._config.napoleon_custom_sections:
if isinstance(entry, str):
@ -554,8 +523,7 @@ class GoogleDocstring:
self._sections.get(entry[1].lower(),
self._parse_custom_generic_section)
def _parse(self):
# type: () -> None
def _parse(self) -> None:
self._parsed_lines = self._consume_empty()
if self._name and self._what in ('attribute', 'data', 'property'):
@ -594,16 +562,14 @@ class GoogleDocstring:
lines = self._consume_to_next_section()
return self._format_admonition(admonition, lines)
def _parse_attribute_docstring(self):
# type: () -> List[str]
def _parse_attribute_docstring(self) -> List[str]:
_type, _desc = self._consume_inline_attribute()
lines = self._format_field('', '', _desc)
if _type:
lines.extend(['', ':type: %s' % _type])
return lines
def _parse_attributes_section(self, section):
# type: (str) -> List[str]
def _parse_attributes_section(self, section: str) -> List[str]:
lines = []
for _name, _type, _desc in self._consume_fields():
if self._config.napoleon_use_ivar:
@ -624,8 +590,7 @@ class GoogleDocstring:
lines.append('')
return lines
def _parse_examples_section(self, section):
# type: (str) -> List[str]
def _parse_examples_section(self, section: str) -> List[str]:
labels = {
'example': _('Example'),
'examples': _('Examples'),
@ -638,16 +603,14 @@ class GoogleDocstring:
# for now, no admonition for simple custom sections
return self._parse_generic_section(section, False)
def _parse_usage_section(self, section):
# type: (str) -> List[str]
def _parse_usage_section(self, section: str) -> List[str]:
header = ['.. rubric:: Usage:', '']
block = ['.. code-block:: python', '']
lines = self._consume_usage_section()
lines = self._indent(lines, 3)
return header + block + lines + ['']
def _parse_generic_section(self, section, use_admonition):
# type: (str, bool) -> List[str]
def _parse_generic_section(self, section: str, use_admonition: bool) -> List[str]:
lines = self._strip_empty(self._consume_to_next_section())
lines = self._dedent(lines)
if use_admonition:
@ -660,8 +623,7 @@ class GoogleDocstring:
else:
return [header, '']
def _parse_keyword_arguments_section(self, section):
# type: (str) -> List[str]
def _parse_keyword_arguments_section(self, section: str) -> List[str]:
fields = self._consume_fields()
if self._config.napoleon_use_keyword:
return self._format_docutils_params(
@ -671,8 +633,7 @@ class GoogleDocstring:
else:
return self._format_fields(_('Keyword Arguments'), fields)
def _parse_methods_section(self, section):
# type: (str) -> List[str]
def _parse_methods_section(self, section: str) -> List[str]:
lines = [] # type: List[str]
for _name, _type, _desc in self._consume_fields(parse_type=False):
lines.append('.. method:: %s' % _name)
@ -681,25 +642,21 @@ class GoogleDocstring:
lines.append('')
return lines
def _parse_notes_section(self, section):
# type: (str) -> List[str]
def _parse_notes_section(self, section: str) -> List[str]:
use_admonition = self._config.napoleon_use_admonition_for_notes
return self._parse_generic_section(_('Notes'), use_admonition)
def _parse_other_parameters_section(self, section):
# type: (str) -> List[str]
def _parse_other_parameters_section(self, section: str) -> List[str]:
return self._format_fields(_('Other Parameters'), self._consume_fields())
def _parse_parameters_section(self, section):
# type: (str) -> List[str]
def _parse_parameters_section(self, section: str) -> List[str]:
fields = self._consume_fields()
if self._config.napoleon_use_param:
return self._format_docutils_params(fields)
else:
return self._format_fields(_('Parameters'), fields)
def _parse_raises_section(self, section):
# type: (str) -> List[str]
def _parse_raises_section(self, section: str) -> List[str]:
fields = self._consume_fields(parse_type=False, prefer_type=True)
lines = [] # type: List[str]
for _name, _type, _desc in fields:
@ -714,13 +671,11 @@ class GoogleDocstring:
lines.append('')
return lines
def _parse_references_section(self, section):
# type: (str) -> List[str]
def _parse_references_section(self, section: str) -> List[str]:
use_admonition = self._config.napoleon_use_admonition_for_references
return self._parse_generic_section(_('References'), use_admonition)
def _parse_returns_section(self, section):
# type: (str) -> List[str]
def _parse_returns_section(self, section: str) -> List[str]:
fields = self._consume_returns_section()
multi = len(fields) > 1
if multi:
@ -748,21 +703,17 @@ class GoogleDocstring:
lines.append('')
return lines
def _parse_see_also_section(self, section):
# type (str) -> List[str]
def _parse_see_also_section(self, section: str) -> List[str]:
return self._parse_admonition('seealso', section)
def _parse_warns_section(self, section):
# type: (str) -> List[str]
def _parse_warns_section(self, section: str) -> List[str]:
return self._format_fields(_('Warns'), self._consume_fields())
def _parse_yields_section(self, section):
# type: (str) -> List[str]
def _parse_yields_section(self, section: str) -> List[str]:
fields = self._consume_returns_section()
return self._format_fields(_('Yields'), fields)
def _partition_field_on_colon(self, line):
# type: (str) -> Tuple[str, str, str]
def _partition_field_on_colon(self, line: str) -> Tuple[str, str, str]:
before_colon = []
after_colon = []
colon = ''
@ -784,8 +735,7 @@ class GoogleDocstring:
colon,
"".join(after_colon).strip())
def _qualify_name(self, attr_name, klass):
# type: (str, Type) -> str
def _qualify_name(self, attr_name: str, klass: Type) -> str:
if klass and '.' not in attr_name:
if attr_name.startswith('~'):
attr_name = attr_name[1:]
@ -796,8 +746,7 @@ class GoogleDocstring:
return '~%s.%s' % (q, attr_name)
return attr_name
def _strip_empty(self, lines):
# type: (List[str]) -> List[str]
def _strip_empty(self, lines: List[str]) -> List[str]:
if lines:
start = -1
for i, line in enumerate(lines):
@ -910,14 +859,14 @@ class NumpyDocstring(GoogleDocstring):
The lines of the docstring in a list.
"""
def __init__(self, docstring, config=None, app=None, what='', name='',
obj=None, options=None):
# type: (Union[str, List[str]], SphinxConfig, Sphinx, str, str, Any, Any) -> None
def __init__(self, docstring: Union[str, List[str]], config: SphinxConfig = None,
app: Sphinx = None, what: str = '', name: str = '',
obj: Any = None, options: Any = None) -> None:
self._directive_sections = ['.. index::']
super().__init__(docstring, config, app, what, name, obj, options)
def _consume_field(self, parse_type=True, prefer_type=False):
# type: (bool, bool) -> Tuple[str, str, List[str]]
def _consume_field(self, parse_type: bool = True, prefer_type: bool = False
) -> Tuple[str, str, List[str]]:
line = next(self._line_iter)
if parse_type:
_name, _, _type = self._partition_field_on_colon(line)
@ -933,20 +882,17 @@ class NumpyDocstring(GoogleDocstring):
_desc = self.__class__(_desc, self._config).lines()
return _name, _type, _desc
def _consume_returns_section(self):
# type: () -> List[Tuple[str, str, List[str]]]
def _consume_returns_section(self) -> List[Tuple[str, str, List[str]]]:
return self._consume_fields(prefer_type=True)
def _consume_section_header(self):
# type: () -> str
def _consume_section_header(self) -> str:
section = next(self._line_iter)
if not _directive_regex.match(section):
# Consume the header underline
next(self._line_iter)
return section
def _is_section_break(self):
# type: () -> bool
def _is_section_break(self) -> bool:
line1, line2 = self._line_iter.peek(2)
return (not self._line_iter.has_next() or
self._is_section_header() or
@ -955,8 +901,7 @@ class NumpyDocstring(GoogleDocstring):
line1 and
not self._is_indented(line1, self._section_indent)))
def _is_section_header(self):
# type: () -> bool
def _is_section_header(self) -> bool:
section, underline = self._line_iter.peek(2)
section = section.lower()
if section in self._sections and isinstance(underline, str):
@ -968,16 +913,14 @@ class NumpyDocstring(GoogleDocstring):
return True
return False
def _parse_see_also_section(self, section):
# type: (str) -> List[str]
def _parse_see_also_section(self, section: str) -> List[str]:
lines = self._consume_to_next_section()
try:
return self._parse_numpydoc_see_also_section(lines)
except ValueError:
return self._format_admonition('seealso', lines)
def _parse_numpydoc_see_also_section(self, content):
# type: (List[str]) -> List[str]
def _parse_numpydoc_see_also_section(self, content: List[str]) -> List[str]:
"""
Derived from the NumpyDoc implementation of _parse_see_also.
@ -991,8 +934,7 @@ class NumpyDocstring(GoogleDocstring):
"""
items = []
def parse_item_name(text):
# type: (str) -> Tuple[str, str]
def parse_item_name(text: str) -> Tuple[str, str]:
"""Match ':role:`name`' or 'name'"""
m = self._name_rgx.match(text)
if m:
@ -1003,8 +945,7 @@ class NumpyDocstring(GoogleDocstring):
return g[2], g[1]
raise ValueError("%s is not a item name" % text)
def push_item(name, rest):
# type: (str, List[str]) -> None
def push_item(name: str, rest: List[str]) -> None:
if not name:
return
name, role = parse_item_name(name)