jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/981650 )
Change subject: [IMPR] Validate default parameter value in input_choice() function ......................................................................
[IMPR] Validate default parameter value in input_choice() function
Also update documentation.
Bug: T353097 Change-Id: I3817ce91a527b07d5f9191c77d6b7f991b9e6703 --- M pywikibot/userinterfaces/terminal_interface_base.py M pywikibot/bot.py 2 files changed, 70 insertions(+), 27 deletions(-)
Approvals: Xqt: Looks good to me, approved jenkins-bot: Verified
diff --git a/pywikibot/bot.py b/pywikibot/bot.py index 0fe8fdd..d1827d4 100644 --- a/pywikibot/bot.py +++ b/pywikibot/bot.py @@ -562,8 +562,12 @@ return_shortcut: bool = True, automatic_quit: bool = True, force: bool = False) -> Any: - """ - Ask the user the question and return one of the valid answers. + """Ask the user the question and return one of the valid answers. + + .. seealso:: + * :meth:`userinterfaces._interface_base.ABUIC.input_choice` + * :meth:`userinterfaces.buffer_interface.UI.input_choice` + * :meth:`userinterfaces.terminal_interface_base.UI.input_choice`
:param question: The question asked without trailing spaces. :param answers: The valid answers each containing a full length answer and @@ -574,8 +578,8 @@ :param return_shortcut: Whether the shortcut or the index of the answer is returned. :param automatic_quit: Adds the option 'Quit' ('q') and throw a - :py:obj:`QuitKeyboardInterrupt` if selected. - :param force: Automatically use the default + :exc:`bot.QuitKeyboardInterrupt` if selected. + :param force: Automatically use the *default*. :return: The selected answer shortcut or index. Is -1 if the default is selected, it does not return the shortcut and the default is not a valid shortcut. @@ -589,28 +593,35 @@ default: bool | str | None = None, automatic_quit: bool = True, force: bool = False) -> bool: - """ - Ask the user a yes/no question and return the answer as a bool. + """Ask the user a yes/no question and return the answer as a bool. + + **Example:** + + >>> input_yn('Do you like Pywikibot?', 'y', False, force=True) + ... # doctest: +SKIP + Do you like Pywikibot? ([Y]es, [n]o) + True + >>> input_yn('More examples?', False, automatic_quit=False, force=True) + ... # doctest: +SKIP + Some more examples? ([y]es, [N]o) + False + + .. seealso:: :func:`input_choice`
:param question: The question asked without trailing spaces. - :param default: The result if no answer was entered. It must be a bool or - 'y' or 'n' and can be disabled by setting it to None. + :param default: The result if no answer was entered. It must be a + bool or ``'y'``, ``'n'``, ``0`` or ``1`` and can be disabled by + setting it to None. :param automatic_quit: Adds the option 'Quit' ('q') and throw a - :py:obj:`QuitKeyboardInterrupt` if selected. - :param force: Automatically use the default + :exc:`bot.QuitKeyboardInterrupt` if selected. + :param force: Automatically use the *default*. :return: Return True if the user selected yes and False if the user selected no. If the default is not None it'll return True if default is True or 'y' and False if default is False or 'n'. """ - if default not in ['y', 'Y', 'n', 'N']: - if default: - default = 'y' - elif default is not None: - default = 'n' - assert default in ['y', 'Y', 'n', 'N', None], \ - 'Default choice must be one of YyNn or default' + if default in (True, False): + default = 'ny'[default]
- assert not isinstance(default, bool) return input_choice(question, [('Yes', 'y'), ('No', 'n')], default, automatic_quit=automatic_quit, force=force) == 'y' diff --git a/pywikibot/userinterfaces/terminal_interface_base.py b/pywikibot/userinterfaces/terminal_interface_base.py index 019b8e8..8346abb 100644 --- a/pywikibot/userinterfaces/terminal_interface_base.py +++ b/pywikibot/userinterfaces/terminal_interface_base.py @@ -1,6 +1,6 @@ """Base for terminal user interfaces.""" # -# (C) Pywikibot team, 2003-2022 +# (C) Pywikibot team, 2003-2023 # # Distributed under the terms of the MIT license. # @@ -15,7 +15,7 @@
import pywikibot from pywikibot import config -from pywikibot.backports import Iterable, Sequence +from pywikibot.backports import Iterable, Sequence, removeprefix from pywikibot.bot_choice import ( ChoiceException, Option, @@ -424,12 +424,17 @@ automatic_quit: bool = True, force: bool = False, ) -> Any: - """ - Ask the user and returns a value from the options. + """Ask the user and returns a value from the options.
- Depending on the options setting return_shortcut to False may not be - sensible when the option supports multiple values as it'll return an - ambiguous index. + Depending on the options setting *return_shortcut* to False may + not be sensible when the option supports multiple values as + it'll return an ambiguous index. + + .. versionchanged:: 9.0 + Raise ValueError if no *default* value is given with *force*; + raise ValueError if *force* is True and *default* value is + invalid; raise TypeError if *default* value is neither str + nor None.
:param question: The question, without trailing whitespace. :param options: Iterable of all available options. Each entry contains @@ -447,6 +452,9 @@ :return: If return_shortcut the shortcut of options or the value of default (if it's not None). Otherwise the index of the answer in options. If default is not a shortcut, it'll return -1. + :raises ValueError: invalid or no *default* value is given with + *force* or no or an invalid option is given. + :raises TypeError: *default* value is neither None nor str """ def output_option(option, before_question) -> None: """Print an OutputOption before or after question.""" @@ -454,18 +462,25 @@ and option.before_question is before_question: self.stream_output(option.out + '\n')
- if force and default is None: + if force and not default: raise ValueError('With no default option it cannot be forced') + if isinstance(options, Option): options = [options] else: # make a copy options = list(options) + if not options: raise ValueError('No options are given.') if automatic_quit: options.append(QuitKeyboardInterrupt()) - if default: + + if isinstance(default, str): default = default.lower() + elif default is not None: + raise TypeError(f'Invalid type {type(default).__name__!r} for ' + f"parameter 'default' ({default}); str expected") + for i, option in enumerate(options): if not isinstance(option, Option): if len(option) != 2: @@ -495,6 +510,11 @@ output_option(option, before_question=False) handled = option.stop break + else: + if force: + raise ValueError( + f'{default!r} is not a valid Option for ' + f'{removeprefix(output, question).lstrip()}')
if isinstance(answer, ChoiceException): raise answer
pywikibot-commits@lists.wikimedia.org