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
--
To view, visit
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/981650
To unsubscribe, or for help writing mail filters, visit
https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: I3817ce91a527b07d5f9191c77d6b7f991b9e6703
Gerrit-Change-Number: 981650
Gerrit-PatchSet: 6
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Ammarpad <ammarpad(a)yahoo.com>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged