jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/608153 )
Change subject: [IMPR] Rewrite tools.intersect_generators
......................................................................
[IMPR] Rewrite tools.intersect_generators
- Rewrite tools.intersect_generators using itertools.zip_longest
instead of threads. This makes the intersect_generators running
up to 10'000 times faster.
- use a set for seen instead a dict
- use the iterable index for cache instead of thread
- explicit increase cache Counter by 1 instead of update it by a single item
- use a set for active iterables instead of active_count() method
- Move BasicGeneratorIntersectTestCase and GeneratorIntersectTestCase
from thread_tests to tools_tests.py because intersect_generators is
no longer done by threads.
- early return if our cache's keys is a subset of active iterables
- Add more tests to TestFactoryGenerator.test_intersect_generator
Bug: T293276
Bug: T85623
Change-Id: Ic123eb821abc9abb44d5cab6973e58da911d6d76
---
M pywikibot/tools/__init__.py
M tests/pagegenerators_tests.py
M tests/thread_tests.py
M tests/tools_tests.py
4 files changed, 133 insertions(+), 121 deletions(-)
Approvals:
Matěj Suchánek: Looks good to me, but someone else must approve
Xqt: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/tools/__init__.py b/pywikibot/tools/__init__.py
index a988b10..eb4fe40 100644
--- a/pywikibot/tools/__init__.py
+++ b/pywikibot/tools/__init__.py
@@ -801,16 +801,15 @@
def intersect_generators(*iterables, allow_duplicates: bool = False):
- """Intersect generators listed in iterables.
+ """Generator of intersect iterables.
- Yield items only if they are yielded by all generators of iterables.
- Threads (via ThreadedGenerator) are used in order to run generators
- in parallel, so that items can be yielded before generators are
- exhausted.
+ Yield items only if they are yielded by all iterables. zip_longest
+ is used to retrieve items from all iterables in parallel, so that
+ items can be yielded before iterables are exhausted.
- Threads are stopped when they are either exhausted or Ctrl-C is pressed.
- Quitting before all generators are finished is attempted if
- there is no more chance of finding an item in all queues.
+ Generator is stopped when all iterables are exhausted. Quitting
+ before all iterables are finished is attempted if there is no more
+ chance of finding an item in all of them.
Sample:
@@ -834,6 +833,9 @@
``allow_duplicates`` as positional argument,
``iterables`` as list type
+ .. versionchanged:: 7.0
+ Reimplemented without threads which is up to 10'000 times faster
+
:param iterables: page generators
:param allow_duplicates: optional keyword argument to allow duplicates
if present in all generators
@@ -861,73 +863,55 @@
yield from iterables[0]
return
- # If any generator is empty, no pages are going to be returned
+ # If any iterable is empty, no pages are going to be returned
for source in iterables:
if not source:
- debug('At least one generator ({!r}) is empty and execution was '
+ debug('At least one iterable ({!r}) is empty and execution was '
'skipped immediately.'.format(source), 'intersect')
return
- # Item is cached to check that it is found n_gen
- # times before being yielded.
+ # Item is cached to check that it is found n_gen times
+ # before being yielded.
cache = collections.defaultdict(collections.Counter)
n_gen = len(iterables)
- # Class to keep track of alive threads.
- # Start new threads and remove completed threads.
- thrlist = ThreadList()
+ ones = collections.Counter(range(n_gen))
+ active_iterables = set(range(n_gen))
+ seen = set()
- for source in iterables:
- threaded_gen = ThreadedGenerator(name=repr(source), target=source)
- threaded_gen.daemon = True
- thrlist.append(threaded_gen)
+ # Get items from iterables in a round-robin way.
+ sentinel = object()
+ for items in zip_longest(*iterables, fillvalue=sentinel):
+ for index, item in enumerate(items):
- ones = collections.Counter(thrlist)
- seen = {}
+ if item is sentinel:
+ active_iterables.discard(index)
+ continue
- while True:
- # Get items from queues in a round-robin way.
- for t in thrlist:
- try:
- # TODO: evaluate if True and timeout is necessary.
- item = t.queue.get(True, 0.1)
+ if not allow_duplicates and hash(item) in seen:
+ continue
- if not allow_duplicates and hash(item) in seen:
- continue
+ # Each cache entry is a Counter of iterables' index
+ cache[item][index] += 1
- # Cache entry is a Counter of ThreadedGenerator objects.
- cache[item].update([t])
- if len(cache[item]) == n_gen:
- if allow_duplicates:
- yield item
- # Remove item from cache if possible.
- if all(el == 1 for el in cache[item].values()):
- cache.pop(item)
- else:
- cache[item] -= ones
- else:
- yield item
- cache.pop(item)
- seen[hash(item)] = True
+ if len(cache[item]) == n_gen:
+ yield item
- active = thrlist.active_count()
- max_cache = n_gen
- if cache.values():
- max_cache = max(len(v) for v in cache.values())
- # No. of active threads is not enough to reach n_gen.
- # We can quit even if some thread is still active.
- # There could be an item in all generators which has not yet
- # appeared from any generator. Only when we have lost one
- # generator, then we can bail out early based on seen items.
- if active < n_gen and n_gen - max_cache > active:
- thrlist.stop_all()
- return
- except queue.Empty:
- pass
- except KeyboardInterrupt:
- thrlist.stop_all()
- # All threads are done.
- if thrlist.active_count() == 0:
+ # Remove item from cache if possible or decrease Counter entry
+ if not allow_duplicates:
+ del cache[item]
+ seen.add(hash(item))
+ elif cache[item] == ones:
+ del cache[item]
+ else:
+ cache[item] -= ones
+
+ # We can quit if an iterable is exceeded and cached iterables is
+ # a subset of active iterables.
+ if len(active_iterables) < n_gen:
+ cached_iterables = set(
+ chain.from_iterable(v.keys() for v in cache.values()))
+ if cached_iterables <= active_iterables:
return
diff --git a/tests/pagegenerators_tests.py b/tests/pagegenerators_tests.py
index beb9c92..6129d5e 100644
--- a/tests/pagegenerators_tests.py
+++ b/tests/pagegenerators_tests.py
@@ -30,7 +30,7 @@
TestCase,
WikidataTestCase,
)
-from tests.thread_tests import GeneratorIntersectTestCase
+from tests.tools_tests import GeneratorIntersectTestCase
LINKSEARCH_MSG = (r'.*pywikibot\.pagegenerators\.LinksearchPageGenerator .*'
@@ -894,12 +894,39 @@
self.assertEqual(tuple(gen), ('A', 'B', 'C'))
def test_intersect_generator(self):
- """Test getCombinedGenerator with generator parameter."""
+ """Test getCombinedGenerator with -intersect option."""
gf = pagegenerators.GeneratorFactory()
gf.handle_arg('-intersect')
- gf.gens = ['Python 3.7-dev']
- gen = gf.getCombinedGenerator(gen='Pywikibot 3.0.dev')
- self.assertEqual(''.join(gen), 'Pyot 3.dev')
+
+ # check wether the generator works for both directions
+ patterns = ['Python 3.7-dev', 'Pywikibot 7.0.dev']
+ for index in range(2):
+ with self.subTest(index=index):
+ gf.gens = [patterns[index]]
+ gen = gf.getCombinedGenerator(gen=patterns[index - 1])
+ self.assertEqual(''.join(gen), 'Pyot 7.dev')
+
+ # check wether the generator works for a very long text
+ patterns.append('PWB 7+ unittest developed with a very long text.')
+ with self.subTest(patterns=patterns):
+ gf.gens = patterns
+ gen = gf.getCombinedGenerator()
+ self.assertEqual(''.join(gen), 'P 7tedvoy.')
+
+ # check whether an early stop fits
+ with self.subTest(comment='Early stop'):
+ gf.gens = 'ABC', 'A Big City'
+ gen = gf.getCombinedGenerator()
+ self.assertEqual(''.join(gen), 'ABC')
+
+ with self.subTest(comment='Commutative'):
+ gf.gens = 'ABB', 'BB'
+ gen1 = gf.getCombinedGenerator()
+ gf2 = pagegenerators.GeneratorFactory()
+ gf2.handle_arg('-intersect')
+ gf2.gens = 'BB', 'ABB'
+ gen2 = gf2.getCombinedGenerator()
+ self.assertEqual(list(gen1), list(gen2))
def test_ns(self):
"""Test namespace option."""
diff --git a/tests/thread_tests.py b/tests/thread_tests.py
index 44ec655..329017f 100644
--- a/tests/thread_tests.py
+++ b/tests/thread_tests.py
@@ -5,10 +5,9 @@
# Distributed under the terms of the MIT license.
#
import unittest
-from collections import Counter
from contextlib import suppress
-from pywikibot.tools import ThreadedGenerator, intersect_generators
+from pywikibot.tools import ThreadedGenerator
from tests.aspects import TestCase
@@ -39,58 +38,6 @@
self.assertEqual(list(thd_gen), list(iterable))
-class GeneratorIntersectTestCase(TestCase):
-
- """Base class for intersect_generators test cases."""
-
- def assertEqualItertools(self, gens):
- """Assert intersect_generators result is same as set intersection."""
- # If they are a generator, we need to convert to a list
- # first otherwise the generator is empty the second time.
- datasets = [list(gen) for gen in gens]
- set_result = set(datasets[0]).intersection(*datasets[1:])
- result = list(intersect_generators(*datasets))
-
- self.assertCountEqual(set(result), result)
- self.assertCountEqual(result, set_result)
-
- def assertEqualItertoolsWithDuplicates(self, gens):
- """Assert intersect_generators result equals Counter intersection."""
- # If they are a generator, we need to convert to a list
- # first otherwise the generator is empty the second time.
- datasets = [list(gen) for gen in gens]
- counter_result = Counter(datasets[0])
- for dataset in datasets[1:]:
- counter_result = counter_result & Counter(dataset)
- counter_result = list(counter_result.elements())
- result = list(intersect_generators(*datasets, allow_duplicates=True))
- self.assertCountEqual(counter_result, result)
-
-
-class BasicGeneratorIntersectTestCase(GeneratorIntersectTestCase):
-
- """Disconnected intersect_generators test cases."""
-
- net = False
-
- def test_intersect_basic(self):
- """Test basic intersect without duplicates."""
- self.assertEqualItertools(['abc', 'db', 'ba'])
-
- def test_intersect_with_dups(self):
- """Test basic intersect with duplicates."""
- self.assertEqualItertools(['aabc', 'dddb', 'baa'])
-
- def test_intersect_with_accepted_dups(self):
- """Test intersect with duplicates accepted."""
- self.assertEqualItertoolsWithDuplicates(['abc', 'db', 'ba'])
- self.assertEqualItertoolsWithDuplicates(['aabc', 'dddb', 'baa'])
- self.assertEqualItertoolsWithDuplicates(['abb', 'bb'])
- self.assertEqualItertoolsWithDuplicates(['bb', 'abb'])
- self.assertEqualItertoolsWithDuplicates(['abbcd', 'abcba'])
- self.assertEqualItertoolsWithDuplicates(['abcba', 'abbcd'])
-
-
if __name__ == '__main__': # pragma: no cover
with suppress(SystemExit):
unittest.main()
diff --git a/tests/tools_tests.py b/tests/tools_tests.py
index 66a6bb5..8069880 100644
--- a/tests/tools_tests.py
+++ b/tests/tools_tests.py
@@ -9,7 +9,8 @@
import subprocess
import tempfile
import unittest
-from collections import OrderedDict
+
+from collections import Counter, OrderedDict
from collections.abc import Mapping
from contextlib import suppress
from importlib import import_module
@@ -18,6 +19,7 @@
from pywikibot.tools import (
classproperty,
has_module,
+ intersect_generators,
is_ip_address,
suppress_warnings,
)
@@ -731,6 +733,58 @@
self.assertEqual(Foo.bar, Foo._bar)
+class GeneratorIntersectTestCase(TestCase):
+
+ """Base class for intersect_generators test cases."""
+
+ def assertEqualItertools(self, gens):
+ """Assert intersect_generators result is same as set intersection."""
+ # If they are a generator, we need to convert to a list
+ # first otherwise the generator is empty the second time.
+ datasets = [list(gen) for gen in gens]
+ set_result = set(datasets[0]).intersection(*datasets[1:])
+ result = list(intersect_generators(*datasets))
+
+ self.assertCountEqual(set(result), result)
+ self.assertCountEqual(result, set_result)
+
+ def assertEqualItertoolsWithDuplicates(self, gens):
+ """Assert intersect_generators result equals Counter intersection."""
+ # If they are a generator, we need to convert to a list
+ # first otherwise the generator is empty the second time.
+ datasets = [list(gen) for gen in gens]
+ counter_result = Counter(datasets[0])
+ for dataset in datasets[1:]:
+ counter_result = counter_result & Counter(dataset)
+ counter_result = list(counter_result.elements())
+ result = list(intersect_generators(*datasets, allow_duplicates=True))
+ self.assertCountEqual(counter_result, result)
+
+
+class BasicGeneratorIntersectTestCase(GeneratorIntersectTestCase):
+
+ """Disconnected intersect_generators test cases."""
+
+ net = False
+
+ def test_intersect_basic(self):
+ """Test basic intersect without duplicates."""
+ self.assertEqualItertools(['abc', 'db', 'ba'])
+
+ def test_intersect_with_dups(self):
+ """Test basic intersect with duplicates."""
+ self.assertEqualItertools(['aabc', 'dddb', 'baa'])
+
+ def test_intersect_with_accepted_dups(self):
+ """Test intersect with duplicates accepted."""
+ self.assertEqualItertoolsWithDuplicates(['abc', 'db', 'ba'])
+ self.assertEqualItertoolsWithDuplicates(['aabc', 'dddb', 'baa'])
+ self.assertEqualItertoolsWithDuplicates(['abb', 'bb'])
+ self.assertEqualItertoolsWithDuplicates(['bb', 'abb'])
+ self.assertEqualItertoolsWithDuplicates(['abbcd', 'abcba'])
+ self.assertEqualItertoolsWithDuplicates(['abcba', 'abbcd'])
+
+
class TestMergeGenerator(TestCase):
"""Test merging generators."""
--
To view, visit https://gerrit.wikimedia.org/r/c/pywikibot/core/+/608153
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: Ic123eb821abc9abb44d5cab6973e58da911d6d76
Gerrit-Change-Number: 608153
Gerrit-PatchSet: 18
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Matěj Suchánek <matejsuchanek97(a)gmail.com>
Gerrit-Reviewer: Mpaa <mpaa.wiki(a)gmail.com>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot
Gerrit-CC: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-CC: Zhuyifei1999 <zhuyifei1999(a)gmail.com>
Gerrit-MessageType: merged
Xqt has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/730564 )
Change subject: [docs] Fix a few typos
......................................................................
[docs] Fix a few typos
There are small typos in:
- pywikibot/tools/__init__.py
- scripts/checkimages.py
- scripts/interwiki.py
Fixes:
- Should read `throttling` rather than `throtteling`.
- Should read `structure` rather than `stucture`.
- Should read `notifications` rather than `nofitications`.
PR39 submitted by timgates42.
Thanks
Change-Id: Iff511329e0ffdcea0a39c5b3997da787c82f1b9c
---
M CREDITS.rst
M pywikibot/tools/__init__.py
M scripts/checkimages.py
M scripts/interwiki.py
4 files changed, 4 insertions(+), 3 deletions(-)
Approvals:
Xqt: Verified; Looks good to me, approved
diff --git a/CREDITS.rst b/CREDITS.rst
index 414caa6..f2e412b 100644
--- a/CREDITS.rst
+++ b/CREDITS.rst
@@ -303,6 +303,7 @@
theopolisme
Thomas R. Koll
ThomasV
+ timgates42
Timo Tijhof
Tony Thomas
Toto Azéro
diff --git a/pywikibot/tools/__init__.py b/pywikibot/tools/__init__.py
index a988b10..8eaf722 100644
--- a/pywikibot/tools/__init__.py
+++ b/pywikibot/tools/__init__.py
@@ -237,7 +237,7 @@
"""Structure to hold values where the key is given by the value itself.
- A stucture like a defaultdict but the key is given by the value
+ A structure like a defaultdict but the key is given by the value
itselfvand cannot be assigned directly. It returns the number of all
items with len() but not the number of keys.
diff --git a/scripts/checkimages.py b/scripts/checkimages.py
index cee134e..08cac8a 100755
--- a/scripts/checkimages.py
+++ b/scripts/checkimages.py
@@ -24,7 +24,7 @@
-duplicatesreport Report the duplicates in a log *AND* put the template in
the images.
--maxusernotify Maximum nofitications added to a user talk page in a single
+-maxusernotify Maximum notifications added to a user talk page in a single
check, to avoid email spamming.
-sendemail Send an email after tagging.
diff --git a/scripts/interwiki.py b/scripts/interwiki.py
index 67ba233..d52cf7b 100755
--- a/scripts/interwiki.py
+++ b/scripts/interwiki.py
@@ -92,7 +92,7 @@
(note: without ending colon)
-async Put page on queue to be saved to wiki asynchronously. This
- enables loading pages during saving throtteling and gives a
+ enables loading pages during saving throttling and gives a
better performance.
NOTE: For post-processing it always assumes that saving the
the pages was successful.
--
To view, visit https://gerrit.wikimedia.org/r/c/pywikibot/core/+/730564
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: Iff511329e0ffdcea0a39c5b3997da787c82f1b9c
Gerrit-Change-Number: 730564
Gerrit-PatchSet: 2
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: D3r1ck01 <xsavitar.wiki(a)aol.com>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged
jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/729234 )
Change subject: [IMPR] Re-enable cached output functionality from compat
......................................................................
[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(-)
Approvals:
Xqt: Looks good to me, approved
jenkins-bot: Verified
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 https://gerrit.wikimedia.org/r/c/pywikibot/core/+/729234
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: I33b4164336773dc98dde642f98e86e2dfe6e95a2
Gerrit-Change-Number: 729234
Gerrit-PatchSet: 5
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged
jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/725719 )
Change subject: [IMPR] Validate setuptools only once
......................................................................
[IMPR] Validate setuptools only once
check_modules function is called every time pwb.py is executed but
called a second time if a script is invoked. Run this check only
once.
Change-Id: If7a8541b362ecdf38571679b82eaee73a4429d24
---
M pwb.py
1 file changed, 18 insertions(+), 15 deletions(-)
Approvals:
Xqt: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pwb.py b/pwb.py
index f93eeb9..b8ddd82 100755
--- a/pwb.py
+++ b/pwb.py
@@ -182,27 +182,30 @@
"""
import pkg_resources
- from setup import dependencies, script_deps
+ from setup import script_deps
missing_requirements = []
version_conflicts = []
- try:
- requirement = next(pkg_resources.parse_requirements(dependencies))
- except ValueError as e:
- # T286980: setuptools is too old and requirement parsing fails
- import setuptools
- setupversion = tuple(int(num)
- for num in setuptools.__version__.split('.'))
- if setupversion < (20, 8, 1):
- # print the minimal requirement
- _print_requirements(['setuptools==20.8.1'], None,
- 'outdated ({})'.format(setuptools.__version__))
- return False
- raise e
-
if script:
dependencies = script_deps.get(Path(script).name, [])
+ else:
+ from setup import dependencies
+ try:
+ next(pkg_resources.parse_requirements(dependencies))
+ except ValueError as e:
+ # T286980: setuptools is too old and requirement parsing fails
+ import setuptools
+ setupversion = tuple(int(num)
+ for num in setuptools.__version__.split('.'))
+ if setupversion < (20, 8, 1):
+ # print the minimal requirement
+ _print_requirements(
+ ['setuptools>=20.8.1'], None,
+ 'outdated ({})'.format(setuptools.__version__))
+ return False
+ raise e
+
for requirement in pkg_resources.parse_requirements(dependencies):
if requirement.marker is None \
or pkg_resources.evaluate_marker(str(requirement.marker)):
--
To view, visit https://gerrit.wikimedia.org/r/c/pywikibot/core/+/725719
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: If7a8541b362ecdf38571679b82eaee73a4429d24
Gerrit-Change-Number: 725719
Gerrit-PatchSet: 5
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: JJMC89 <JJMC89.Wikimedia(a)gmail.com>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged