Replace napoleon.iterators by simpler stack implementation (#9856)

This commit is contained in:
Antony Lee 2022-06-26 13:40:42 +02:00 committed by GitHub
parent d730c55f13
commit 03c1e1b15c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 64 additions and 31 deletions

View File

@ -12,6 +12,7 @@ Deprecated
* #10467: Deprecated ``sphinx.util.stemmer`` in favour of ``snowballstemmer``. * #10467: Deprecated ``sphinx.util.stemmer`` in favour of ``snowballstemmer``.
Patch by Adam Turner. Patch by Adam Turner.
* #9856: Deprecated ``sphinx.ext.napoleon.iterators``.
Features added Features added
-------------- --------------

View File

@ -22,6 +22,11 @@ The following is a list of deprecated interfaces.
- (will be) Removed - (will be) Removed
- Alternatives - Alternatives
* - ``sphinx.ext.napoleon.iterators``
- 5.1
- 7.0
- ``pockets.iterators``
* - ``sphinx.util.stemmer`` * - ``sphinx.util.stemmer``
- 5.1 - 5.1
- 7.0 - 7.0

View File

@ -10,7 +10,6 @@ from typing import Any, Callable, Dict, List, Tuple, Type, 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.deprecation import RemovedInSphinx60Warning from sphinx.deprecation import RemovedInSphinx60Warning
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 stringify_annotation from sphinx.util.inspect import stringify_annotation
@ -46,6 +45,19 @@ _default_regex = re.compile(
_SINGLETONS = ("None", "True", "False", "Ellipsis") _SINGLETONS = ("None", "True", "False", "Ellipsis")
class Deque(collections.deque):
"""A subclass of deque with an additional `.Deque.get` method."""
sentinel = object()
def get(self, n: int) -> Any:
"""
Return the nth element of the stack, or ``self.sentinel`` if n is
greater than the stack size.
"""
return self[n] if n < len(self) else self.sentinel
def _convert_type_spec(_type: str, translations: Dict[str, str] = {}) -> str: def _convert_type_spec(_type: str, translations: Dict[str, str] = {}) -> str:
"""Convert type specification to reference in reST.""" """Convert type specification to reference in reST."""
if _type in translations: if _type in translations:
@ -153,7 +165,7 @@ class GoogleDocstring:
lines = docstring.splitlines() lines = docstring.splitlines()
else: else:
lines = docstring lines = docstring
self._line_iter = modify_iter(lines, modifier=lambda s: s.rstrip()) self._lines = Deque(map(str.rstrip, lines))
self._parsed_lines: List[str] = [] self._parsed_lines: List[str] = []
self._is_in_section = False self._is_in_section = False
self._section_indent = 0 self._section_indent = 0
@ -225,32 +237,32 @@ class GoogleDocstring:
def _consume_indented_block(self, indent: int = 1) -> List[str]: def _consume_indented_block(self, indent: int = 1) -> List[str]:
lines = [] lines = []
line = self._line_iter.peek() line = self._lines.get(0)
while(not self._is_section_break() and while(not self._is_section_break() and
(not line or self._is_indented(line, indent))): (not line or self._is_indented(line, indent))):
lines.append(next(self._line_iter)) lines.append(self._lines.popleft())
line = self._line_iter.peek() line = self._lines.get(0)
return lines return lines
def _consume_contiguous(self) -> List[str]: def _consume_contiguous(self) -> List[str]:
lines = [] lines = []
while (self._line_iter.has_next() and while (self._lines and
self._line_iter.peek() and self._lines.get(0) and
not self._is_section_header()): not self._is_section_header()):
lines.append(next(self._line_iter)) lines.append(self._lines.popleft())
return lines return lines
def _consume_empty(self) -> List[str]: def _consume_empty(self) -> List[str]:
lines = [] lines = []
line = self._line_iter.peek() line = self._lines.get(0)
while self._line_iter.has_next() and not line: while self._lines and not line:
lines.append(next(self._line_iter)) lines.append(self._lines.popleft())
line = self._line_iter.peek() line = self._lines.get(0)
return lines return lines
def _consume_field(self, parse_type: bool = True, prefer_type: bool = False def _consume_field(self, parse_type: bool = True, prefer_type: bool = False
) -> Tuple[str, str, List[str]]: ) -> Tuple[str, str, List[str]]:
line = next(self._line_iter) line = self._lines.popleft()
before, colon, after = self._partition_field_on_colon(line) before, colon, after = self._partition_field_on_colon(line)
_name, _type, _desc = before, '', after _name, _type, _desc = before, '', after
@ -288,7 +300,7 @@ class GoogleDocstring:
return fields return fields
def _consume_inline_attribute(self) -> Tuple[str, List[str]]: def _consume_inline_attribute(self) -> Tuple[str, List[str]]:
line = next(self._line_iter) line = self._lines.popleft()
_type, colon, _desc = self._partition_field_on_colon(line) _type, colon, _desc = self._partition_field_on_colon(line)
if not colon or not _desc: if not colon or not _desc:
_type, _desc = _desc, _type _type, _desc = _desc, _type
@ -326,7 +338,7 @@ class GoogleDocstring:
return lines return lines
def _consume_section_header(self) -> str: def _consume_section_header(self) -> str:
section = next(self._line_iter) section = self._lines.popleft()
stripped_section = section.strip(':') stripped_section = section.strip(':')
if stripped_section.lower() in self._sections: if stripped_section.lower() in self._sections:
section = stripped_section section = stripped_section
@ -334,15 +346,15 @@ class GoogleDocstring:
def _consume_to_end(self) -> List[str]: def _consume_to_end(self) -> List[str]:
lines = [] lines = []
while self._line_iter.has_next(): while self._lines:
lines.append(next(self._line_iter)) lines.append(self._lines.popleft())
return lines return lines
def _consume_to_next_section(self) -> List[str]: def _consume_to_next_section(self) -> List[str]:
self._consume_empty() self._consume_empty()
lines = [] lines = []
while not self._is_section_break(): while not self._is_section_break():
lines.append(next(self._line_iter)) lines.append(self._lines.popleft())
return lines + self._consume_empty() return lines + self._consume_empty()
def _dedent(self, lines: List[str], full: bool = False) -> List[str]: def _dedent(self, lines: List[str], full: bool = False) -> List[str]:
@ -468,12 +480,12 @@ class GoogleDocstring:
return lines return lines
def _get_current_indent(self, peek_ahead: int = 0) -> int: def _get_current_indent(self, peek_ahead: int = 0) -> int:
line = self._line_iter.peek(peek_ahead + 1)[peek_ahead] line = self._lines.get(peek_ahead)
while line != self._line_iter.sentinel: while line is not self._lines.sentinel:
if line: if line:
return self._get_indent(line) return self._get_indent(line)
peek_ahead += 1 peek_ahead += 1
line = self._line_iter.peek(peek_ahead + 1)[peek_ahead] line = self._lines.get(peek_ahead)
return 0 return 0
def _get_indent(self, line: str) -> int: def _get_indent(self, line: str) -> int:
@ -528,7 +540,7 @@ class GoogleDocstring:
return next_indent > indent return next_indent > indent
def _is_section_header(self) -> bool: def _is_section_header(self) -> bool:
section = self._line_iter.peek().lower() section = self._lines.get(0).lower()
match = _google_section_regex.match(section) match = _google_section_regex.match(section)
if match and section.strip(':') in self._sections: if match and section.strip(':') in self._sections:
header_indent = self._get_indent(section) header_indent = self._get_indent(section)
@ -542,8 +554,8 @@ class GoogleDocstring:
return False return False
def _is_section_break(self) -> bool: def _is_section_break(self) -> bool:
line = self._line_iter.peek() line = self._lines.get(0)
return (not self._line_iter.has_next() or return (not self._lines or
self._is_section_header() or self._is_section_header() or
(self._is_in_section and (self._is_in_section and
line and line and
@ -585,7 +597,7 @@ class GoogleDocstring:
self._parsed_lines.extend(res) self._parsed_lines.extend(res)
return return
while self._line_iter.has_next(): while self._lines:
if self._is_section_header(): if self._is_section_header():
try: try:
section = self._consume_section_header() section = self._consume_section_header()
@ -1158,7 +1170,7 @@ class NumpyDocstring(GoogleDocstring):
def _consume_field(self, parse_type: bool = True, prefer_type: bool = False def _consume_field(self, parse_type: bool = True, prefer_type: bool = False
) -> Tuple[str, str, List[str]]: ) -> Tuple[str, str, List[str]]:
line = next(self._line_iter) line = self._lines.popleft()
if parse_type: if parse_type:
_name, _, _type = self._partition_field_on_colon(line) _name, _, _type = self._partition_field_on_colon(line)
else: else:
@ -1189,15 +1201,15 @@ class NumpyDocstring(GoogleDocstring):
return self._consume_fields(prefer_type=True) return self._consume_fields(prefer_type=True)
def _consume_section_header(self) -> str: def _consume_section_header(self) -> str:
section = next(self._line_iter) section = self._lines.popleft()
if not _directive_regex.match(section): if not _directive_regex.match(section):
# Consume the header underline # Consume the header underline
next(self._line_iter) self._lines.popleft()
return section return section
def _is_section_break(self) -> bool: def _is_section_break(self) -> bool:
line1, line2 = self._line_iter.peek(2) line1, line2 = self._lines.get(0), self._lines.get(1)
return (not self._line_iter.has_next() or return (not self._lines or
self._is_section_header() or self._is_section_header() or
['', ''] == [line1, line2] or ['', ''] == [line1, line2] or
(self._is_in_section and (self._is_in_section and
@ -1205,7 +1217,7 @@ class NumpyDocstring(GoogleDocstring):
not self._is_indented(line1, self._section_indent))) not self._is_indented(line1, self._section_indent)))
def _is_section_header(self) -> bool: def _is_section_header(self) -> bool:
section, underline = self._line_iter.peek(2) section, underline = self._lines.get(0), self._lines.get(1)
section = section.lower() section = section.lower()
if section in self._sections and isinstance(underline, str): if section in self._sections and isinstance(underline, str):
return bool(_numpy_section_regex.match(underline)) return bool(_numpy_section_regex.match(underline))

View File

@ -1,8 +1,14 @@
"""A collection of helpful iterators.""" """A collection of helpful iterators."""
import collections import collections
import warnings
from typing import Any, Iterable, Optional from typing import Any, Iterable, Optional
from sphinx.deprecation import RemovedInSphinx70Warning
warnings.warn('sphinx.ext.napoleon.iterators is deprecated.',
RemovedInSphinx70Warning)
class peek_iter: class peek_iter:
"""An iterator object that supports peeking ahead. """An iterator object that supports peeking ahead.

View File

@ -1,10 +1,19 @@
"""Tests for :mod:`sphinx.ext.napoleon.iterators` module.""" """Tests for :mod:`sphinx.ext.napoleon.iterators` module."""
import sys
from unittest import TestCase from unittest import TestCase
from sphinx.deprecation import RemovedInSphinx70Warning
from sphinx.ext.napoleon.iterators import modify_iter, peek_iter from sphinx.ext.napoleon.iterators import modify_iter, peek_iter
class ModuleIsDeprecatedTest(TestCase):
def test_module_is_deprecated(self):
sys.modules.pop("sphinx.ext.napoleon.iterators")
with self.assertWarns(RemovedInSphinx70Warning):
import sphinx.ext.napoleon.iterators # noqa
class BaseIteratorsTest(TestCase): class BaseIteratorsTest(TestCase):
def assertEqualTwice(self, expected, func, *args): def assertEqualTwice(self, expected, func, *args):
self.assertEqual(expected, func(*args)) self.assertEqual(expected, func(*args))