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