diff --git a/CHANGES b/CHANGES index 35104202180..6a92e8e4388 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,7 @@ Deprecated * #10467: Deprecated ``sphinx.util.stemmer`` in favour of ``snowballstemmer``. Patch by Adam Turner. +* #9856: Deprecated ``sphinx.ext.napoleon.iterators``. Features added -------------- diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index d88eb27b0e5..f496dceae08 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -22,6 +22,11 @@ The following is a list of deprecated interfaces. - (will be) Removed - Alternatives + * - ``sphinx.ext.napoleon.iterators`` + - 5.1 + - 7.0 + - ``pockets.iterators`` + * - ``sphinx.util.stemmer`` - 5.1 - 7.0 diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py index d866594a335..21523ffb459 100644 --- a/sphinx/ext/napoleon/docstring.py +++ b/sphinx/ext/napoleon/docstring.py @@ -10,7 +10,6 @@ from sphinx.application import Sphinx from sphinx.config import Config as SphinxConfig from sphinx.deprecation import RemovedInSphinx60Warning -from sphinx.ext.napoleon.iterators import modify_iter from sphinx.locale import _, __ from sphinx.util import logging from sphinx.util.inspect import stringify_annotation @@ -46,6 +45,19 @@ _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: """Convert type specification to reference in reST.""" if _type in translations: @@ -153,7 +165,7 @@ def __init__(self, docstring: Union[str, List[str]], config: SphinxConfig = None lines = docstring.splitlines() else: 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._is_in_section = False self._section_indent = 0 @@ -225,32 +237,32 @@ def lines(self) -> List[str]: def _consume_indented_block(self, indent: int = 1) -> List[str]: lines = [] - line = self._line_iter.peek() + line = self._lines.get(0) while(not self._is_section_break() and (not line or self._is_indented(line, indent))): - lines.append(next(self._line_iter)) - line = self._line_iter.peek() + lines.append(self._lines.popleft()) + line = self._lines.get(0) return lines def _consume_contiguous(self) -> List[str]: lines = [] - while (self._line_iter.has_next() and - self._line_iter.peek() and + while (self._lines and + self._lines.get(0) and not self._is_section_header()): - lines.append(next(self._line_iter)) + lines.append(self._lines.popleft()) return lines def _consume_empty(self) -> List[str]: lines = [] - line = self._line_iter.peek() - while self._line_iter.has_next() and not line: - lines.append(next(self._line_iter)) - line = self._line_iter.peek() + line = self._lines.get(0) + while self._lines and not line: + lines.append(self._lines.popleft()) + line = self._lines.get(0) return lines def _consume_field(self, parse_type: bool = True, prefer_type: bool = False ) -> Tuple[str, str, List[str]]: - line = next(self._line_iter) + line = self._lines.popleft() before, colon, after = self._partition_field_on_colon(line) _name, _type, _desc = before, '', after @@ -288,7 +300,7 @@ def _consume_fields(self, parse_type: bool = True, prefer_type: bool = False, return fields 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) if not colon or not _desc: _type, _desc = _desc, _type @@ -326,7 +338,7 @@ def _consume_usage_section(self) -> List[str]: return lines def _consume_section_header(self) -> str: - section = next(self._line_iter) + section = self._lines.popleft() stripped_section = section.strip(':') if stripped_section.lower() in self._sections: section = stripped_section @@ -334,15 +346,15 @@ def _consume_section_header(self) -> str: def _consume_to_end(self) -> List[str]: lines = [] - while self._line_iter.has_next(): - lines.append(next(self._line_iter)) + while self._lines: + lines.append(self._lines.popleft()) return lines def _consume_to_next_section(self) -> List[str]: self._consume_empty() lines = [] while not self._is_section_break(): - lines.append(next(self._line_iter)) + lines.append(self._lines.popleft()) return lines + self._consume_empty() def _dedent(self, lines: List[str], full: bool = False) -> List[str]: @@ -468,12 +480,12 @@ def _format_fields(self, field_type: str, fields: List[Tuple[str, str, List[str] return lines 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: + line = self._lines.get(peek_ahead) + while line is not self._lines.sentinel: if line: return self._get_indent(line) peek_ahead += 1 - line = self._line_iter.peek(peek_ahead + 1)[peek_ahead] + line = self._lines.get(peek_ahead) return 0 def _get_indent(self, line: str) -> int: @@ -528,7 +540,7 @@ def _is_list(self, lines: List[str]) -> bool: return next_indent > indent def _is_section_header(self) -> bool: - section = self._line_iter.peek().lower() + section = self._lines.get(0).lower() match = _google_section_regex.match(section) if match and section.strip(':') in self._sections: header_indent = self._get_indent(section) @@ -542,8 +554,8 @@ def _is_section_header(self) -> bool: return False def _is_section_break(self) -> bool: - line = self._line_iter.peek() - return (not self._line_iter.has_next() or + line = self._lines.get(0) + return (not self._lines or self._is_section_header() or (self._is_in_section and line and @@ -585,7 +597,7 @@ def _parse(self) -> None: self._parsed_lines.extend(res) return - while self._line_iter.has_next(): + while self._lines: if self._is_section_header(): try: section = self._consume_section_header() @@ -1158,7 +1170,7 @@ def _escape_args_and_kwargs(self, name: str) -> str: def _consume_field(self, parse_type: bool = True, prefer_type: bool = False ) -> Tuple[str, str, List[str]]: - line = next(self._line_iter) + line = self._lines.popleft() if parse_type: _name, _, _type = self._partition_field_on_colon(line) else: @@ -1189,15 +1201,15 @@ def _consume_returns_section(self, preprocess_types: bool = False return self._consume_fields(prefer_type=True) def _consume_section_header(self) -> str: - section = next(self._line_iter) + section = self._lines.popleft() if not _directive_regex.match(section): # Consume the header underline - next(self._line_iter) + self._lines.popleft() return section def _is_section_break(self) -> bool: - line1, line2 = self._line_iter.peek(2) - return (not self._line_iter.has_next() or + line1, line2 = self._lines.get(0), self._lines.get(1) + return (not self._lines or self._is_section_header() or ['', ''] == [line1, line2] or (self._is_in_section and @@ -1205,7 +1217,7 @@ def _is_section_break(self) -> bool: not self._is_indented(line1, self._section_indent))) 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() if section in self._sections and isinstance(underline, str): return bool(_numpy_section_regex.match(underline)) diff --git a/sphinx/ext/napoleon/iterators.py b/sphinx/ext/napoleon/iterators.py index 9459ad4a6f7..8c9de73e6b4 100644 --- a/sphinx/ext/napoleon/iterators.py +++ b/sphinx/ext/napoleon/iterators.py @@ -1,8 +1,14 @@ """A collection of helpful iterators.""" import collections +import warnings from typing import Any, Iterable, Optional +from sphinx.deprecation import RemovedInSphinx70Warning + +warnings.warn('sphinx.ext.napoleon.iterators is deprecated.', + RemovedInSphinx70Warning) + class peek_iter: """An iterator object that supports peeking ahead. diff --git a/tests/test_ext_napoleon_iterators.py b/tests/test_ext_napoleon_iterators.py index f5fe541b678..d587bd1ef18 100644 --- a/tests/test_ext_napoleon_iterators.py +++ b/tests/test_ext_napoleon_iterators.py @@ -1,10 +1,19 @@ """Tests for :mod:`sphinx.ext.napoleon.iterators` module.""" +import sys from unittest import TestCase +from sphinx.deprecation import RemovedInSphinx70Warning 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): def assertEqualTwice(self, expected, func, *args): self.assertEqual(expected, func(*args))