XZise has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/203051

Change subject: [FIX] Use str for __repr__ in both Python versions
......................................................................

[FIX] Use str for __repr__ in both Python versions

This returns str in both Python versions (which essentially means bytes
in Python 2 and unicode in Python 3). Because of encoding problems it
forces some unicode strings back into bytes with Python 2. As repr()
should always return str it needs to be decoded when it's added to a
unicode. And with 1e54a7d6 a lot of those str have been converted into
unicode so it'll now decode it (while before it inserting a bytes into
bytes) using ASCII which is not always possible. So it's forcing strings
which have been bytes before 1e54a7d6 into bytes again by using 'str()'.

This only applies to Python 2 and it will only change some parts in
Python 3 whenever repr() returned bytes instead of str.

Bug: T95499
Change-Id: Ic7c96194c0d975033923ea43af19f9f40d331321
---
M pywikibot/__init__.py
M pywikibot/bot.py
M pywikibot/comms/rcstream.py
M pywikibot/data/api.py
M pywikibot/data/wikidataquery.py
M pywikibot/diff.py
M pywikibot/family.py
M pywikibot/i18n.py
M pywikibot/page.py
M pywikibot/site.py
M pywikibot/textlib.py
M pywikibot/tools/__init__.py
M pywikibot/userinterfaces/transliteration.py
M pywikibot/userinterfaces/win32_unicode.py
M scripts/archivebot.py
M scripts/maintenance/cache.py
M tests/__init__.py
M tests/aspects.py
M tests/dry_api_tests.py
M tests/pwb/print_locals.py
M tests/script_tests.py
M tests/site_tests.py
M tests/utils.py
M tests/wikibase_tests.py
24 files changed, 121 insertions(+), 102 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/pywikibot/core 
refs/changes/51/203051/1

diff --git a/pywikibot/__init__.py b/pywikibot/__init__.py
index db96621..4f88afc 100644
--- a/pywikibot/__init__.py
+++ b/pywikibot/__init__.py
@@ -194,7 +194,7 @@
             return newdt
 
 
-class Coordinate(object):
+class Coordinate(UnicodeMixin):
 
     """
     Class for handling and storing Coordinates.
@@ -242,7 +242,7 @@
         else:
             self.site = site
 
-    def __repr__(self):
+    def _repr(self):
         string = 'Coordinate(%s, %s' % (self.lat, self.lon)
         if self.globe != 'earth':
             string += ', globe="%s"' % self.globe
@@ -322,7 +322,7 @@
         raise NotImplementedError
 
 
-class WbTime(object):
+class WbTime(UnicodeMixin):
 
     """A Wikibase time representation."""
 
@@ -448,7 +448,7 @@
     def __eq__(self, other):
         return self.__dict__ == other.__dict__
 
-    def __repr__(self):
+    def _repr(self):
         return u"WbTime(year=%(year)d, month=%(month)d, day=%(day)d, " \
             u"hour=%(hour)d, minute=%(minute)d, second=%(second)d, " \
             u"precision=%(precision)d, before=%(before)d, after=%(after)d, " \
@@ -456,7 +456,7 @@
             % self.__dict__
 
 
-class WbQuantity(object):
+class WbQuantity(UnicodeMixin):
 
     """A Wikibase quantity representation."""
 
@@ -517,7 +517,7 @@
     def __eq__(self, other):
         return self.__dict__ == other.__dict__
 
-    def __repr__(self):
+    def _repr(self):
         return (u"WbQuantity(amount=%(amount)s, upperBound=%(upperBound)s, "
                 u"lowerBound=%(lowerBound)s, unit=%(unit)s)" % self.__dict__)
 
diff --git a/pywikibot/bot.py b/pywikibot/bot.py
index 580f760..5f03eb2 100644
--- a/pywikibot/bot.py
+++ b/pywikibot/bot.py
@@ -1025,7 +1025,7 @@
         """
         if page != self._current_page:
             self._current_page = page
