jenkins-bot submitted this change.

View Change

Approvals: Xqt: Looks good to me, approved jenkins-bot: Verified
[IMPR] Re-enable cached output functionality from compat

Bug: T73646
Bug: T151727
Change-Id: I33b4164336773dc98dde642f98e86e2dfe6e95a2
---
M ROADMAP.rst
M pywikibot/userinterfaces/terminal_interface_base.py
M tests/i18n_tests.py
3 files changed, 103 insertions(+), 62 deletions(-)

diff --git a/ROADMAP.rst b/ROADMAP.rst
index eba2565..c3549f7 100644
--- a/ROADMAP.rst
+++ b/ROADMAP.rst
@@ -4,11 +4,11 @@
Improvements and Bugfixes
-------------------------

+* The cached output functionality from compat release was re-implemented (T151727, T73646, T74942, T132135, T144698, T196039, T280466)
* Adjust groupsize within pagegenerators.PreloadingGenerator (T291770)
* New "maxlimit" property was added to APISite (T291770)


-
Breaking changes
----------------

diff --git a/pywikibot/userinterfaces/terminal_interface_base.py b/pywikibot/userinterfaces/terminal_interface_base.py
index 83d6684..5a94d6e 100755
--- a/pywikibot/userinterfaces/terminal_interface_base.py
+++ b/pywikibot/userinterfaces/terminal_interface_base.py
@@ -22,6 +22,7 @@
StandardOption,
)
from pywikibot.logging import INFO, INPUT, STDOUT, VERBOSE, WARNING
+from pywikibot.tools import RLock
from pywikibot.userinterfaces import transliteration
from pywikibot.userinterfaces._interface_base import ABUIC

@@ -75,6 +76,8 @@
self.argv = sys.argv
self.encoding = config.console_encoding
self.transliteration_target = config.transliteration_target
+ self.cache = []
+ self.lock = RLock()

def init_handlers(self, root_logger, default_stream='stderr'):
"""Initialize the handlers for user output.
@@ -184,12 +187,46 @@
self.encounter_color(color_stack[-1], target_stream)

def output(self, text, targetStream=None):
+ """Forward text to cache and flush if output is not locked.
+
+ All input methods locks the output to a stream but collect them
+ in cache. They will be printed with next unlocked output call or
+ at termination time.
+
+ .. versionchanged:: 7.0
+ Forward text to cache and flush if output is not locked.
"""
- Output text to a stream.
+ self.cache_output(text, targetStream=targetStream)
+ if not self.lock.locked():
+ self.flush()
+
+ def flush(self):
+ """Output cached text.
+
+ .. versionadded:: 7.0
+ """
+ with self.lock:
+ for args, kwargs in self.cache:
+ self.stream_output(*args, **kwargs)
+ self.cache.clear()
+
+ def cache_output(self, *args, **kwargs):
+ """Put text to cache.
+
+ .. versionadded:: 7.0
+ """
+ with self.lock:
+ self.cache.append((args, kwargs))
+
+ def stream_output(self, text, targetStream=None):
+ """Output text to a stream.

