From 8d0650cdd3921066b310f9cbbea33ef1ae274ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Michael=20O=2E=20Hegg=C3=B8?= <danmichaelo@gmail.com> Date: Sat, 18 Jul 2015 01:01:11 +0200 Subject: [PATCH] [#52] Python 3 compatible iterator methods - Factor out parse_timestamp - Use `iterkeys`, `itervalues`, `iteritems` and `next` from six - Use __next__ for Python 3.x and next for Python2.x --- mwclient/client.py | 15 +++++---------- mwclient/listing.py | 21 +++++++++++++-------- mwclient/page.py | 14 +++++++------- mwclient/util.py | 7 +++++++ tests/test_util.py | 23 +++++++++++++++++++++++ 5 files changed, 55 insertions(+), 25 deletions(-) create mode 100644 mwclient/util.py create mode 100644 tests/test_util.py diff --git a/mwclient/client.py b/mwclient/client.py index 8e048f5..8e11b60 100644 --- a/mwclient/client.py +++ b/mwclient/client.py @@ -5,6 +5,7 @@ import sys import weakref import logging from six import text_type +import six try: # Python 2.7+ @@ -34,12 +35,6 @@ __ver__ = '0.7.2' log = logging.getLogger(__name__) -def parse_timestamp(t): - if t == '0000-00-00T00:00:00Z': - return (0, 0, 0, 0, 0, 0, 0, 0) - return time.strptime(t, '%Y-%m-%dT%H:%M:%SZ') - - class WaitToken(object): def __init__(self): @@ -125,7 +120,7 @@ class Site(object): # Extract site info self.site = meta['query']['general'] - self.namespaces = dict(((i['id'], i.get('*', '')) for i in meta['query']['namespaces'].itervalues())) + self.namespaces = dict(((i['id'], i.get('*', '')) for i in six.itervalues(meta['query']['namespaces']))) self.writeapi = 'writeapi' in self.site # Determine version @@ -235,8 +230,8 @@ class Site(object): @staticmethod def _query_string(*args, **kwargs): kwargs.update(args) - qs1 = [(k, v) for k, v in kwargs.iteritems() if k not in ('wpEditToken', 'token')] - qs2 = [(k, v) for k, v in kwargs.iteritems() if k in ('wpEditToken', 'token')] + qs1 = [(k, v) for k, v in six.iteritems(kwargs) if k not in ('wpEditToken', 'token')] + qs2 = [(k, v) for k, v in six.iteritems(kwargs) if k in ('wpEditToken', 'token')] return OrderedDict(qs1 + qs2) def raw_call(self, script, data, files=None, retry_on_error=True): @@ -456,7 +451,7 @@ class Site(object): title = 'Test' info = self.api('query', titles=title, prop='info', intoken=type) - for i in info['query']['pages'].itervalues(): + for i in six.itervalues(info['query']['pages']): if i['title'] == title: self.tokens[type] = i['%stoken' % type] diff --git a/mwclient/listing.py b/mwclient/listing.py index 1c83a9e..e10af95 100644 --- a/mwclient/listing.py +++ b/mwclient/listing.py @@ -1,6 +1,7 @@ -import client import page +import six from six import text_type +from mwclient.util import parse_timestamp class List(object): @@ -31,15 +32,15 @@ class List(object): def __iter__(self): return self - def next(self, full=False): + def __next__(self, full=False): if self.max_items is not None: if self.count >= self.max_items: raise StopIteration try: - item = self._iter.next() + item = six.next(self._iter) self.count += 1 if 'timestamp' in item: - item['timestamp'] = client.parse_timestamp(item['timestamp']) + item['timestamp'] = parse_timestamp(item['timestamp']) if full: return item @@ -56,8 +57,12 @@ class List(object): self.load_chunk() return List.next(self, full=full) + def next(self, full=False): + """ For Python 2.x support """ + return self.__next__(full) + def load_chunk(self): - data = self.site.api('query', (self.generator, self.list_name), *[(text_type(k), v) for k, v in self.args.iteritems()]) + data = self.site.api('query', (self.generator, self.list_name), *[(text_type(k), v) for k, v in six.iteritems(self.args)]) if not data: # Non existent page raise StopIteration @@ -80,7 +85,7 @@ class List(object): elif type(data['query'][self.result_member]) is list: self._iter = iter(data['query'][self.result_member]) else: - self._iter = data['query'][self.result_member].itervalues() + self._iter = six.itervalues(data['query'][self.result_member]) def __repr__(self): return "<List object '%s' for %s>" % (self.list_name, self.site) @@ -88,7 +93,7 @@ class List(object): @staticmethod def generate_kwargs(_prefix, *args, **kwargs): kwargs.update(args) - for key, value in kwargs.iteritems(): + for key, value in six.iteritems(kwargs): if value is not None and value is not False: yield _prefix + key, value @@ -223,7 +228,7 @@ class PageProperty(List): self.generator = 'prop' def set_iter(self, data): - for page in data['query']['pages'].itervalues(): + for page in six.itervalues(data['query']['pages']): if page['title'] == self.page.name: self._iter = iter(page.get(self.list_name, ())) return diff --git a/mwclient/page.py b/mwclient/page.py index c0c3aec..554536c 100644 --- a/mwclient/page.py +++ b/mwclient/page.py @@ -1,11 +1,11 @@ -import client import errors import listing - +import six from six.moves import urllib from six import text_type import time import warnings +from mwclient.util import parse_timestamp class Page(object): @@ -19,9 +19,9 @@ class Page(object): if not info: if extra_properties: - prop = 'info|' + '|'.join(extra_properties.iterkeys()) + prop = 'info|' + '|'.join(six.iterkeys(extra_properties)) extra_props = [] - [extra_props.extend(extra_prop) for extra_prop in extra_properties.itervalues()] + [extra_props.extend(extra_prop) for extra_prop in six.itervalues(extra_properties)] else: prop = 'info' extra_props = () @@ -32,7 +32,7 @@ class Page(object): else: info = self.site.api('query', prop=prop, titles=name, inprop='protection', *extra_props) - info = info['query']['pages'].itervalues().next() + info = six.next(six.itervalues(info['query']['pages'])) self._info = info self.namespace = info.get('ns', 0) @@ -42,7 +42,7 @@ class Page(object): else: self.page_title = self.name - self.touched = client.parse_timestamp(info.get('touched', '0000-00-00T00:00:00Z')) + self.touched = parse_timestamp(info.get('touched')) self.revision = info.get('lastrevid', 0) self.exists = 'missing' not in info self.length = info.get('length') @@ -214,7 +214,7 @@ class Page(object): # 'newtimestamp' is not included if no change was made if 'newtimestamp' in result['edit'].keys(): - self.last_rev_time = client.parse_timestamp(result['edit'].get('newtimestamp')) + self.last_rev_time = parse_timestamp(result['edit'].get('newtimestamp')) return result['edit'] def handle_edit_error(self, e, summary): diff --git a/mwclient/util.py b/mwclient/util.py new file mode 100644 index 0000000..bcd7b60 --- /dev/null +++ b/mwclient/util.py @@ -0,0 +1,7 @@ +import time + + +def parse_timestamp(t): + if t is None or t == '0000-00-00T00:00:00Z': + return (0, 0, 0, 0, 0, 0, 0, 0, 0) + return time.strptime(t, '%Y-%m-%dT%H:%M:%SZ') diff --git a/tests/test_util.py b/tests/test_util.py new file mode 100644 index 0000000..19e7fa1 --- /dev/null +++ b/tests/test_util.py @@ -0,0 +1,23 @@ +# encoding=utf-8 +from __future__ import print_function +import unittest +import time +from mwclient.util import parse_timestamp + +if __name__ == "__main__": + print() + print("Note: Running in stand-alone mode. Consult the README") + print(" (section 'Contributing') for advice on running tests.") + print() + + +class TestUtil(unittest.TestCase): + + def test_parse_empty_timestamp(self): + assert (0, 0, 0, 0, 0, 0, 0, 0, 0) == parse_timestamp('0000-00-00T00:00:00Z') + + def test_parse_nonempty_timestamp(self): + assert time.struct_time([2015, 1, 2, 20, 18, 36, 4, 2, -1]) == parse_timestamp('2015-01-02T20:18:36Z') + +if __name__ == '__main__': + unittest.main() -- GitLab