-            msg = u'Working on %r' % page.title()
+            msg = u'Working on %s' % page.title()
             if config.colorized_output:
                 log(msg)
                 stdout(u'\n\n>>> \03{lightpurple}%s\03{default} <<<'
diff --git a/pywikibot/comms/rcstream.py b/pywikibot/comms/rcstream.py
index d024bc8..c2829df 100644
--- a/pywikibot/comms/rcstream.py
+++ b/pywikibot/comms/rcstream.py
@@ -19,11 +19,12 @@
     from Queue import Queue, Empty
 
 from pywikibot.bot import debug, warning
+from pywikibot.tools import UnicodeMixin
 
 _logger = 'pywikibot.rcstream'
 
 
-class RcListenerThread(threading.Thread):
+class RcListenerThread(threading.Thread, UnicodeMixin):
 
     """
     Low-level RC Listener Thread, which reads the RC stream and pushes them to 
it's internal queue.
@@ -76,14 +77,14 @@
         self.count = 0
 
         import socketIO_client
-        debug('Opening connection to %r' % self, _logger)
+        debug(str('Opening connection to %r') % self, _logger)
         self.client = socketIO_client.SocketIO(rchost, rcport)
 
         thread = self
 
         class RCListener(socketIO_client.BaseNamespace):
             def on_change(self, change):
-                debug('Received change %r' % change, _logger)
+                debug(str('Received change %r') % change, _logger)
                 if not thread.running:
                     debug('Thread in shutdown mode; ignoring change.', _logger)
                     return
@@ -91,7 +92,7 @@
                 thread.count += 1
                 thread.queue.put(change)
                 if thread.queue.qsize() > thread.warn_queue_length:
-                    warning('%r queue length exceeded %i'
+                    warning(str('%r queue length exceeded %i')
                             % (thread,
                                thread.warn_queue_length),
                             _logger=_logger)
@@ -102,14 +103,14 @@
                     return
 
             def on_connect(self):
-                debug('Connected to %r; subscribing to %s'
+                debug(str('Connected to %r; subscribing to %s')
                           % (thread, thread.wikihost),
                       _logger)
                 self.emit('subscribe', thread.wikihost)
                 debug('Subscribed to %s' % thread.wikihost, _logger)
 
             def on_reconnect(self):
-                debug('Reconnected to %r' % (thread,), _logger)
+                debug(str('Reconnected to %r') % (thread,), _logger)
                 self.on_connect()
 
         class GlobalListener(socketIO_client.BaseNamespace):
@@ -119,7 +120,7 @@
         self.client.define(RCListener, rcpath)
         self.client.define(GlobalListener)
 
-    def __repr__(self):
+    def _repr(self):
         """Return representation."""
         return "<rcstream for socketio://%s@%s:%s%s>" % (
                self.wikihost, self.rchost, self.rcport, self.rcpath
@@ -130,9 +131,9 @@
         self.running = True
         while self.running:
             self.client.wait(seconds=0.1)
-        debug('Shut down event loop for %r' % self, _logger)
+        debug(str('Shut down event loop for %r') % self, _logger)
         self.client.disconnect()
-        debug('Disconnected %r' % self, _logger)
+        debug(str('Disconnected %r') % self, _logger)
         self.queue.put(None)
 
     def stop(self):
@@ -180,7 +181,7 @@
         total=total
     )
 
-    debug('Starting rcstream thread %r' % rc_thread,
+    debug(str('Starting rcstream thread %r') % rc_thread,
           _logger)
     rc_thread.start()
 
diff --git a/pywikibot/data/api.py b/pywikibot/data/api.py
index 61b34f0..6ed462a 100644
--- a/pywikibot/data/api.py
+++ b/pywikibot/data/api.py
@@ -29,7 +29,9 @@
 
 import pywikibot
 from pywikibot import config, login
-from pywikibot.tools import MediaWikiVersion, deprecated, itergroup, ip
+from pywikibot.tools import (
+    MediaWikiVersion, deprecated, itergroup, ip, UnicodeMixin
+)
 from pywikibot.exceptions import (
     Server504Error, Server414Error, FatalServerError, Error
 )
@@ -43,7 +45,7 @@
     # unless the fix is not backported to py3.x versions that should
     # instead support PWB.
     basestring = (str, )
-    from urllib.parse import urlencode, unquote
+    from urllib.parse import urlencode, unquote_to_bytes as unquote
     unicode = str
 
     from io import BytesIO
@@ -88,7 +90,7 @@
 lagpattern = re.compile(r"Waiting for [\d.]+: (?P<lag>\d+) seconds? lagged")
 
 
-class APIError(Error):
+class APIError(Error, UnicodeMixin):
 
     """The wiki site returned an error message."""
 
@@ -99,12 +101,12 @@
         self.other = kwargs
         self.unicode = unicode(self.__str__())
 
-    def __repr__(self):
+    def _repr(self):
         """Return internal representation."""
         return '{name}("{code}", "{info}", {other})'.format(
             name=self.__class__.__name__, **self.__dict__)
 
-    def __str__(self):
+    def __unicode__(self):
         """Return a string representation."""
         return "%(code)s: %(info)s" % self.__dict__
 
@@ -1025,7 +1027,7 @@
         return 'https'
 
 
-class Request(MutableMapping):
+class Request(MutableMapping, UnicodeMixin):
 
     """A request to a Site's api.php interface.
 
@@ -1146,7 +1148,7 @@
 
             if ip.is_IP(self.site._userinfo['name']):
                 raise Error(u"API write action attempted as IP %r"
-                            % str(self.site._userinfo['name']))
+                            % self.site._userinfo['name'])
 
             if not self.site.user():
                 pywikibot.warning(
@@ -1382,15 +1384,17 @@
         """
         return urlencode(self._encoded_items())
 
-    def __str__(self):
+    def __unicode__(self):
         """Return a string representation."""
+        # It's using unquote_to_bytes in Python 3 which will, like in Python 2,
+        # return a bytes object instead of a str/unicode object
         return unquote(self.site.scriptpath() +
                        '/api.php?' +
-                       self._http_param_string())
+                       self._http_param_string()).decode(self.site.encoding())
 
-    def __repr__(self):
+    def _repr(self):
         """Return internal representation."""
-        return "%s.%s<%s->%r>" % (self.__class__.__module__, 
self.__class__.__name__, self.site, str(self))
+        return "%s.%s<%s->%s>" % (self.__class__.__module__, 
self.__class__.__name__, self.site, self)
 
     def _simulate(self, action):
         """Simulate action."""
@@ -1859,7 +1863,7 @@
             # file not found
             return False
         except Exception as e:
-            pywikibot.output("Could not load cache: %r" % e)
+            pywikibot.output(str('Could not load cache: %r') % e)
             return False
 
     def _write_cache(self, data):
@@ -2063,8 +2067,8 @@
                     self.limited_module = module
                     limited_modules.remove(module)
                     break
-            pywikibot.log('%s: multiple requested query modules support limits'
-                          "; using the first such module '%s' of %r"
+            pywikibot.log(str('%s: multiple requested query modules support '
+                              "limits; using the first such module '%s' of %r")
                           % (self.__class__.__name__, self.limited_module,
                              self.modules))
 
diff --git a/pywikibot/data/wikidataquery.py b/pywikibot/data/wikidataquery.py
index 2af1792..c64a79b 100644
--- a/pywikibot/data/wikidataquery.py
+++ b/pywikibot/data/wikidataquery.py
@@ -23,6 +23,7 @@
 from pywikibot.comms import http
 from pywikibot.page import ItemPage, PropertyPage, Claim
 from pywikibot import config
+from pywikibot.tools import UnicodeMixin
 
 
 def listify(x):
@@ -34,7 +35,7 @@
     return x if isinstance(x, list) else [x]
 
 
-class QuerySet():
+class QuerySet(UnicodeMixin):
 
     """
     A QuerySet represents a set of queries or other query sets.
@@ -111,11 +112,11 @@
 
         return s
 
-    def __repr__(self):
+    def _repr(self):
         return u"QuerySet(%s)" % self
 
 
-class Query():
+class Query(UnicodeMixin):
 
     """
     A query is a single query for the WikidataQuery API.
@@ -233,7 +234,7 @@
         """
         raise NotImplementedError
 
-    def __repr__(self):
+    def _repr(self):
         return u"Query(%s)" % self
 
 
diff --git a/pywikibot/diff.py b/pywikibot/diff.py
index 6e0e38b..1895ff4 100644
--- a/pywikibot/diff.py
+++ b/pywikibot/diff.py
@@ -23,9 +23,10 @@
 
 import pywikibot
 from pywikibot.backports import format_range_unified  # introduced in 2.7.2
+from pywikibot.tools import UnicodeMixin
 
 
-class Hunk(object):
+class Hunk(UnicodeMixin):
 
     """One change hunk between a and b.
 
@@ -163,11 +164,11 @@
         """Turn a into b for this hunk."""
         return self.b[self.b_rng[0]:self.b_rng[1]]
 
-    def __str__(self):
+    def __unicode__(self):
         """Return the diff as plain text."""
         return u''.join(self.diff_plain_text)
 
-    def __repr__(self):
+    def _repr(self):
         """Return a reconstructable representation."""
         # TODO
         return '%s(a, b, %s)' \
diff --git a/pywikibot/family.py b/pywikibot/family.py
index cee0a29..7831d47 100644
--- a/pywikibot/family.py
+++ b/pywikibot/family.py
@@ -28,7 +28,9 @@
 import pywikibot
 
 from pywikibot import config2 as config
-from pywikibot.tools import deprecated, deprecate_arg, 
issue_deprecation_warning
+from pywikibot.tools import (
+    deprecated, deprecate_arg, issue_deprecation_warning, UnicodeMixin
+)
 from pywikibot.exceptions import Error, UnknownFamily, FamilyMaintenanceWarning
 
 logger = logging.getLogger("pywiki.wiki.family")
@@ -38,7 +40,7 @@
 CODE_CHARACTERS = string.ascii_lowercase + string.digits + '-'
 
 
-class Family(object):
+class Family(UnicodeMixin):
 
     """Parent class for all wiki families."""
 
@@ -1237,10 +1239,10 @@
     def __hash__(self):
         return hash(self.name)
 
-    def __str__(self):
+    def __unicode__(self):
         return self.name
 
-    def __repr__(self):
+    def _repr(self):
         return 'Family("%s")' % self.name
 
     def shared_image_repository(self, code):
diff --git a/pywikibot/i18n.py b/pywikibot/i18n.py
index 2fff519..603b888 100644
--- a/pywikibot/i18n.py
+++ b/pywikibot/i18n.py
@@ -491,8 +491,8 @@
                     continue
             if trans is None:
                 raise TranslationError(
-                    "No English translation has been defined "
-                    "for TranslateWiki key %r" % twtitle)
+                    str('No English translation has been defined '
+                        'for TranslateWiki key %r') % twtitle)
     # send the language code back via the given list
     if code_needed:
         code.append(lang)
diff --git a/pywikibot/page.py b/pywikibot/page.py
index 4e29b24..95c3991 100644
--- a/pywikibot/page.py
+++ b/pywikibot/page.py
@@ -271,10 +271,9 @@
         """Return a unicode string representation."""
         return self.title(asLink=True, forceInterwiki=True)
 
-    def __repr__(self):
+    def _repr(self):
         """Return a more complete string representation."""
-        title = 
self.title().encode(config.console_encoding).decode('unicode-escape')
-        return '{0}({1})'.format(self.__class__.__name__, title)
+        return '{0}({1})'.format(self.__class__.__name__, self.title())
 
     def _cmpkey(self):
         """
@@ -2946,8 +2945,8 @@
         if not isinstance(site, pywikibot.site.DataSite):
             raise TypeError("site must be a pywikibot.site.DataSite object")
         if title and ('ns' not in kwargs and 'entity_type' not in kwargs):
-            pywikibot.debug("%s.__init__: %s title %r specified without "
-                            "ns or entity_type"
+            pywikibot.debug(str('%s.__init__: %s title %r specified without '
+                                'ns or entity_type')
                             % (self.__class__.__name__, site, title),
                             layer='wikibase')
 
@@ -2965,8 +2964,8 @@
                 elif site.property_namespace.id == ns:
                     self._namespace = site.property_namespace
                 else:
-                    raise ValueError('%r: Namespace "%d" is not valid'
-                                     % self.site)
+                    raise ValueError(str('%r: Namespace "%d" is not valid')
+                                     % (self.site, ns))
 
         if 'entity_type' in kwargs:
             entity_type = kwargs.pop('entity_type')
@@ -3008,7 +3007,7 @@
             elif self.site.property_namespace.id == ns:
                 self._namespace = self.site.property_namespace
             else:
-                raise ValueError('%r: Namespace "%r" is not valid'
+                raise ValueError(str('%r: Namespace "%r" is not valid')
                                  % (self.site, ns))
 
         # .site forces a parse of the Link title to determine site
@@ -4398,7 +4397,7 @@
 
     def __repr__(self):
         """Return a more complete string representation."""
-        return "pywikibot.page.Link(%r, %r)" % (self.title, self.site)
+        return str('pywikibot.page.Link(%r, %r)') % (self.title, self.site)
 
     def parse_site(self):
         """Parse only enough text to determine which site the link points to.
diff --git a/pywikibot/site.py b/pywikibot/site.py
index 96b62a7..cdf9779 100644
--- a/pywikibot/site.py
+++ b/pywikibot/site.py
@@ -84,7 +84,7 @@
     """Page cannot be reserved for writing due to existing lock."""
 
 
-class LoginStatus(object):
+class LoginStatus(UnicodeMixin):
 
     """Enum for Login statuses.
 
@@ -110,14 +110,14 @@
         for key, value in cls.__dict__.items():
             if key == key.upper() and value == search_value:
                 return key
-        raise KeyError("Value %r could not be found in this enum"
+        raise KeyError(str('Value %r could not be found in this enum')
                        % search_value)
 
     def __init__(self, state):
         """Constructor."""
         self.state = state
 
-    def __repr__(self):
+    def _repr(self):
         """Return internal representation."""
         return 'LoginStatus(%s)' % (LoginStatus.name(self.state))
 
@@ -364,7 +364,7 @@
         else:
             kwargs = ''
 
-        return '%s(id=%d, custom_name=%r, canonical_name=%r, aliases=%r%s)' \
+        return str('%s(id=%d, custom_name=%r, canonical_name=%r, 
aliases=%r%s)') \
                % (self.__class__.__name__, self.id, self.custom_name,
                   self.canonical_name, self.aliases, kwargs)
 
@@ -466,7 +466,7 @@
                   for ns in identifiers]
 
         if NotImplemented in result:
-            raise TypeError('identifiers contains inappropriate types: %r'
+            raise TypeError(str('identifiers contains inappropriate types: %r')
                             % identifiers)
 
         # Namespace.lookup_name returns None if the name is not recognised
@@ -479,7 +479,7 @@
         return result
 
 
-class BaseSite(ComparableMixin):
+class BaseSite(ComparableMixin, UnicodeMixin):
 
     """Site methods that are independent of the communication interface."""
 
@@ -658,7 +658,7 @@
             raise AttributeError("%s instance has no attribute '%s'"
                                  % (self.__class__.__name__, attr))
 
-    def __str__(self):
+    def __unicode__(self):
         """Return string representing this Site's name and code."""
         return self.family.name + ':' + self.code
 
@@ -667,7 +667,7 @@
         """String representing this Site's name and code."""
         return SelfCallString(self.__str__())
 
-    def __repr__(self):
+    def _repr(self):
         return 'Site("%s", "%s")' % (self.code, self.family.name)
 
     def __hash__(self):
@@ -3080,7 +3080,7 @@
                 if namespaces:
                     if excluded_namespaces.intersect(namespaces):
                         raise ValueError(
-                            'incompatible namespaces %r and member_type %r'
+                            str('incompatible namespaces %r and member_type 
%r')
                             % (namespaces, member_type))
                     # All excluded namespaces are not present in `namespaces`.
                 else:
@@ -5433,7 +5433,7 @@
             return self._item_namespace
         else:
             raise EntityTypeUnknownException(
-                '%r does not support entity type "item"'
+                str('%r does not support entity type "item"')
                 % self)
 
     @property
@@ -5451,7 +5451,7 @@
             return self._property_namespace
         else:
             raise EntityTypeUnknownException(
-                '%r does not support entity type "property"'
+                str('%r does not support entity type "property"')
                 % self)
 
     def __getattr__(self, attr):
@@ -5476,7 +5476,7 @@
                 return f
         return super(APISite, self).__getattr__(attr)
 
-    def __repr__(self):
+    def _repr(self):
         return 'DataSite("%s", "%s")' % (self.code, self.family.name)
 
     @deprecated("pywikibot.PropertyPage")
diff --git a/pywikibot/textlib.py b/pywikibot/textlib.py
index 410c100..8e02e0d 100644
--- a/pywikibot/textlib.py
+++ b/pywikibot/textlib.py
@@ -31,7 +31,7 @@
 
 from pywikibot import config2 as config
 from pywikibot.family import Family
-from pywikibot.tools import OrderedDict
+from pywikibot.tools import OrderedDict, UnicodeMixin
 
 TEMP_REGEX = re.compile(
     
r'{{(?:msg:)?(?P<name>[^{\|]+?)(?:\|(?P<params>[^{]+?(?:{[^{]+?}[^{]*?)?))?}}')
@@ -1230,7 +1230,7 @@
 # Time parsing functionality (Archivebot)
 # ---------------------------------------
 
-class tzoneFixedOffset(datetime.tzinfo):
+class tzoneFixedOffset(datetime.tzinfo, UnicodeMixin):
 
     """
     Class building tzinfo objects for fixed-offset time zones.
@@ -1256,7 +1256,7 @@
         """Return no daylight savings time."""
         return datetime.timedelta(0)
 
-    def __repr__(self):
+    def _repr(self):
         """Return the internal representation of the timezone."""
         return "%s(%s, %s)" % (
             self.__class__.__name__,
diff --git a/pywikibot/tools/__init__.py b/pywikibot/tools/__init__.py
index b7de462..7c57914 100644
--- a/pywikibot/tools/__init__.py
+++ b/pywikibot/tools/__init__.py
@@ -94,7 +94,7 @@
 
 class UnicodeMixin(object):
 
-    """Mixin class to add __str__ method in Python 2 or 3."""
+    """Mixin class to add __str__ and __repr__ method in Python 2 or 3."""
 
     if sys.version_info[0] > 2:
         def __str__(self):
@@ -104,6 +104,15 @@
         def __str__(self):
             """Return the str representation of the UTF-8 encoded Unicode."""
             return self.__unicode__().encode('utf8')
+
+    if sys.version_info[0] > 2:
+        def __repr__(self):
+            """Return a more complete string representation."""
+            return self._repr()
+    else:
+        def __repr__(self):
+            """Return a more complete string representation."""
+            return self._repr().encode('utf8')
 
 
 # From http://python3porting.com/preparing.html
@@ -500,7 +509,7 @@
             time.sleep(2)
         super(ThreadList, self).append(thd)
         thd.start()
-        debug("thread started: %r" % thd, self._logger)
+        debug(str('thread started: %r') % thd, self._logger)
 
     def stop_all(self):
         """Stop all threads the pool."""
diff --git a/pywikibot/userinterfaces/transliteration.py 
b/pywikibot/userinterfaces/transliteration.py
index efd4103..48d5dea 100644
--- a/pywikibot/userinterfaces/transliteration.py
+++ b/pywikibot/userinterfaces/transliteration.py
@@ -2813,7 +2813,7 @@
             if value == "?":
                 continue
             while value.encode(encoding, 'replace').decode(encoding) == "?" 
and value in self.trans:
-                assert value != self.trans[value], "%r == self.trans[%r]!" % 
(value, value)
+                assert value != self.trans[value], str('%r == 
self.trans[%r]!') % (value, value)
                 value = self.trans[value]
             self.trans[char] = value
 
diff --git a/pywikibot/userinterfaces/win32_unicode.py 
b/pywikibot/userinterfaces/win32_unicode.py
index 5f7da5f..1514f52 100755
--- a/pywikibot/userinterfaces/win32_unicode.py
+++ b/pywikibot/userinterfaces/win32_unicode.py
@@ -187,7 +187,7 @@
                         try:
                             self._stream.flush()
                         except Exception as e:
-                            _complain("%s.flush: %r from %r"
+                            _complain(str('%s.flush: %r from %r')
                                       % (self.name, e, self._stream))
                             raise
 
@@ -210,14 +210,14 @@
                                                        min(remaining, 10000),
                                                        byref(n), None)
                                 if retval == 0 or n.value == 0:
-                                    raise IOError("WriteConsoleW returned %r, 
n.value = %r"
+                                    raise IOError(str('WriteConsoleW returned 
%r, n.value = %r')
                                                   % (retval, n.value))
                                 remaining -= n.value
                                 if remaining == 0:
                                     break
                                 text = text[n.value:]
                     except Exception as e:
-                        _complain("%s.write: %r" % (self.name, e))
+                        _complain(str('%s.write: %r') % (self.name, e))
                         raise
 
                 def writelines(self, lines):
@@ -225,7 +225,7 @@
                         for line in lines:
                             self.write(line)
                     except Exception as e:
-                        _complain("%s.writelines: %r" % (self.name, e))
+                        _complain(str('%s.writelines: %r') % (self.name, e))
                         raise
 
             if real_stdin:
@@ -245,7 +245,7 @@
                 stderr = UnicodeOutput(None, sys.stderr, old_stderr_fileno,
                                        '<Unicode redirected stderr>')
     except Exception as e:
-        _complain("exception %r while fixing up sys.stdout and sys.stderr" % 
(e,))
+        _complain(str('exception %r while fixing up sys.stdout and 
sys.stderr') % (e,))
 
     # While we're at it, let's unmangle the command-line arguments:
 
diff --git a/scripts/archivebot.py b/scripts/archivebot.py
index 22d9af7..b4b82a5 100644
--- a/scripts/archivebot.py
+++ b/scripts/archivebot.py
@@ -108,6 +108,7 @@
 from pywikibot import i18n
 from pywikibot.textlib import TimeStripper
 from pywikibot.textlib import to_local_digits
+from pywikibot.tools import UnicodeMixin
 
 ZERO = datetime.timedelta(0)
 
@@ -231,7 +232,7 @@
     return re.compile(r'(?:(?:%s):)%s%s' % (u'|'.join(ns), marker, title))
 
 
-class TZoneUTC(datetime.tzinfo):
+class TZoneUTC(datetime.tzinfo, UnicodeMixin):
 
     """Class building a UTC tzinfo object."""
 
@@ -244,11 +245,11 @@
     def dst(self, dt):  # pylint: disable=unused-argument
         return ZERO
 
-    def __repr__(self):
+    def _repr(self):
         return "%s()" % self.__class__.__name__
 
 
-class DiscussionThread(object):
+class DiscussionThread(UnicodeMixin):
 
     """
     An object representing a discussion thread on a page.
@@ -269,7 +270,7 @@
         self.content = ""
         self.timestamp = None
 
-    def __repr__(self):
+    def _repr(self):
         return '%s("%s",%d bytes)' \
                % (self.__class__.__name__, self.title,
                   len(self.content.encode('utf-8')))
diff --git a/scripts/maintenance/cache.py b/scripts/maintenance/cache.py
index 4a0d1d2..2b1dabb 100644
--- a/scripts/maintenance/cache.py
+++ b/scripts/maintenance/cache.py
@@ -54,6 +54,7 @@
 from pywikibot.data import api
 
 from pywikibot.site import APISite, DataSite, LoginStatus  # noqa
+from pywikibot.tools import UnicodeMixin
 from pywikibot.page import User  # noqa
 
 
@@ -62,7 +63,7 @@
     """Error parsing."""
 
 
-class CacheEntry(api.CachedRequest):
+class CacheEntry(api.CachedRequest, UnicodeMixin):
 
     """A Request cache entry."""
 
@@ -71,10 +72,10 @@
         self.directory = directory
         self.filename = filename
 
-    def __str__(self):
+    def __unicode__(self):
         return self.filename
 
-    def __repr__(self):
+    def _repr(self):
         return self._cachefile_path()
 
     def _create_file_name(self):
diff --git a/tests/__init__.py b/tests/__init__.py
index 031b90d..00ed866 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -140,7 +140,7 @@
               % ', '.join(extra_test_modules))
 
     if disabled_tests:
-        print('Skipping tests (to run: python -m unittest ...):\n  %r'
+        print(str('Skipping tests (to run: python -m unittest ...):\n  %r')
               % disabled_tests)
 
     modules = [module
diff --git a/tests/aspects.py b/tests/aspects.py
index 1fb1728..a7a878a 100644
--- a/tests/aspects.py
+++ b/tests/aspects.py
@@ -105,7 +105,7 @@
             namespaces = set([namespaces])
 
         self.assertIn(page.namespace(), namespaces,
-                      "%s not in namespace %r" % (page, namespaces))
+                      str('%s not in namespace %r') % (page, namespaces))
 
     def _get_gen_pages(self, gen, count=None, site=None):
         """
@@ -193,7 +193,7 @@
         page_namespaces = [page.namespace() for page in gen]
 
         if skip and set(page_namespaces) != namespaces:
-            raise unittest.SkipTest('Pages in namespaces %r not found.'
+            raise unittest.SkipTest(str('Pages in namespaces %r not found.')
                                     % list(namespaces - set(page_namespaces)))
         else:
             self.assertEqual(set(page_namespaces), namespaces)
diff --git a/tests/dry_api_tests.py b/tests/dry_api_tests.py
index 82d35af..881a156 100644
--- a/tests/dry_api_tests.py
+++ b/tests/dry_api_tests.py
@@ -125,11 +125,11 @@
             def siteinfo(self):
                 return self._siteinfo
 
-            def __repr__(self):
+            def _repr(self):
                 return "MockSite()"
 
             def __getattr__(self, attr):
-                raise Exception("Attribute %r not defined" % attr)
+                raise Exception(str('Attribute %r not defined') % attr)
 
         self.mocksite = MockSite()
         super(MockCachedRequestKeyTests, self).setUp()
diff --git a/tests/pwb/print_locals.py b/tests/pwb/print_locals.py
index c39b776..4fd2e5e 100644
--- a/tests/pwb/print_locals.py
+++ b/tests/pwb/print_locals.py
@@ -8,6 +8,6 @@
     if k in ['__cached__', '__loader__', '__spec__']:
         continue
     if k == '__file__':
-        print("__file__: %r" % os.path.join('.', os.path.relpath(__file__)))
+        print(str('__file__: %r') % os.path.join('.', 
os.path.relpath(__file__)))
     else:
-        print("%r: %r" % (k, v))
+        print(str('%r: %r') % (k, v))
diff --git a/tests/script_tests.py b/tests/script_tests.py
index dd44df3..3cd6084 100644
--- a/tests/script_tests.py
+++ b/tests/script_tests.py
@@ -172,12 +172,12 @@
               % ', '.join(deadlock_script_list))
 
     if unrunnable_script_list:
-        print('Skipping execution of unrunnable scripts:\n  %r'
+        print(str('Skipping execution of unrunnable scripts:\n  %r')
               % unrunnable_script_list)
 
     if not enable_autorun_tests:
-        print('Skipping execution of auto-run scripts '
-              '(set PYWIKIBOT2_TEST_AUTORUN=1 to enable):\n  %r'
+        print(str('Skipping execution of auto-run scripts '
+                  '(set PYWIKIBOT2_TEST_AUTORUN=1 to enable):\n  %r')
               % auto_run_script_list)
 
     tests = (['test__login_help'] +
@@ -273,7 +273,7 @@
                         print(' auto-run script simulated edit blocked',
                               end='  ')
                     else:
-                        print(' auto-run script stderr within %d seconds: %r'
+                        print(str(' auto-run script stderr within %d seconds: 
%r')
                               % (timeout, result['stderr']), end='  ')
 
                 self.assertNotIn('Traceback (most recent call last)',
diff --git a/tests/site_tests.py b/tests/site_tests.py
index d8d305e..aefc5e9 100644
--- a/tests/site_tests.py
+++ b/tests/site_tests.py
@@ -991,7 +991,7 @@
         mysite = self.get_site()
         if mysite.has_extension("Wikia Search"):
             raise unittest.SkipTest(
-                'The site %r does not use MediaWiki search' % mysite)
+                str('The site %r does not use MediaWiki search') % mysite)
         try:
             se = list(mysite.search("wiki", total=100))
             self.assertLessEqual(len(se), 100)
@@ -1013,7 +1013,7 @@
                 self.assertEqual(hit.namespace(), 0)
         except pywikibot.data.api.APIError as e:
             if e.code == "gsrsearch-error" and "timed out" in e.info:
-                raise unittest.SkipTest("gsrsearch returned timeout on site: 
%r" % e)
+                raise unittest.SkipTest(str("gsrsearch returned timeout on 
site: %r") % e)
             raise
 
     def testUsercontribs(self):
diff --git a/tests/utils.py b/tests/utils.py
index 99c0957..bac74d6 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -193,11 +193,11 @@
         self._siteinfo._cache['lang'] = (code, True)
         self._namespaces = SelfCallDict(Namespace.builtin_namespaces())
 
-    def __repr__(self):
+    def _repr(self):
         """Override default so warnings and errors indicate test is dry."""
-        return "%s(%r, %r)" % (self.__class__.__name__,
-                               self.code,
-                               self.family.name)
+        return str('%s(%r, %r)') % (self.__class__.__name__,
+                                    self.code,
+                                    self.family.name)
 
     @property
     def userinfo(self):
@@ -206,7 +206,7 @@
 
     def version(self):
         """Dummy version, with warning to show the callers context."""
-        warn('%r returning version 1.24; override if unsuitable.'
+        warn(str('%r returning version 1.24; override if unsuitable.')
              % self, DrySiteNote, stacklevel=2)
         return '1.24'
 
diff --git a/tests/wikibase_tests.py b/tests/wikibase_tests.py
index 5a37136..106378d 100644
--- a/tests/wikibase_tests.py
+++ b/tests/wikibase_tests.py
@@ -110,7 +110,7 @@
                          '    "unit": "1",\n'
                          '    "upperBound": %(val)r\n'
                          '}' % {'val': 0.044405586})
-        self.assertEqual("%r" % q,
+        self.assertEqual(str('%r') % q,
                          "WbQuantity(amount=%(val)s, "
                          "upperBound=%(val)s, lowerBound=%(val)s, "
                          "unit=1)" % {'val': 0.044405586})

-- 
To view, visit https://gerrit.wikimedia.org/r/203051
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ic7c96194c0d975033923ea43af19f9f40d331321
Gerrit-PatchSet: 1
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: XZise <commodorefabia...@gmx.de>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to