jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/709236 )
Change subject: [IMPR] diff.py type hints ......................................................................
[IMPR] diff.py type hints
Bug: T286403 Change-Id: I51740ee93eefea892d36d9b805add1b2c69a31ce --- M pywikibot/bot_choice.py M pywikibot/diff.py 2 files changed, 74 insertions(+), 53 deletions(-)
Approvals: Xqt: Looks good to me, approved jenkins-bot: Verified
diff --git a/pywikibot/bot_choice.py b/pywikibot/bot_choice.py index 0107862..90810e4 100755 --- a/pywikibot/bot_choice.py +++ b/pywikibot/bot_choice.py @@ -20,6 +20,10 @@ issue_deprecation_warning, )
+# TODO: replace these after T286867 + +OPT_REPLACE_TYPE = Any # Optional['pywikibot.bot.InteractiveReplace'] +
class Option(ABC):
@@ -281,13 +285,14 @@
"""A simple choice consisting of an option, shortcut and handler."""
- def __init__(self, option: str, shortcut: str, replacer) -> None: + def __init__(self, option: str, shortcut: str, + replacer: OPT_REPLACE_TYPE) -> None: """Initializer.""" super().__init__(option, shortcut) self._replacer = replacer
@property - def replacer(self): + def replacer(self) -> OPT_REPLACE_TYPE: """The replacer.""" return self._replacer
@@ -319,9 +324,8 @@
"""A choice returning a mix of the link new and current link."""
- def __init__(self, option: str, shortcut: str, replacer, - replace_section: bool, - replace_label: bool) -> None: + def __init__(self, option: str, shortcut: str, replacer: OPT_REPLACE_TYPE, + replace_section: bool, replace_label: bool) -> None: """Initializer.""" super().__init__(option, shortcut, replacer) self._section = replace_section @@ -361,7 +365,7 @@
"""Add an option to always apply the default."""
- def __init__(self, replacer, + def __init__(self, replacer: OPT_REPLACE_TYPE, option: str = 'always', shortcut: str = 'a') -> None: """Initializer.""" super().__init__(option, shortcut, replacer) @@ -468,7 +472,7 @@
"""An option to select something from a list."""
- def __init__(self, sequence: Sequence[str], prefix='', + def __init__(self, sequence: Sequence[str], prefix: str = '', **kwargs: Any) -> None: """Initializer.""" self._list = sequence diff --git a/pywikibot/diff.py b/pywikibot/diff.py index fea270d..9c91a9b 100644 --- a/pywikibot/diff.py +++ b/pywikibot/diff.py @@ -6,11 +6,15 @@ # import difflib import math -from collections.abc import Sequence -from difflib import _format_range_unified as format_range_unified +from collections import abc +from difflib import ( # type: ignore[attr-defined] + _format_range_unified as format_range_unified +) from itertools import zip_longest +from typing import Optional, Union
import pywikibot +from pywikibot.backports import Dict, Iterable, List, Sequence, Tuple from pywikibot.tools import chars, deprecated_args from pywikibot.tools.formatter import color_format
@@ -27,7 +31,10 @@ NOT_APPR = -1 PENDING = 0
- def __init__(self, a, b, grouped_opcode): + def __init__(self, a: Union[str, Sequence[str]], + b: Union[str, Sequence[str]], + grouped_opcode: Sequence[Tuple[str, int, int, int, int]] + ) -> None: """ Initializer.
@@ -64,22 +71,26 @@
self.reviewed = self.PENDING
- def get_header(self): + self.pre_context = 0 + self.post_context = 0 + + def get_header(self) -> str: """Provide header of unified diff.""" return self.get_header_text(self.a_rng, self.b_rng) + '\n'
@staticmethod - def get_header_text(a_rng, b_rng, affix='@@'): + def get_header_text(a_rng: Tuple[int, int], b_rng: Tuple[int, int], + affix: str = '@@') -> str: """Provide header for any ranges.""" a_rng = format_range_unified(*a_rng) b_rng = format_range_unified(*b_rng) return '{0} -{1} +{2} {0}'.format(affix, a_rng, b_rng)
- def create_diff(self): + def create_diff(self) -> Iterable[str]: """Generator of diff text for this hunk, without formatting.""" # make sure each line ends with '\n' to prevent # behaviour like https://bugs.python.org/issue2142 - def check_line(line): + def check_line(line: str) -> str: return line if line.endswith('\n') else line + '\n'
for tag, i1, i2, j1, j2 in self.group: @@ -98,11 +109,12 @@ for line in difflib.ndiff(self.a[i1:i2], self.b[j1:j2]): yield check_line(line)
- def format_diff(self): + def format_diff(self) -> Iterable[str]: """Color diff lines.""" diff = iter(self.diff)
- fmt, line1, line2 = '', '', next(diff) + fmt = '' # type: Optional[str] + line1, line2 = '', next(diff) for line in diff: fmt, line1, line2 = line1, line2, line # do not show lines starting with '?'. @@ -142,7 +154,7 @@ fmt = fmt if fmt else None yield self.color_line(line2, fmt)
- def color_line(self, line: str, line_ref=None): + def color_line(self, line: str, line_ref: Optional[str] = None) -> str: """Color line characters.
If line_ref is None, the whole line is colored. @@ -186,46 +198,46 @@
return colored_line
- def apply(self): + def apply(self) -> Sequence[str]: """Turn a into b for this hunk.""" return self.b[self.b_rng[0]:self.b_rng[1]]
- def __str__(self): + def __str__(self) -> str: """Return the diff as plain text.""" return ''.join(self.diff_plain_text)
- def __repr__(self): + def __repr__(self) -> str: """Return a reconstructable representation.""" # TODO return '{}(a, b, {})'.format(self.__class__.__name__, self.group)
-class _SuperHunk(Sequence): +class _SuperHunk(abc.Sequence):
- def __init__(self, hunks): + def __init__(self, hunks: Sequence[Hunk]) -> None: self._hunks = hunks self.a_rng = (self._hunks[0].a_rng[0], self._hunks[-1].a_rng[1]) self.b_rng = (self._hunks[0].b_rng[0], self._hunks[-1].b_rng[1]) self.pre_context = self._hunks[0].pre_context self.post_context = self._hunks[0].post_context
- def __getitem__(self, idx): + def __getitem__(self, idx: int) -> Hunk: # type: ignore[override] return self._hunks[idx]
- def __len__(self): + def __len__(self) -> int: return len(self._hunks)
- def split(self): + def split(self) -> List['_SuperHunk']: return [_SuperHunk([hunk]) for hunk in self._hunks]
@property - def reviewed(self): + def reviewed(self) -> int: assert len({hunk.reviewed for hunk in self._hunks}) == 1, \ 'All hunks should have the same review status' return self._hunks[0].reviewed
@reviewed.setter - def reviewed(self, reviewed): + def reviewed(self, reviewed: int) -> None: for hunk in self._hunks: hunk.reviewed = reviewed
@@ -238,23 +250,21 @@ """
@deprecated_args(n='context') - def __init__(self, text_a: str, text_b: str, context=0, by_letter=False, - replace_invisible=False) -> None: + def __init__(self, text_a: str, text_b: str, context: int = 0, + by_letter: bool = False, + replace_invisible: bool = False) -> None: """Initializer.
:param text_a: base text :param text_b: target text :param context: number of lines which are context - :type context: int :param by_letter: if text_a and text_b are single lines, comparison can be done letter by letter. - :type by_letter: bool :param replace_invisible: Replace invisible characters like U+200e with the charnumber in brackets (e.g. <200e>). - :type replace_invisible: bool """ - self.a = text_a.splitlines(True) - self.b = text_b.splitlines(True) + self.a = text_a.splitlines(True) # type: Union[str, List[str]] + self.b = text_b.splitlines(True) # type: Union[str, List[str]] if by_letter and len(self.a) <= 1 and len(self.b) <= 1: self.a = text_a self.b = text_b @@ -281,7 +291,7 @@ self._super_hunks = self._generate_super_hunks() self._replace_invisible = replace_invisible
- def get_blocks(self): + def get_blocks(self) -> List[Tuple[int, Tuple[int, int], Tuple[int, int]]]: """Return list with blocks of indexes.
Format of each block:: @@ -312,13 +322,14 @@
return blocks
- def print_hunks(self): + def print_hunks(self) -> None: """Print the headers and diff texts of all hunks to the output.""" if self.hunks: pywikibot.output('\n'.join(self._generate_diff(super_hunk) for super_hunk in self._super_hunks))
- def _generate_super_hunks(self, hunks=None): + def _generate_super_hunks(self, hunks: Optional[Iterable[Hunk]] = None + ) -> List[_SuperHunk]: if hunks is None: hunks = self.hunks
@@ -327,7 +338,7 @@
if self.context: # Determine if two hunks are connected by self.context - super_hunk = [] + super_hunk = [] # type: List[Hunk] super_hunks = [super_hunk] for hunk in hunks: # self.context * 2, because if self.context is 2 the hunks @@ -345,7 +356,8 @@ super_hunks = [[hunk] for hunk in hunks] return [_SuperHunk(sh) for sh in super_hunks]
- def _get_context_range(self, super_hunk): + def _get_context_range(self, super_hunk: _SuperHunk + ) -> Tuple[Tuple[int, int], Tuple[int, int]]: """Dynamically determine context range for a super hunk.""" a0, a1 = super_hunk.a_rng b0, b1 = super_hunk.b_rng @@ -354,9 +366,9 @@ (b0 - min(super_hunk.pre_context, self.context), b1 + min(super_hunk.post_context, self.context)))
- def _generate_diff(self, hunks): + def _generate_diff(self, hunks: _SuperHunk) -> str: """Generate a diff text for the given hunks.""" - def extend_context(start, end): + def extend_context(start: int, end: int) -> str: """Add context lines.""" return ''.join(' {}\n'.format(line.rstrip()) for line in self.a[start:end]) @@ -378,9 +390,9 @@ output = chars.replace_invisible(output) return output
- def review_hunks(self): + def review_hunks(self) -> None: """Review hunks.""" - def find_pending(start, end): + def find_pending(start: int, end: int) -> Optional[int]: step = -1 if start > end else +1 for pending in range(start, end, step): if super_hunks[pending].reviewed == Hunk.PENDING: @@ -406,11 +418,12 @@
super_hunks = self._generate_super_hunks( h for h in self.hunks if h.reviewed == Hunk.PENDING) - position = 0 + position = 0 # type: Optional[int]
while any(any(hunk.reviewed == Hunk.PENDING for hunk in super_hunk) for super_hunk in super_hunks):
+ assert position is not None super_hunk = super_hunks[position]
next_pending = find_pending(position + 1, len(super_hunks)) @@ -480,14 +493,14 @@ # the last entry is the first changed line which usually ends # with a \n (only the last may not, which is covered by the # if-condition following this block) - hunk_list = ''.join( + hunk_list_str = ''.join( line_template.format( '*' if hunk_entry[1] == position + 1 else ' ', *hunk_entry) for hunk_entry in hunk_list) - if hunk_list.endswith('\n'): - hunk_list = hunk_list[:-1] - pywikibot.output(hunk_list) + if hunk_list_str.endswith('\n'): + hunk_list_str = hunk_list_str[:-1] + pywikibot.output(hunk_list_str) next_hunk = pywikibot.input('Go to which hunk?') try: next_hunk_position = int(next_hunk) - 1 @@ -500,10 +513,12 @@ pywikibot.error( 'Invalid hunk number "{}"'.format(next_hunk)) elif choice == 'j': + assert next_pending is not None position = next_pending elif choice == 'J': position += 1 elif choice == 'k': + assert prev_pending is not None position = prev_pending elif choice == 'K': position -= 1 @@ -519,14 +534,14 @@ '{0} -> {1}'.format(answer, help_msg[answer]) for answer in answers)))
- def apply(self): + def apply(self) -> List[str]: """Apply changes. If there are undecided changes, ask to review.""" if any(h.reviewed == h.PENDING for h in self.hunks): pywikibot.output('There are unreviewed hunks.\n' 'Please review them before proceeding.\n') self.review_hunks()
- l_text = [] + l_text = [] # type: List[str] for hunk_idx, (i1, i2), (j1, j2) in self.blocks: # unchanged text. if hunk_idx < 0: @@ -545,7 +560,8 @@ return l_text
-def cherry_pick(oldtext, newtext, n=0, by_letter=False): +def cherry_pick(oldtext: str, newtext: str, n: int = 0, + by_letter: bool = False) -> str: """Propose a list of changes for approval.
Text with approved changes will be returned. @@ -577,7 +593,7 @@ return text
-def html_comparator(compare_string: str) -> dict: +def html_comparator(compare_string: str) -> Dict[str, List[str]]: """List of added and deleted contexts from 'action=compare' html string.
This function is useful when combineds with site.py's "compare" method. @@ -589,7 +605,8 @@ """ from bs4 import BeautifulSoup
- comparands = {'deleted-context': [], 'added-context': []} + comparands = {'deleted-context': [], + 'added-context': []} # type: Dict[str, List[str]] soup = BeautifulSoup(compare_string, 'html.parser') for change_type, css_class in (('deleted-context', 'diff-deletedline'), ('added-context', 'diff-addedline')):