jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/812372 )
Change subject: [IMPR] Provide a Generator wrapper class to start/restart a generator ......................................................................
[IMPR] Provide a Generator wrapper class to start/restart a generator
- Make all api generators a collections.abc.Generator - Make EventStreams a a collections.abc.GeneratorGenerator - add an abstract api.APIGeneratorBase class which replaces the _RequestWrapper class. This base class provide all methods from _RequestWrapper but also a abstract generator method to be used with the send() and throw() method which defines the Generator. The close() method is inherited from collections.abc.generator as well as Iterator and Iterable functionality. A restart() method was added which is able to restart the generator. - rename all '__iter__' methods to generator property to be used with the Generator methods above. - show an info if a Iterable is wrapped into a generator in BaseBot.run() - remove iter() function for every generator based on APIGeneratorBase - update tests - shorten boolean test in thanks_tests.py and flow_thanks_tests.py - remove TestRecentChanges.setUpClass because imagepage isn't used - enable doctest for eventstreams.py
Bug: T301318 Bug: T312654 Bug: T312883 Change-Id: I92941d18ad71f10b04a3c7f7d7bbbd4460e9c861 --- M pywikibot/bot.py M pywikibot/comms/eventstreams.py M pywikibot/data/api/__init__.py M pywikibot/data/api/_generators.py M pywikibot/login.py M pywikibot/page/_user.py M pywikibot/tools/collections.py M scripts/checkimages.py M tests/api_tests.py M tests/flow_thanks_tests.py M tests/logentries_tests.py M tests/proofreadpage_tests.py M tests/site_tests.py M tests/thanks_tests.py M tox.ini 15 files changed, 283 insertions(+), 119 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/bot.py b/pywikibot/bot.py index 91b252a..19a3f24 100644 --- a/pywikibot/bot.py +++ b/pywikibot/bot.py @@ -1642,6 +1642,8 @@ .format(self.__class__.__name__)) if not isinstance(self.generator, Generator): # to provide close() method + pywikibot.info('wrapping {} type to a Generator type' + .format(type(self.generator).__name__)) self.generator = (item for item in self.generator) try: for item in self.generator: diff --git a/pywikibot/comms/eventstreams.py b/pywikibot/comms/eventstreams.py index 2f1ce69..eed7350 100644 --- a/pywikibot/comms/eventstreams.py +++ b/pywikibot/comms/eventstreams.py @@ -25,6 +25,7 @@
from pywikibot import Site, Timestamp, config, debug, warning from pywikibot.tools import cached +from pywikibot.tools.collections import GeneratorWrapper
try: @@ -39,36 +40,73 @@ "install it with 'pip install "requests>=2.20.1"'\n")
-class EventStreams: +class EventStreams(GeneratorWrapper):
- """Basic EventStreams iterator class for Server-Sent Events (SSE) protocol. + """Generator class for Server-Sent Events (SSE) protocol.
It provides access to arbitrary streams of data including recent changes.
Usage:
>>> stream = EventStreams(streams='recentchange') - >>> stream.register_filter(type='edit', wiki='wikidatawiki') - >>> change = next(iter(stream)) - >>> print('{type} on page {title} by {user}.'.format_map(change)) - edit on page Q32857263 by XXN-bot. - >>> change - {'comment': '/* wbcreateclaim-create:1| */ [[Property:P31]]: [[Q4167836]]', - 'wiki': 'wikidatawiki', 'type': 'edit', 'server_name': 'www.wikidata.org', - 'server_script_path': '/w', 'namespace': 0, 'title': 'Q32857263', - 'bot': True, 'server_url': 'https://www.wikidata.org', - 'length': {'new': 1223, 'old': 793}, - 'meta': {'domain': 'www.wikidata.org', 'partition': 0, - 'uri': 'https://www.wikidata.org/wiki/Q32857263', - 'offset': 288986585, 'topic': 'eqiad.mediawiki.recentchange', - 'request_id': '1305a006-8204-4f51-a27b-0f2df58289f4', - 'schema_uri': 'mediawiki/recentchange/1', - 'dt': '2017-07-13T10:55:31+00:00', - 'id': 'ca13742b-67b9-11e7-935d-141877614a33'}, - 'user': 'XXN-bot', 'timestamp': 1499943331, 'patrolled': True, - 'id': 551158959, 'minor': False, - 'revision': {'new': 518751558, 'old': 517180066}} + >>> stream.register_filter(type='edit', wiki='wikidatawiki', bot=True) + >>> change = next(stream) + >>> msg = '{type} on page {title} by {user}.'.format_map(change) + >>> print(msg) # doctest: +SKIP + edit on page Q2190037 by KrBot. + >>> from pprint import pprint + >>> pprint(change, width=75) # doctest: +SKIP + {'$schema': '/mediawiki/recentchange/1.0.0', + 'bot': True, + 'comment': '/* wbsetreference-set:2| */ [[Property:P10585]]: 96FPN, см. ' + '/ see [[Template:Autofix|autofix]] на / on [[Property ' + 'talk:P356]]', + 'id': 1728475074, + 'length': {'new': 8871, 'old': 8871}, + 'meta': {'domain': 'www.wikidata.org', + 'dt': '2022-07-12T17:54:15Z', + 'id': '2cdec62f-a2b3-49b8-9a52-85a42236fb99', + 'offset': 4000957901, + 'partition': 0, + 'request_id': 'f7896e77-fd2b-4a95-a9e4-44c1e3ad818b', + 'stream': 'mediawiki.recentchange', + 'topic': 'eqiad.mediawiki.recentchange', + 'uri': 'https://www.wikidata.org/wiki/Q2190037%27%7D, + 'minor': False, + 'namespace': 0, + 'parsedcomment': '\u200e<span dir="auto"><span ' + 'class="autocomment">Изменена ссылка на заявление: ' + '</span></span> <a href="/wiki/Property:P10585" ' + 'title="Property:P10585">Property:P10585</a>: 96FPN, ' + 'см. / see <a href="/wiki/Template:Autofix" ' + 'title="Template:Autofix">autofix</a> на / on <a ' + 'href="/wiki/Property_talk:P356" title="Property ' + 'talk:P356">Property talk:P356</a>', + 'patrolled': True, + 'revision': {'new': 1676015019, 'old': 1675697125}, + 'server_name': 'www.wikidata.org', + 'server_script_path': '/w', + 'server_url': 'https://www.wikidata.org', + 'timestamp': 1657648455, + 'title': 'Q2190037', + 'type': 'edit', + 'user': 'KrBot', + 'wiki': 'wikidatawiki'} + >>> pprint(next(stream), width=75) # doctest: +ELLIPSIS + {'$schema': '/mediawiki/recentchange/1.0.0', + 'bot': True, + ... + 'server_name': 'www.wikidata.org', + 'server_script_path': '/w', + 'server_url': 'https://www.wikidata.org', + ... + 'type': 'edit', + 'user': '...', + 'wiki': 'wikidatawiki'} >>> del stream + + .. versionchanged:: 7.6 + subclassed from :class:`pywikibot.tools.collections.GeneratorWrapper` """
def __init__(self, **kwargs) -> None: @@ -273,8 +311,13 @@ return True return any(function(data) for function in self.filter['any'])
- def __iter__(self): - """Iterator.""" + @property + def generator(self): + """Inner generator. + + .. versionchanged:: 7.6 + changed from iterator method to generator property + """ n = 0 event = None ignore_first_empty_warning = True diff --git a/pywikibot/data/api/__init__.py b/pywikibot/data/api/__init__.py index 7f5b15c..dbd6b4c 100644 --- a/pywikibot/data/api/__init__.py +++ b/pywikibot/data/api/__init__.py @@ -10,6 +10,7 @@
from pywikibot.comms import http from pywikibot.data.api._generators import ( + APIGeneratorBase, APIGenerator, ListGenerator, LogEntryListGenerator, @@ -25,6 +26,7 @@ from pywikibot.family import SubdomainFamily
__all__ = ( + 'APIGeneratorBase', 'APIGenerator', 'CachedRequest', 'ListGenerator', diff --git a/pywikibot/data/api/_generators.py b/pywikibot/data/api/_generators.py index 2f35937..e3a1f21 100644 --- a/pywikibot/data/api/_generators.py +++ b/pywikibot/data/api/_generators.py @@ -1,15 +1,23 @@ -"""Objects representing API/Query generators.""" +"""Objects representing API/Query generators. + +.. versionchanged:: 7.6 + All Objects were changed from Iterable object to a Generator object. + They are subclassed from + :class:`pywikibot.tools.collections.GeneratorWrapper` +""" # # (C) Pywikibot team, 2008-2022 # # Distributed under the terms of the MIT license. # +from abc import abstractmethod, ABC from typing import Union from warnings import warn
import pywikibot from pywikibot import config from pywikibot.exceptions import Error, InvalidTitleError, UnsupportedPageError +from pywikibot.tools.collections import GeneratorWrapper
__all__ = ( 'APIGenerator', @@ -22,9 +30,13 @@ )
-class _RequestWrapper: +class APIGeneratorBase(ABC):
- """A wrapper class to handle the usage of the ``parameters`` parameter.""" + """A wrapper class to handle the usage of the ``parameters`` parameter. + + .. versionchanged:: 7.6 + renamed from _RequestWrapper + """
def _clean_kwargs(self, kwargs, **mw_api_args): """Clean kwargs, define site and request class.""" @@ -39,19 +51,28 @@ kwargs['parameters'].update(mw_api_args) return kwargs
+ @abstractmethod def set_maximum_items(self, value: Union[int, str, None]) -> None: + """Set the maximum number of items to be retrieved from the wiki. + + .. versionadded:: 7.1 + .. versionchanged:: 7.6 + become an abstract method + """ raise NotImplementedError
-class APIGenerator(_RequestWrapper): +class APIGenerator(APIGeneratorBase, GeneratorWrapper):
- """ - Iterator that handle API responses containing lists. + """Generator that handle API responses containing lists.
- The iterator will iterate each item in the query response and use the - continue request parameter to retrieve the next portion of items + The generator will iterate each item in the query response and use + the continue request parameter to retrieve the next portion of items automatically. If the limit attribute is set, the iterator will stop after iterating that many values. + + .. versionchanged:: 7.6 + subclassed from :class:`pywikibot.tools.collections.GeneratorWrapper` """
def __init__( @@ -121,11 +142,15 @@ pywikibot.debug('{}: Set limit (maximum_items) to {}.' .format(type(self).__name__, self.limit))
- def __iter__(self): + @property + def generator(self): """ Submit request and iterate the response.
Continues response as needed until limit (if defined) is reached. + + .. versionchanged:: 7.6 + changed from iterator method to generator property """ offset = self.starting_offset n = 0 @@ -154,23 +179,26 @@ break
-class QueryGenerator(_RequestWrapper): +class QueryGenerator(APIGeneratorBase, GeneratorWrapper):
- """ - Base class for iterators that handle responses to API action=query. + """Base class for generators that handle responses to API action=query.
- By default, the iterator will iterate each item in the query response, - and use the (query-)continue element, if present, to continue iterating as - long as the wiki returns additional values. However, if the iterator's - limit attribute is set to a positive int, the iterator will stop after - iterating that many values. If limit is negative, the limit parameter - will not be passed to the API at all. + By default, the generator will iterate each item in the query + response, and use the (query-)continue element, if present, to + continue iterating as long as the wiki returns additional values. + However, if the generators's limit attribute is set to a positive + int, the generators will stop after iterating that many values. If + limit is negative, the limit parameter will not be passed to the API + at all.
- Most common query types are more efficiently handled by subclasses, but - this class can be used directly for custom queries and miscellaneous - types (such as "meta=...") that don't return the usual list of pages or - links. See the API documentation for specific query options. + Most common query types are more efficiently handled by subclasses, + but this class can be used directly for custom queries and + miscellaneous types (such as "meta=...") that don't return the usual + list of pages or links. See the API documentation for specific query + options.
+ .. versionchanged:: 7.6 + subclassed from :class:`pywikibot.tools.collections.GeneratorWrapper` """
# Should results be filtered during iteration according to set_namespace? @@ -560,11 +588,14 @@ raise RuntimeError( 'QueryGenerator._extract_results reached the limit')
- def __iter__(self): + @property + def generator(self): """Submit request and iterate the response based on self.resultkey.
Continues response as needed until limit (if any) is reached.
+ .. versionchanged:: 7.6 + changed from iterator method to generator property """ previous_result_had_data = True prev_limit = new_limit = None @@ -627,12 +658,11 @@
class PageGenerator(QueryGenerator):
- """Iterator for response to a request of type action=query&generator=foo. + """Generator for response to a request of type action=query&generator=foo.
- This class can be used for any of the query types that are listed in the - API documentation as being able to be used as a generator. Instances of - this class iterate Page objects. - + This class can be used for any of the query types that are listed in + the API documentation as being able to be used as a generator. + Instances of this class iterate Page objects. """
def __init__( @@ -700,17 +730,16 @@
class PropertyGenerator(QueryGenerator):
- """Iterator for queries of type action=query&prop=foo. + """Generator for queries of type action=query&prop=foo.
See the API documentation for types of page properties that can be queried.
- This iterator yields one or more dict object(s) corresponding - to each "page" item(s) from the API response; the calling module has to + This generator yields one or more dict object(s) corresponding to + each "page" item(s) from the API response; the calling module has to decide what to do with the contents of the dict. There will be one dict for each page queried via a titles= or ids= parameter (which must be supplied when instantiating this class). - """
def __init__(self, prop: str, **kwargs) -> None: @@ -732,10 +761,15 @@ """The requested property names.""" return self._props
- def __iter__(self): - """Yield results.""" + @property + def generator(self): + """Yield results. + + .. versionchanged:: 7.6 + changed from iterator method to generator property + """ self._previous_dicts = {} - yield from super().__iter__() + yield from super().generator yield from self._previous_dicts.values()
def _extract_results(self, resultdata): @@ -775,18 +809,18 @@
class ListGenerator(QueryGenerator):
- """Iterator for queries of type action=query&list=foo. + """Generator for queries of type action=query&list=foo.
- See the API documentation for types of lists that can be queried. Lists - include both site-wide information (such as 'allpages') and page-specific - information (such as 'backlinks'). + See the API documentation for types of lists that can be queried. + Lists include both site-wide information (such as 'allpages') and + page-specific information (such as 'backlinks').
- This iterator yields a dict object for each member of the list returned - by the API, with the format of the dict depending on the particular list - command used. For those lists that contain page information, it may be - easier to use the PageGenerator class instead, as that will convert the - returned information into a Page object. - + This generator yields a dict object for each member of the list + returned by the API, with the format of the dict depending on the + particular list command used. For those lists that contain page + information, it may be easier to use the PageGenerator class + instead, as that will convert the returned information into a Page + object. """
def __init__(self, listaction: str, **kwargs) -> None: @@ -804,8 +838,7 @@
class LogEntryListGenerator(ListGenerator):
- """ - Iterator for queries of list 'logevents'. + """Generator for queries of list 'logevents'.
Yields LogEntry objects instead of dicts. """ diff --git a/pywikibot/login.py b/pywikibot/login.py index d14195e..2bbbebd 100644 --- a/pywikibot/login.py +++ b/pywikibot/login.py @@ -132,7 +132,7 @@
try: data = self.site.allusers(start=main_username, total=1) - user = next(iter(data)) + user = next(data) except APIError as e: if e.code == 'readapidenied': pywikibot.warning("Could not check user '{}' exists on {}" diff --git a/pywikibot/page/_user.py b/pywikibot/page/_user.py index b095775..22c4385 100644 --- a/pywikibot/page/_user.py +++ b/pywikibot/page/_user.py @@ -320,7 +320,7 @@ :return: last user log entry :rtype: LogEntry or None """ - return next(iter(self.logevents(total=1)), None) + return next(self.logevents(total=1), None)
def contributions(self, total: int = 500, **kwargs) -> tuple: """ diff --git a/pywikibot/tools/collections.py b/pywikibot/tools/collections.py index e06d070..a6934dc 100644 --- a/pywikibot/tools/collections.py +++ b/pywikibot/tools/collections.py @@ -5,15 +5,28 @@ # Distributed under the terms of the MIT license. # import collections -from collections.abc import Container, Iterable, Iterator, Mapping, Sized + +from abc import abstractmethod, ABC +from collections.abc import ( + Container, + Generator, + Iterable, + Iterator, + Mapping, + Sized, +) from contextlib import suppress from itertools import chain +from typing import Any + +from pywikibot.backports import Generator as GeneratorType
__all__ = ( 'CombinedError', 'DequeGenerator', 'EmptyDefault', + 'GeneratorWrapper', 'SizedKeyCollection', 'EMPTY_DEFAULT', ) @@ -193,3 +206,96 @@ result = '{}({})'.format(self.__class__.__name__, items) self.extend(items) return result + + +class GeneratorWrapper(ABC, Generator): + + """A Generator base class which wraps the internal `generator` property. + + This generator iterator also has :python:`generator.close() + <reference/expressions.html#generator.close>` mixin method and it can + be used as Iterable and Iterator as well. + + .. versionadded:: 7.6 + + Example: + + >>> class Gen(GeneratorWrapper): + ... @property + ... def generator(self): + ... return (c for c in 'Pywikibot') + >>> gen = Gen() + >>> next(gen) # can be used as Iterator ... + 'P' + >>> next(gen) + 'y' + >>> ''.join(c for c in gen) # ... or as Iterable + 'wikibot' + >>> next(gen) # the generator is exhausted ... + Traceback (most recent call last): + ... + StopIteration + >>> gen.restart() # ... but can be restarted + >>> next(gen) + next(gen) + 'Py' + >>> gen.close() # the generator may be closed + >>> next(gen) + Traceback (most recent call last): + ... + StopIteration + >>> gen.restart() # restart a closed generator + >>> # also send() and throw() works + >>> gen.send(None) + gen.send(None) + 'Py' + >>> gen.throw(RuntimeError('Foo')) + Traceback (most recent call last): + ... + RuntimeError: Foo + + .. seealso:: :pep:`342` + """ + + @abstractmethod + def generator(self) -> GeneratorType[Any, Any, Any]: + """Abstract generator property.""" + return iter(()) + + def send(self, value: Any) -> Any: + """Return next yielded value from generator or raise StopIteration. + + The `value` parameter is ignored yet; usually it should be ``None``. + If the wrapped generator property exits without yielding another + value this method raises `StopIteration`. The send method works + like the `next`function with a GeneratorWrapper instance as + parameter. + + Refer :python:`generator.send() + <reference/expressions.html#generator.send>` for its usage. + + :raises TypeError: generator property is not a generator + """ + if not isinstance(self.generator, GeneratorType): + raise TypeError('generator property is not a generator but {}' + .format(type(self.generator).__name__)) + if not hasattr(self, '_started_gen'): + # start the generator + self._started_gen = self.generator + return next(self._started_gen) + + def throw(self, typ: Exception, val=None, tb=None) -> None: + """Raise an exception inside the wrapped generator. + + Refer :python:`generator.throw() + <reference/expressions.html#generator.throw>` for various + parameter usage. + + :raises RuntimeError: No generator started + """ + if not hasattr(self, '_started_gen'): + raise RuntimeError('No generator was started') + self._started_gen.throw(typ, val, tb) + + def restart(self) -> None: + """Restart the generator.""" + with suppress(AttributeError): + del self._started_gen diff --git a/scripts/checkimages.py b/scripts/checkimages.py index ef83170..0113c9b 100755 --- a/scripts/checkimages.py +++ b/scripts/checkimages.py @@ -833,7 +833,7 @@
site = pywikibot.Site('commons') commons_image_with_this_hash = next( - iter(site.allimages(sha1=hash_found, total=1)), None) + site.allimages(sha1=hash_found, total=1), None) if commons_image_with_this_hash: service_template = pywikibot.translate(self.site, SERVICE_TEMPLATES) diff --git a/tests/api_tests.py b/tests/api_tests.py index be3a6e4..30b629a 100755 --- a/tests/api_tests.py +++ b/tests/api_tests.py @@ -500,6 +500,7 @@ with self.subTest(amount=i): self.gen.set_maximum_items(i) self.assertPageTitlesEqual(self.gen, self.titles[:i]) + self.gen.restart()
def test_limit_zero(self): """Test that a limit of zero is the same as limit None.""" diff --git a/tests/flow_thanks_tests.py b/tests/flow_thanks_tests.py index b4a83f5..acdd8cb 100755 --- a/tests/flow_thanks_tests.py +++ b/tests/flow_thanks_tests.py @@ -47,13 +47,7 @@ post.thank() log_entries = site.logevents(logtype='thanks', total=5, page=user, start=before_time, reverse=True) - try: - next(iter(log_entries)) - except StopIteration: - found_log = False - else: - found_log = True - self.assertTrue(found_log) + self.assertTrue(bool(next(log_entries, None)))
def test_self_thank(self): """Test that thanking one's own Flow post causes an error.""" diff --git a/tests/logentries_tests.py b/tests/logentries_tests.py index a996cbd..e4468c3 100755 --- a/tests/logentries_tests.py +++ b/tests/logentries_tests.py @@ -19,6 +19,7 @@ ) from tests import unittest_print from tests.aspects import MetaTestCaseClass, TestCase +from tests.utils import skipping
class TestLogentriesBase(TestCase): @@ -64,10 +65,10 @@ # MW versions and otherwise it might not be visible that the test # isn't run on an older wiki. self.assertLess(self.site.mw_version, '1.25') - try: - le = next(iter(self.site.logevents(logtype=logtype, total=1))) - except StopIteration: - self.skipTest('No entry found for {!r}'.format(logtype)) + + with skipping(StopIteration, + msg='No entry found for {!r}'.format(logtype)): + le = next(self.site.logevents(logtype=logtype, total=1)) return le
def _test_logevent(self, logtype): @@ -271,11 +272,11 @@ """Test equality of LogEntry instances.""" site = self.get_site('dewp') other_site = self.get_site('tewp') - gen1 = iter(site.logevents(reverse=True, total=2)) - gen2 = iter(site.logevents(reverse=True, total=2)) + gen1 = site.logevents(reverse=True, total=2) + gen2 = site.logevents(reverse=True, total=2) le1 = next(gen1) le2 = next(gen2) - le3 = next(iter(other_site.logevents(reverse=True, total=1))) + le3 = next(other_site.logevents(reverse=True, total=1)) le4 = next(gen1) le5 = next(gen2) self.assertEqual(le1, le2) diff --git a/tests/proofreadpage_tests.py b/tests/proofreadpage_tests.py index 8f99e79..ad2f675 100755 --- a/tests/proofreadpage_tests.py +++ b/tests/proofreadpage_tests.py @@ -297,7 +297,7 @@ rvgen.set_maximum_items(-1) # suppress use of rvlimit parameter
try: - pagedict = next(iter(rvgen)) + pagedict = next(rvgen) loaded_text = pagedict.get('revisions')[0].get('*') except (StopIteration, TypeError, KeyError, ValueError, IndexError): loaded_text = '' diff --git a/tests/site_tests.py b/tests/site_tests.py index fcb9338..4d70509 100755 --- a/tests/site_tests.py +++ b/tests/site_tests.py @@ -1075,10 +1075,10 @@ with skipping( StopIteration, msg='No images on the main page of site {!r}'.format(mysite)): - imagepage = next(iter(page.imagelinks())) # 1st image of page + imagepage = next(page.imagelinks()) # 1st image of page
- pywikibot.output('site_tests.TestImageUsage found {} on {}' - .format(imagepage, page)) + unittest_print('site_tests.TestImageUsage found {} on {}' + .format(imagepage, page))
self.__class__._image_page = imagepage return imagepage @@ -1234,19 +1234,6 @@
"""Test recentchanges method."""
- @classmethod - def setUpClass(cls): - """Test up test class.""" - super().setUpClass() - mysite = cls.get_site() - try: - # 1st image on main page - imagepage = next(iter(mysite.allimages())) - except StopIteration: - unittest_print('No images on site {!r}'.format(mysite)) - imagepage = None - cls.imagepage = imagepage - def test_basic(self): """Test the site.recentchanges() method.""" mysite = self.site @@ -2719,7 +2706,7 @@ """Test properties.""" gen = self.site.filearchive(prop=['sha1', 'size', 'user'], total=1) self.assertIn('faprop=sha1|size|user', str(gen.request)) - item = next(iter(gen)) + item = next(gen) self.assertIn('sha1', item) self.assertIn('size', item) self.assertIn('user', item) @@ -2730,8 +2717,8 @@ gen2 = self.site.filearchive(reverse=True, total=1) self.assertNotIn('fadir=', str(gen1.request)) self.assertIn('fadir=descending', str(gen2.request)) - fa1 = next(iter(gen1)) - fa2 = next(iter(gen2)) + fa1 = next(gen1) + fa2 = next(gen2) self.assertLess(fa1['name'], fa2['name'])
def test_filearchive_start(self): @@ -2739,7 +2726,7 @@ gen = self.site.filearchive(start='py', end='wiki', total=1) self.assertIn('fafrom=py', str(gen.request)) self.assertIn('fato=wiki', str(gen.request)) - item = next(iter(gen)) + item = next(gen) self.assertGreaterEqual(item['name'], 'Py')
def test_filearchive_sha1(self): @@ -2747,7 +2734,7 @@ sha1 = '0d5a00aa774100408e60da09f5fb21f253b366f1' gen = self.site.filearchive(sha1=sha1, prop='sha1', total=1) self.assertIn('fasha1=' + sha1, str(gen.request)) - item = next(iter(gen)) + item = next(gen) self.assertEqual(item['sha1'], sha1)
diff --git a/tests/thanks_tests.py b/tests/thanks_tests.py index c670392..08a3f3e 100755 --- a/tests/thanks_tests.py +++ b/tests/thanks_tests.py @@ -46,13 +46,7 @@ site.thank_revision(revid, source='pywikibot test') log_entries = site.logevents(logtype='thanks', total=5, page=user, start=before_time, reverse=True) - try: - next(iter(log_entries)) - except StopIteration: - found_log = False - else: - found_log = True - self.assertTrue(found_log) + self.assertTrue(bool(next(log_entries, None)))
def test_self_thank(self): """Test that thanking oneself causes an error. diff --git a/tox.ini b/tox.ini index bfde443..226d907 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ hacking-py37
[params] -doctest_skip = --ignore-files=(eventstreams|gui).py +doctest_skip = --ignore-files=gui.py generate_user_files = -W error::UserWarning -m pwb generate_user_files -family:wikipedia -lang:test -v
[testenv] @@ -70,6 +70,7 @@ nosetests -v --with-doctest pywikibot {[params]doctest_skip} deps = nose + .[eventstreams] .[memento] .[mwparserfromhell] .[mysql]
pywikibot-commits@lists.wikimedia.org