jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/703916 )
Change subject: [IMPR] bot_choice.py type hints ......................................................................
[IMPR] bot_choice.py type hints
Bug: T286403 Change-Id: Ia04380cf787aac4b92d17b81b3baa87ba9764243 --- M pywikibot/bot_choice.py 1 file changed, 86 insertions(+), 68 deletions(-)
Approvals: JJMC89: Looks good to me, approved jenkins-bot: Verified
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')
pywikibot-commits@lists.wikimedia.org