jenkins-bot submitted this change.

View Change

Approvals: JJMC89: Looks good to me, approved jenkins-bot: Verified
[IMPR] bot_choice.py type hints

Bug: T286403
Change-Id: Ia04380cf787aac4b92d17b81b3baa87ba9764243
---
M pywikibot/bot_choice.py
1 file changed, 86 insertions(+), 68 deletions(-)

diff --git a/pywikibot/bot_choice.py b/pywikibot/bot_choice.py
index c1f6a7b..89daa17 100755
--- a/pywikibot/bot_choice.py
+++ b/pywikibot/bot_choice.py
@@ -8,10 +8,12 @@

from abc import ABC, abstractmethod
from textwrap import fill
-from typing import Optional
+from typing import Any, Optional

import pywikibot

+from pywikibot.backports import Iterable, Sequence
+
from pywikibot.tools import (
deprecated,
deprecated_args,
@@ -38,18 +40,20 @@
True for the same value.
"""

- def __init__(self, stop=True) -> None:
+ def __init__(self, stop: bool = True) -> None:
"""Initializer."""
self._stop = stop

@staticmethod
- def formatted(text: str, options, default=None) -> str:
+ def formatted(text: str, options: Iterable['Option'],
+ default: Optional[str] = None) -> str:
"""
Create a text with the options formatted into it.

:param text: Text into which options are to be formatted
:param options: Option instances to be formatted
- :type options: Iterable
+ :param default: filler for any option's 'default' placeholder
+
:return: Text with the options formatted into it
"""
formatted_options = []
@@ -66,26 +70,24 @@
"""Return whether this option stops asking."""
return self._stop

- def handled(self, value):
+ def handled(self, value: str) -> Optional['Option']:
"""
Return the Option object that applies to the given value.

If this Option object doesn't know which applies it returns None.
"""
- if self.test(value):
- return self
- return None
+ return self if self.test(value) else None

- def format(self, default=None):
+ def format(self, default: Optional[str] = None) -> str:
"""Return a formatted string for that option."""
raise NotImplementedError()

- def test(self, value):
+ def test(self, value: str) -> bool:
"""Return True whether this option applies."""
raise NotImplementedError()

@abstractmethod
- def result(self, value):
+ def result(self, value: str) -> Any:
"""Return the actual value which is associated by the given one.

*New in version 6.2:* *result()* is an abstract method and must
@@ -105,11 +107,11 @@
before_question = False

@property
- def stop(self):
+ def stop(self) -> bool:
"""Never stop asking."""
return False

- def result(self, value):
+ def result(self, value: str) -> Any:
"""Just return None."""
return None

@@ -123,7 +125,7 @@
"""
return ''

- def output(self):
+ def output(self) -> None:
"""Output string when selected and possibly before the question.

:Note: This method should never be overridden.
@@ -135,7 +137,7 @@

"""An option with a description and shortcut and returning the shortcut."""

- def __init__(self, option: str, shortcut: str, **kwargs):
+ def __init__(self, option: str, shortcut: str, **kwargs) -> None:
"""
Initializer.

@@ -146,7 +148,7 @@
self.option = option
self.shortcut = shortcut.lower()

- def format(self, default=None) -> str:
+ def format(self, default: Optional[str] = None) -> str:
"""Return a formatted string for that option."""
index = self.option.lower().find(self.shortcut)
shortcut = self.shortcut
@@ -158,11 +160,11 @@
self.option[index + len(self.shortcut):])
return '{} [{}]'.format(self.option, shortcut)

- def result(self, value):
+ def result(self, value: str) -> Any:
"""Return the lowercased shortcut."""
return self.shortcut

- def test(self, value) -> bool:
+ def test(self, value: str) -> bool:
"""Return True whether this option applies."""
return (self.shortcut.lower() == value.lower()
or self.option.lower() == value.lower())
@@ -172,7 +174,8 @@

"""An option which calls out property of the given output class."""

- def __init__(self, option, shortcut, output, **kwargs):
+ def __init__(self, option: str, shortcut: str, output: OutputOption,
+ **kwargs) -> None:
"""Create a new option for the given sequence."""
super().__init__(option, shortcut, **kwargs)
self._outputter = output
@@ -197,18 +200,19 @@
option applies while handle returns the sub option.
"""

- def __init__(self, option, shortcut, description, options):
+ def __init__(self, option: str, shortcut: str, description: str,
+ options: Iterable[Option]) -> None:
"""Initializer."""
super().__init__(option, shortcut, stop=False)
self.description = description
self.options = options

- def format(self, default=None):
+ def format(self, default: Optional[str] = None) -> str:
"""Return a formatted string for that option."""
self._output = Option.formatted(self.description, self.options)
return super().format(default=default)

- def handled(self, value):
+ def handled(self, value: str) -> Optional[Option]:
"""Return itself if it applies or the applying sub option."""
for option in self.options:
handled = option.handled(value)
@@ -218,7 +222,7 @@
return super().handled(value)

@property
- def out(self):
+ def out(self) -> str:
"""Output of suboptions."""
return self._output

@@ -227,9 +231,8 @@

"""An option to show more and more context."""

- def __init__(
- self, option, shortcut, text, context, delta=100, start=0, end=0
- ):
+ def __init__(self, option: str, shortcut: str, text: str, context: int,
+ delta: int = 100, start: int = 0, end: int = 0) -> None:
"""Initializer."""
super().__init__(option, shortcut, stop=False)
self.text = text
@@ -238,13 +241,13 @@
self.start = start
self.end = end

- def result(self, value):
+ def result(self, value: str) -> Any:
"""Add the delta to the context and output it."""
self.context += self.delta
super().result(value)

@property
- def out(self):
+ def out(self) -> str:
"""Output section of the text."""
start = max(0, self.start - self.context)
end = min(len(self.text), self.end + self.context)
@@ -252,7 +255,7 @@

@deprecated_args(start_context='start', end_context='end')
@deprecated('pywikibot.output(ContextOption.out)', since='6.2.0')
- def output_range(self, start, end):
+ def output_range(self, start: int, end: int) -> None:
"""DEPRECATED. Output a section from the text."""
pywikibot.output(self.text[start:end])

@@ -261,22 +264,24 @@

"""A simple choice consisting of an option, shortcut and handler."""

- def __init__(self, option, shortcut, replacer):
+ def __init__(self, option: str, shortcut: str,
+ replacer: Optional['pywikibot.bot.InteractiveReplace']
+ ) -> None:
"""Initializer."""
super().__init__(option, shortcut)
self._replacer = replacer

@property
- def replacer(self):
+ def replacer(self) -> Optional['pywikibot.bot.InteractiveReplace']:
"""The replacer."""
return self._replacer

@abstractmethod
- def handle(self):
+ def handle(self) -> Any:
"""Handle this choice. Must be implemented."""
raise NotImplementedError()

- def handle_link(self):
+ def handle_link(self) -> bool:
"""The current link will be handled by this choice."""
return False

@@ -285,12 +290,12 @@

"""A static choice which just returns the given value."""

- def __init__(self, option, shortcut, result):
+ def __init__(self, option: str, shortcut: str, result: Any) -> None:
"""Create instance with replacer set to None."""
super().__init__(option, shortcut, None)
self._result = result

- def handle(self):
+ def handle(self) -> Any:
"""Return the predefined value."""
return self._result

@@ -299,15 +304,20 @@

"""A choice returning a mix of the link new and current link."""

- def __init__(self, option, shortcut, replacer, replace_section,
- replace_label):
+ def __init__(self, option: str, shortcut: str,
+ replacer: Optional['pywikibot.bot.InteractiveReplace'],
+ replace_section: bool,
+ replace_label: bool) -> None:
"""Initializer."""
super().__init__(option, shortcut, replacer)
self._section = replace_section
self._label = replace_label

- def handle(self):
+ def handle(self) -> Any:
"""Handle by either applying the new section or label."""
+ if not self.replacer:
+ raise ValueError('LinkChoice requires a replacer')
+
kwargs = {}
if self._section:
kwargs['section'] = self.replacer._new.section
@@ -337,23 +347,27 @@

"""Add an option to always apply the default."""

- def __init__(self, replacer, option='always', shortcut='a'):
+ def __init__(self, replacer: Optional['pywikibot.bot.InteractiveReplace'],
+ option: str = 'always', shortcut: str = 'a') -> None:
"""Initializer."""
super().__init__(option, shortcut, replacer)
self.always = False

- def handle(self):
+ def handle(self) -> Any:
"""Handle the custom shortcut."""
self.always = True
return self.answer

- def handle_link(self):
+ def handle_link(self) -> bool:
"""Directly return answer whether it's applying it always."""
return self.always

@property
- def answer(self):
+ def answer(self) -> Any:
"""Get the actual default answer instructing the replacement."""
+ if not self.replacer:
+ raise ValueError('AlwaysChoice requires a replacer')
+
return self.replacer.handle_answer(self.replacer._default)


@@ -361,7 +375,8 @@

"""An option allowing a range of integers."""

- def __init__(self, minimum=1, maximum=None, prefix='', **kwargs):
+ def __init__(self, minimum: int = 1, maximum: Optional[int] = None,
+ prefix: str = '', **kwargs) -> None:
"""Initializer."""
super().__init__(**kwargs)
if not ((minimum is None or isinstance(minimum, int))
@@ -374,34 +389,36 @@
self._max = maximum
self.prefix = prefix

- def test(self, value) -> bool:
+ def test(self, value: str) -> bool:
"""Return whether the value is an int and in the specified range."""
try:
- value = self.parse(value)
+ int_value = self.parse(value)
except ValueError:
return False

- return ((self.minimum is None or value >= self.minimum)
- and (self.maximum is None or value <= self.maximum))
+ return ((self.minimum is None or int_value >= self.minimum)
+ and (self.maximum is None or int_value <= self.maximum))

@property
- def minimum(self):
+ def minimum(self) -> int:
"""Return the lower bound of the range of allowed values."""
return self._min

@property
- def maximum(self):
+ def maximum(self) -> Optional[int]:
"""Return the upper bound of the range of allowed values."""
return self._max

- def format(self, default=None) -> str:
+ def format(self, default: Optional[str] = None) -> str:
"""Return a formatted string showing the range."""
+ value = None # type: Optional[int]
+
if default is not None and self.test(default):
value = self.parse(default)
default = '[{}]'.format(value)
else:
- value = None
default = ''
+
if self.minimum is not None or self.maximum is not None:
if default and value == self.minimum:
minimum = default
@@ -422,14 +439,14 @@
rng = 'any' + default
return '{}<number> [{}]'.format(self.prefix, rng)

- def parse(self, value) -> int:
+ def parse(self, value: str) -> int:
"""Return integer from value with prefix removed."""
if value.lower().startswith(self.prefix.lower()):
return int(value[len(self.prefix):])
raise ValueError('Value does not start with prefix')

- def result(self, value):
- """Return the value converted into int."""
+ def result(self, value: str) -> Any:
+ """Return a tuple with the prefix and value converted into an int."""
return self.prefix, self.parse(value)


@@ -437,7 +454,7 @@

"""An option to select something from a list."""

- def __init__(self, sequence, prefix='', **kwargs):
+ def __init__(self, sequence: Sequence[str], prefix='', **kwargs) -> None:
"""Initializer."""
self._list = sequence
try:
@@ -446,7 +463,7 @@
raise ValueError('The sequence is empty.')
del self._max

- def format(self, default=None):
+ def format(self, default: Optional[str] = None) -> str:
"""Return a string showing the range."""
if not self._list:
raise ValueError('The sequence is empty.')
@@ -458,7 +475,7 @@
"""Return the maximum value."""
return len(self._list)

- def result(self, value):
+ def result(self, value: str) -> Any:
"""Return a tuple with the prefix and selected value."""
return self.prefix, self._list[self.parse(value) - 1]

@@ -472,8 +489,9 @@

before_question = True

- def __init__(self, sequence, prefix='', pre: Optional[str] = None,
- post: Optional[str] = None, **kwargs):
+ def __init__(self, sequence: Sequence[str], prefix: str = '',
+ pre: Optional[str] = None, post: Optional[str] = None,
+ **kwargs) -> None:
"""Initializer.

:param pre: Additional comment printed before the list.
@@ -484,12 +502,12 @@
self.post = post

@property
- def stop(self):
+ def stop(self) -> bool:
"""Return whether this option stops asking."""
return self._stop

@property
- def out(self):
+ def out(self) -> str:
"""Output text of the enumerated list."""
text = ''
if self.pre is not None:
@@ -509,7 +527,7 @@
*New in version 3.0.*
"""

- def test(self, value) -> bool:
+ def test(self, value: str) -> bool:
"""Return whether the values are int and in the specified range."""
try:
values = [self.parse(val) for val in value.split(',')]
@@ -526,7 +544,7 @@

return False

- def result(self, value):
+ def result(self, value: str) -> Any:
"""Return a tuple with the prefix and selected values as a list."""
values = (self.parse(val) for val in value.split(','))
result = [self._list[val - 1] for val in values]
@@ -548,7 +566,7 @@
color = 'lightred'

@property
- def out(self):
+ def out(self) -> str:
"""Highlighted output section of the text."""
start = max(0, self.start - self.context)
end = min(len(self.text), self.end + self.context)
@@ -561,7 +579,7 @@

@deprecated('pywikibot.output(HighlightContextOption.out)',
since='6.2.0')
- def output_range(self, start, end):
+ def output_range(self, start: int, end: int) -> None:
"""DEPRECATED. Show normal context with a highlighted center region."""
color_format = pywikibot.tools.formatter.color_format
text = color_format('{}{%(color)s}{}{default}{}'
@@ -576,7 +594,7 @@

"""The given answer didn't suffice."""

- def __int__(self, stop=False):
+ def __int__(self, stop: bool = False) -> None:
"""Initializer."""
self.stop = stop

@@ -585,7 +603,7 @@

"""A choice for input_choice which result in this exception."""

- def result(self, value):
+ def result(self, value: Any) -> Any:
"""Return itself to raise the exception."""
return self

@@ -594,6 +612,6 @@

"""The user has cancelled processing at a prompt."""

- def __init__(self):
+ def __init__(self) -> None:
"""Constructor using the 'quit' ('q') in input_choice."""
super().__init__('quit', 'q')

To view, visit change 703916. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: Ia04380cf787aac4b92d17b81b3baa87ba9764243
Gerrit-Change-Number: 703916
Gerrit-PatchSet: 3
Gerrit-Owner: Damian <atagar1@gmail.com>
Gerrit-Reviewer: JJMC89 <JJMC89.Wikimedia@gmail.com>
Gerrit-Reviewer: jenkins-bot
Gerrit-CC: Xqt <info@gno.de>
Gerrit-MessageType: merged