If a character can't be displayed in the encoding used by the user's
terminal, it will be replaced with a question mark or by a
transliteration.
+
+ .. versionadded:: 7.0
+ ``UI.output()`` was renamed to ``UI.stream_output()``
"""
if config.transliterate:
# Encode our unicode string in the encoding used by the user's
@@ -277,27 +314,29 @@
question += end_marker

# lock stream output
- # with self.lock: (T282962)
- if force:
- self.output(question + '\n')
- return default
- # sound the terminal bell to notify the user
- if config.ring_bell:
- sys.stdout.write('\07')
- # TODO: make sure this is logged as well
- while True:
- self.output(question + ' ')
- text = self._input_reraise_cntl_c(password)
-
- if text is None:
- continue
-
- if text:
- return text
-
- if default is not None:
+ with self.lock:
+ if force:
+ self.stream_output(question + '\n')
return default

+ # sound the terminal bell to notify the user
+ if config.ring_bell:
+ sys.stdout.write('\07')
+
+ # TODO: make sure this is logged as well
+ while True:
+ self.stream_output(question + ' ')
+ text = self._input_reraise_cntl_c(password)
+
+ if text is None:
+ continue
+
+ if text:
+ return text
+
+ if default is not None:
+ return default
+
def _input_reraise_cntl_c(self, password):
"""Input and decode, and re-raise Control-C."""
try:
@@ -351,7 +390,7 @@
"""Print an OutputOption before or after question."""
if isinstance(option, OutputOption) \
and option.before_question is before_question:
- self.output(option.out + '\n')
+ self.stream_output(option.out + '\n')

if force and default is None:
raise ValueError('With no default option it cannot be forced')
@@ -376,24 +415,24 @@
handled = False

# lock stream output
- # with self.lock: (T282962)
- while not handled:
- for option in options:
- output_option(option, before_question=True)
- output = Option.formatted(question, options, default)
- if force:
- self.output(output + '\n')
- answer = default
- else:
- answer = self.input(output) or default
- # something entered or default is defined
- if answer:
- for index, option in enumerate(options):
- if option.handled(answer):
- answer = option.result(answer)
- output_option(option, before_question=False)
- handled = option.stop
- break
+ with self.lock:
+ while not handled:
+ for option in options:
+ output_option(option, before_question=True)
+ output = Option.formatted(question, options, default)
+ if force:
+ self.stream_output(output + '\n')
+ answer = default
+ else:
+ answer = self.input(output) or default
+ # something entered or default is defined
+ if answer:
+ for index, option in enumerate(options):
+ if option.handled(answer):
+ answer = option.result(answer)
+ output_option(option, before_question=False)
+ handled = option.stop
+ break

if isinstance(answer, ChoiceException):
raise answer
@@ -414,31 +453,33 @@
:return: Return a single Sequence entry.
"""
# lock stream output
- # with self.lock: (T282962)
- if not force:
- line_template = '{{0: >{}}}: {{1}}'.format(len(str(len(answers))))
- for i, entry in enumerate(answers, start=1):
- pywikibot.output(line_template.format(i, entry))
+ with self.lock:
+ if not force:
+ line_template = '{{0: >{}}}: {{1}}\n'.format(
+ len(str(len(answers))))
+ for i, entry in enumerate(answers, start=1):
+ self.stream_output(line_template.format(i, entry))

- while True:
- choice = self.input(question, default=default, force=force)
+ while True:
+ choice = self.input(question, default=default, force=force)

- try:
- choice = int(choice) - 1
- except (TypeError, ValueError):
- if choice in answers:
- return choice
- choice = -1
+ try:
+ choice = int(choice) - 1
+ except (TypeError, ValueError):
+ if choice in answers:
+ return choice
+ choice = -1

- # User typed choice number
- if 0 <= choice < len(answers):
- return answers[choice]
+ # User typed choice number
+ if 0 <= choice < len(answers):
+ return answers[choice]

- if force:
- raise ValueError('Invalid value "{}" for default during force.'
- .format(default))
+ if force:
+ raise ValueError(
+ 'Invalid value "{}" for default during force.'
+ .format(default))

- pywikibot.error('Invalid response')
+ pywikibot.error('Invalid response')

def editText(self, text: str, jumpIndex: Optional[int] = None,
highlight: Optional[str] = None):
diff --git a/tests/i18n_tests.py b/tests/i18n_tests.py
index 21d22d3..946c0c3 100644
--- a/tests/i18n_tests.py
+++ b/tests/i18n_tests.py
@@ -341,9 +341,9 @@
bot.set_interface('terminal')
self.output_text = ''
self.orig_raw_input = bot.ui._raw_input
- self.orig_output = bot.ui.output
+ self.orig_output = bot.ui.stream_output
bot.ui._raw_input = lambda *args, **kwargs: 'dummy input'
- bot.ui.output = self._capture_output
+ bot.ui.stream_output = self._capture_output
self.old_cc_setting = config.cosmetic_changes_mylang_only

def tearDown(self):

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

Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: I33b4164336773dc98dde642f98e86e2dfe6e95a2
Gerrit-Change-Number: 729234
Gerrit-PatchSet: 5
Gerrit-Owner: Xqt <info@gno.de>
Gerrit-Reviewer: Xqt <info@gno.de>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged