← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad/six-1.12.0 into lp:launchpad

 

Colin Watson has proposed merging lp:~cjwatson/launchpad/six-1.12.0 into lp:launchpad.

Commit message:
Upgrade to six 1.12.0, and use the new six.ensure_* where appropriate.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/six-1.12.0/+merge/360475

These reduce noise from isinstance checks in a number of places, and provide a more convenient way of obtaining a native string appropriate to the Python version (previously done using twisted.python.compat.nativeString, which is odd in non-Twisted contexts).
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/six-1.12.0 into lp:launchpad.
=== modified file 'constraints.txt'
--- constraints.txt	2018-07-16 10:51:04 +0000
+++ constraints.txt	2018-12-10 14:09:32 +0000
@@ -348,7 +348,7 @@
 setuptools-scm==1.15.7
 simplejson==3.8.2
 SimpleTAL==4.3
-six==1.11.0
+six==1.12.0
 snowballstemmer==1.2.1
 soupmatchers==0.4
 sphinxcontrib-websupport==1.0.1

=== modified file 'lib/lp/bugs/externalbugtracker/xmlrpc.py'
--- lib/lp/bugs/externalbugtracker/xmlrpc.py	2018-07-13 12:48:19 +0000
+++ lib/lp/bugs/externalbugtracker/xmlrpc.py	2018-12-10 14:09:32 +0000
@@ -21,6 +21,7 @@
 
 import requests
 from requests.cookies import RequestsCookieJar
+import six
 
 from lp.bugs.externalbugtracker.base import repost_on_redirect_hook
 from lp.services.config import config
@@ -71,8 +72,7 @@
         url = urlunparse((self.scheme, host, handler, '', '', ''))
         # httplib can raise a UnicodeDecodeError when using a Unicode
         # URL, a non-ASCII body and a proxy. http://bugs.python.org/issue12398
-        if not isinstance(url, bytes):
-            url = url.encode('utf-8')
+        url = six.ensure_binary(url)
         try:
             with override_timeout(self.timeout):
                 response = urlfetch(

=== modified file 'lib/lp/buildmaster/model/packagebuild.py'
--- lib/lp/buildmaster/model/packagebuild.py	2015-02-17 07:39:47 +0000
+++ lib/lp/buildmaster/model/packagebuild.py	2018-12-10 14:09:32 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010-2013 Canonical Ltd.  This software is licensed under the
+# Copyright 2010-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
@@ -9,6 +9,7 @@
 
 from cStringIO import StringIO
 
+import six
 from zope.component import getUtility
 
 from lp.buildmaster.enums import BuildStatus
@@ -82,8 +83,7 @@
         if filename is None:
             filename = 'upload_%s_log.txt' % self.id
         contentType = filenameToContentType(filename)
-        if isinstance(content, unicode):
-            content = content.encode('utf-8')
+        content = six.ensure_binary(content)
         file_size = len(content)
         file_content = StringIO(content)
         restricted = self.is_private

=== modified file 'lib/lp/code/interfaces/codehosting.py'
--- lib/lp/code/interfaces/codehosting.py	2013-01-07 02:40:55 +0000
+++ lib/lp/code/interfaces/codehosting.py	2018-12-10 14:09:32 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Internal Codehosting API interfaces."""
@@ -25,6 +25,7 @@
 import urllib
 
 from lazr.uri import URI
+import six
 from zope.interface import Interface
 
 from lp.app.validators.name import valid_name
@@ -201,11 +202,9 @@
     accepted_schemes.add('sftp')
     assert scheme in accepted_schemes, "Unknown scheme: %s" % scheme
     host = URI(config.codehosting.supermirror_root).host
-    if isinstance(unique_name, unicode):
-        unique_name = unique_name.encode('utf-8')
     # After quoting and encoding, the path should be perfectly
     # safe as a plain ASCII string, str() just enforces this
-    path = '/' + str(urllib.quote(unique_name, safe='/~+'))
+    path = '/' + str(urllib.quote(six.ensure_binary(unique_name), safe='/~+'))
     if suffix:
         path = os.path.join(path, suffix)
     return str(URI(scheme=scheme, host=host, path=path))

=== modified file 'lib/lp/code/model/branch.py'
--- lib/lp/code/model/branch.py	2018-07-09 09:27:06 +0000
+++ lib/lp/code/model/branch.py	2018-12-10 14:09:32 +0000
@@ -18,6 +18,7 @@
 from bzrlib.revision import NULL_REVISION
 from lazr.lifecycle.event import ObjectCreatedEvent
 import pytz
+import six
 from six.moves.urllib_parse import urlsplit
 from sqlobject import (
     ForeignKey,
@@ -813,10 +814,9 @@
             memcache_client = getUtility(IMemcacheClient)
             instance_name = urlsplit(
                 config.codehosting.internal_bzr_api_endpoint).hostname
-            memcache_key = '%s:bzr-file-list:%s:%s:%s' % (
-                instance_name, self.id, revision_id, dirname)
-            if not isinstance(memcache_key, bytes):
-                memcache_key = memcache_key.encode('UTF-8')
+            memcache_key = six.ensure_binary(
+                '%s:bzr-file-list:%s:%s:%s' % (
+                    instance_name, self.id, revision_id, dirname))
             cached_file_list = memcache_client.get(memcache_key)
             if cached_file_list is not None:
                 try:

=== modified file 'lib/lp/code/model/gitlookup.py'
--- lib/lp/code/model/gitlookup.py	2018-09-28 13:54:16 +0000
+++ lib/lp/code/model/gitlookup.py	2018-12-10 14:09:32 +0000
@@ -12,6 +12,7 @@
     InvalidURIError,
     URI,
     )
+import six
 from zope.component import (
     adapter,
     getUtility,
@@ -235,9 +236,7 @@
         self.traversed = []
 
     def next(self):
-        segment = next(self._iterator)
-        if not isinstance(segment, unicode):
-            segment = segment.decode("US-ASCII")
+        segment = six.ensure_text(next(self._iterator), encoding="US-ASCII")
         self.traversed.append(segment)
         return segment
 

=== modified file 'lib/lp/code/model/gitref.py'
--- lib/lp/code/model/gitref.py	2018-11-09 22:06:43 +0000
+++ lib/lp/code/model/gitref.py	2018-12-10 14:09:32 +0000
@@ -21,6 +21,7 @@
 from lazr.lifecycle.event import ObjectCreatedEvent
 import pytz
 import requests
+import six
 from storm.locals import (
     DateTime,
     Int,
@@ -329,8 +330,7 @@
                 memcache_key += ":limit=%s" % limit
             if stop is not None:
                 memcache_key += ":stop=%s" % stop
-            if isinstance(memcache_key, unicode):
-                memcache_key = memcache_key.encode("UTF-8")
+            memcache_key = six.ensure_binary(memcache_key)
             cached_log = memcache_client.get(memcache_key)
             if cached_log is not None:
                 try:

=== modified file 'lib/lp/code/model/gitrepository.py'
--- lib/lp/code/model/gitrepository.py	2018-12-03 14:44:26 +0000
+++ lib/lp/code/model/gitrepository.py	2018-12-10 14:09:32 +0000
@@ -29,6 +29,7 @@
 from lazr.lifecycle.event import ObjectModifiedEvent
 from lazr.lifecycle.snapshot import Snapshot
 import pytz
+import six
 from storm.databases.postgres import Returning
 from storm.expr import (
     And,
@@ -580,9 +581,7 @@
             raise ValueError('ref info sha1 is not a 40-character string')
         if obj["type"] not in object_type_map:
             raise ValueError('ref info type is not a recognised object type')
-        sha1 = obj["sha1"]
-        if isinstance(sha1, bytes):
-            sha1 = sha1.decode("US-ASCII")
+        sha1 = six.ensure_text(obj["sha1"], encoding="US-ASCII")
         return {"sha1": sha1, "type": object_type_map[obj["type"]]}
 
     def createOrUpdateRefs(self, refs_info, get_objects=False, logger=None):
@@ -1320,12 +1319,10 @@
             grants_for_user[grant.rule].append(grant)
 
         for ref_path in ref_paths:
-            encoded_ref_path = (
-                ref_path if isinstance(ref_path, bytes)
-                else ref_path.encode("UTF-8"))
             matching_rules = [
-                rule for rule in rules if
-                fnmatch(encoded_ref_path, rule.ref_pattern.encode("UTF-8"))]
+                rule for rule in rules if fnmatch(
+                    six.ensure_binary(ref_path),
+                    rule.ref_pattern.encode("UTF-8"))]
             if is_owner and not matching_rules:
                 # If there are no matching rules, then the repository owner
                 # can do anything.

=== modified file 'lib/lp/code/xmlrpc/codehosting.py'
--- lib/lp/code/xmlrpc/codehosting.py	2015-07-08 16:05:11 +0000
+++ lib/lp/code/xmlrpc/codehosting.py	2018-12-10 14:09:32 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2015 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Implementations of the XML-RPC APIs for codehosting."""
@@ -18,6 +18,7 @@
     unescape,
     )
 import pytz
+import six
 import transaction
 from zope.component import getUtility
 from zope.interface import implementer
@@ -109,9 +110,8 @@
         # Don't pass in an actual user. Instead pass in LAUNCHPAD_SERVICES
         # and expect `function` to use `removeSecurityProxy` or similar.
         return function(login_id, *args, **kwargs)
-    if isinstance(login_id, basestring):
-        if isinstance(login_id, bytes):
-            login_id = login_id.decode("UTF-8")
+    if isinstance(login_id, (six.binary_type, six.text_type)):
+        login_id = six.ensure_text(login_id)
         # OpenID identifiers must contain a slash, while names must not.
         if "/" in login_id:
             requester = getUtility(IPersonSet).getByOpenIDIdentifier(login_id)
@@ -242,10 +242,7 @@
                 branch = namespace.createBranch(
                     BranchType.HOSTED, branch_name, requester)
             except LaunchpadValidationError as e:
-                msg = e.args[0]
-                if isinstance(msg, unicode):
-                    msg = msg.encode('utf-8')
-                return faults.PermissionDenied(msg)
+                return faults.PermissionDenied(six.ensure_binary(e.args[0]))
             except BranchCreationException as e:
                 return faults.PermissionDenied(str(e))
 

=== modified file 'lib/lp/code/xmlrpc/git.py'
--- lib/lp/code/xmlrpc/git.py	2018-11-21 00:54:42 +0000
+++ lib/lp/code/xmlrpc/git.py	2018-12-10 14:09:32 +0000
@@ -11,6 +11,7 @@
 import sys
 
 from pymacaroons import Macaroon
+import six
 from six.moves import xmlrpc_client
 from storm.store import Store
 import transaction
@@ -313,11 +314,9 @@
         requester_id = auth_params.get("uid")
         if requester_id is None:
             requester_id = LAUNCHPAD_ANONYMOUS
-        if isinstance(path, str):
-            path = path.decode('utf-8')
         return run_with_login(
             requester_id, self._translatePath,
-            path.strip("/"), permission, auth_params)
+            six.ensure_text(path).strip("/"), permission, auth_params)
 
     def notify(self, translated_path):
         """See `IGitAPI`."""

=== modified file 'lib/lp/codehosting/codeimport/uifactory.py'
--- lib/lp/codehosting/codeimport/uifactory.py	2011-12-19 23:38:16 +0000
+++ lib/lp/codehosting/codeimport/uifactory.py	2018-12-10 14:09:32 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """A UIFactory useful for code imports."""
@@ -12,6 +12,7 @@
 
 from bzrlib.ui import NoninteractiveUIFactory
 from bzrlib.ui.text import TextProgressView
+import six
 
 
 class LoggingUIFactory(NoninteractiveUIFactory):
@@ -45,9 +46,7 @@
             "%s", self.format_user_warning(warning_id, message_args))
 
     def show_warning(self, msg):
-        if isinstance(msg, unicode):
-            msg = msg.encode("utf-8")
-        self.logger.warning("%s", msg)
+        self.logger.warning("%s", six.ensure_binary(msg))
 
     def get_username(self, prompt, **kwargs):
         return None

=== modified file 'lib/lp/codehosting/inmemory.py'
--- lib/lp/codehosting/inmemory.py	2015-10-01 10:25:19 +0000
+++ lib/lp/codehosting/inmemory.py	2018-12-10 14:09:32 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """In-memory doubles of core codehosting objects."""
@@ -16,6 +16,7 @@
     escape,
     unescape,
     )
+import six
 from twisted.internet import defer
 from zope.component import (
     adapter,
@@ -715,10 +716,7 @@
         except LaunchpadFault as e:
             return e
         except LaunchpadValidationError as e:
-            msg = e.args[0]
-            if isinstance(msg, unicode):
-                msg = msg.encode('utf-8')
-            return faults.PermissionDenied(msg)
+            return faults.PermissionDenied(six.ensure_binary(e.args[0]))
 
     def requestMirror(self, requester_id, branch_id):
         self._branch_set.get(branch_id).requestMirror()

=== modified file 'lib/lp/codehosting/vfs/branchfs.py'
--- lib/lp/codehosting/vfs/branchfs.py	2018-01-26 13:47:51 +0000
+++ lib/lp/codehosting/vfs/branchfs.py	2018-12-10 14:09:32 +0000
@@ -73,6 +73,7 @@
 from bzrlib.transport import get_transport
 from bzrlib.transport.memory import MemoryServer
 from lazr.uri import URI
+import six
 from twisted.internet import (
     defer,
     error,
@@ -609,9 +610,7 @@
             fault = trap_fault(
                 fail, faults.NotFound, faults.PermissionDenied,
                 faults.InvalidSourcePackageName, faults.InvalidProductName)
-            faultString = fault.faultString
-            if isinstance(faultString, unicode):
-                faultString = faultString.encode('utf-8')
+            faultString = six.ensure_binary(fault.faultString)
             return failure.Failure(
                 PermissionDenied(virtual_url_fragment, faultString))
 

=== modified file 'lib/lp/registry/scripts/tests/test_closeaccount.py'
--- lib/lp/registry/scripts/tests/test_closeaccount.py	2018-12-04 17:33:38 +0000
+++ lib/lp/registry/scripts/tests/test_closeaccount.py	2018-12-10 14:09:32 +0000
@@ -7,12 +7,12 @@
 
 __metaclass__ = type
 
+import six
 from testtools.matchers import (
     Not,
     StartsWith,
     )
 import transaction
-from twisted.python.compat import nativeString
 from zope.component import getUtility
 from zope.security.proxy import removeSecurityProxy
 
@@ -143,7 +143,7 @@
         person_id = person.id
         account_id = person.account.id
         self.factory.makeProduct(owner=person)
-        script = self.makeScript([nativeString(person.name)])
+        script = self.makeScript([six.ensure_str(person.name)])
         with dbuser('launchpad'):
             self.assertRaisesWithContent(
                 LaunchpadScriptFailure,
@@ -157,14 +157,14 @@
 
     def test_single_by_name(self):
         person, person_id, account_id = self.makePopulatedUser()
-        script = self.makeScript([nativeString(person.name)])
+        script = self.makeScript([six.ensure_str(person.name)])
         with dbuser('launchpad'):
             self.runScript(script)
         self.assertRemoved(account_id, person_id)
 
     def test_single_by_email(self):
         person, person_id, account_id = self.makePopulatedUser()
-        script = self.makeScript([nativeString(person.preferredemail.email)])
+        script = self.makeScript([six.ensure_str(person.preferredemail.email)])
         with dbuser('launchpad'):
             self.runScript(script)
         self.assertRemoved(account_id, person_id)
@@ -199,7 +199,7 @@
         snap = self.factory.makeSnap()
         snap_build = self.factory.makeSnapBuild(requester=person, snap=snap)
         specification = self.factory.makeSpecification(drafter=person)
-        script = self.makeScript([nativeString(person.name)])
+        script = self.makeScript([six.ensure_str(person.name)])
         with dbuser('launchpad'):
             self.runScript(script)
         self.assertRemoved(account_id, person_id)
@@ -219,7 +219,7 @@
             question.addComment(person, "comment")
             removeSecurityProxy(question).status = status
             questions.append(question)
-        script = self.makeScript([nativeString(person.name)])
+        script = self.makeScript([six.ensure_str(person.name)])
         with dbuser('launchpad'):
             self.runScript(script)
         self.assertRemoved(account_id, person_id)
@@ -241,7 +241,7 @@
             question.addComment(person, "comment")
             removeSecurityProxy(question).status = status
             questions[status] = question
-        script = self.makeScript([nativeString(person.name)])
+        script = self.makeScript([six.ensure_str(person.name)])
         with dbuser('launchpad'):
             self.runScript(script)
         self.assertRemoved(account_id, person_id)

=== modified file 'lib/lp/registry/stories/teammembership/xx-teammembership.txt'
--- lib/lp/registry/stories/teammembership/xx-teammembership.txt	2016-11-15 17:49:49 +0000
+++ lib/lp/registry/stories/teammembership/xx-teammembership.txt	2018-12-10 14:09:32 +0000
@@ -224,13 +224,13 @@
 well as the former members and the ones which proposed themselves or that
 have been invited.
 
+    >>> import six
+
     >>> def print_members(contents, type):
     ...     table = find_tag_by_id(contents, type)
     ...     for link in table.findAll('a'):
     ...         if link.renderContents() != 'Edit' and not link.find('img'):
-    ...             contents = link.renderContents()
-    ...             if isinstance(contents, str):
-    ...                 contents = contents.decode('utf-8')
+    ...             contents = six.ensure_text(link.renderContents())
     ...             print contents.encode('ascii', 'replace')
 
     >>> browser.open('http://launchpad.dev/~landscape-developers')

=== modified file 'lib/lp/scripts/utilities/test.py'
--- lib/lp/scripts/utilities/test.py	2017-05-08 11:56:10 +0000
+++ lib/lp/scripts/utilities/test.py	2018-12-10 14:09:32 +0000
@@ -22,6 +22,7 @@
 import time
 import warnings
 
+import six
 from zope.testing import testrunner
 from zope.testing.testrunner import options
 
@@ -39,9 +40,7 @@
     class _SpoofOut(doctest._SpoofOut):
 
         def write(self, value):
-            if isinstance(value, unicode):
-                value = value.encode('utf8')
-            _RealSpoofOut.write(self, value)
+            _RealSpoofOut.write(self, six.ensure_binary(value))
 
     doctest._SpoofOut = _SpoofOut
 

=== modified file 'lib/lp/services/librarian/client.py'
--- lib/lp/services/librarian/client.py	2016-06-02 17:52:18 +0000
+++ lib/lp/services/librarian/client.py	2018-12-10 14:09:32 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
@@ -32,6 +32,7 @@
     )
 
 from lazr.restful.utils import get_current_browser_request
+import six
 from storm.store import Store
 from zope.interface import implementer
 
@@ -142,8 +143,7 @@
         if size <= min_size:
             raise UploadFailed('Invalid length: %d' % size)
 
-        if isinstance(name, unicode):
-            name = name.encode('utf-8')
+        name = six.ensure_binary(name)
 
         # Import in this method to avoid a circular import
         from lp.services.librarian.model import LibraryFileContent
@@ -235,8 +235,7 @@
             raise TypeError('No data')
         if size <= 0:
             raise UploadFailed('No data')
-        if isinstance(name, unicode):
-            name = name.encode('utf-8')
+        name = six.ensure_binary(name)
         self._connect()
         try:
             database_name = ConnectionString(dbconfig.main_master).dbname

=== modified file 'lib/lp/services/oauth/model.py'
--- lib/lp/services/oauth/model.py	2016-01-28 17:52:53 +0000
+++ lib/lp/services/oauth/model.py	2018-12-10 14:09:32 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
@@ -17,6 +17,9 @@
     )
 import hashlib
 import re
+
+import pytz
+import six
 from storm.locals import (
     Bool,
     DateTime,
@@ -24,8 +27,6 @@
     Reference,
     Unicode,
     )
-
-import pytz
 from zope.interface import implementer
 
 from lp.registry.interfaces.distribution import IDistribution
@@ -81,10 +82,7 @@
     this is straightforward because hexdigest() returns that anyway, but in
     Python 2 we must decode.
     """
-    digest = hashlib.sha256(data).hexdigest()
-    if isinstance(digest, bytes):
-        digest = digest.decode('ASCII')
-    return digest
+    return six.ensure_text(hashlib.sha256(data).hexdigest(), encoding='ASCII')
 
 
 @implementer(IOAuthConsumer)

=== modified file 'lib/lp/services/webapp/authentication.py'
--- lib/lp/services/webapp/authentication.py	2016-02-28 19:13:17 +0000
+++ lib/lp/services/webapp/authentication.py	2018-12-10 14:09:32 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
@@ -15,6 +15,7 @@
 import binascii
 
 from contrib.oauth import OAuthRequest
+import six
 from zope.authentication.interfaces import ILoginPassword
 from zope.component import getUtility
 from zope.event import notify
@@ -283,9 +284,7 @@
         # http://oauth.net/core/1.0/#encoding_parameters says "Text names
         # and values MUST be encoded as UTF-8 octets before percent-encoding
         # them", so we can reasonably fail if this hasn't been done.
-        if isinstance(header, bytes):
-            header = header.decode("UTF-8")
-        return OAuthRequest._split_header(header)
+        return OAuthRequest._split_header(six.ensure_text(header))
     else:
         return request.form
 

=== modified file 'lib/lp/services/webapp/login.py'
--- lib/lp/services/webapp/login.py	2017-01-14 15:16:36 +0000
+++ lib/lp/services/webapp/login.py	2018-12-10 14:09:32 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 """Stuff to do with logging in and logging out."""
 
@@ -26,6 +26,7 @@
     HTTPBadRequest,
     HTTPException,
     )
+import six
 import transaction
 from z3c.ptcompat import ViewPageTemplateFile
 from zope.authentication.interfaces import IUnauthenticatedPrincipal
@@ -262,17 +263,12 @@
             else:
                 value_list = [value]
 
-            def encode_utf8(element):
-                # urllib.urlencode will just encode unicode values to ASCII.
-                # For our purposes, we can be a little more liberal and
-                # allow UTF-8.
-                if isinstance(element, unicode):
-                    element = element.encode('UTF-8')
-                return element
-
+            # urllib.urlencode will just encode unicode values to ASCII.
+            # For our purposes, we can be a little more liberal and allow
+            # UTF-8.
             yield (
-                encode_utf8(name),
-                [encode_utf8(value) for value in value_list])
+                six.ensure_binary(name),
+                [six.ensure_binary(value) for value in value_list])
 
 
 class OpenIDCallbackView(OpenIDLogin):

=== modified file 'lib/lp/services/webapp/servers.py'
--- lib/lp/services/webapp/servers.py	2018-02-01 18:39:04 +0000
+++ lib/lp/services/webapp/servers.py	2018-12-10 14:09:32 +0000
@@ -542,21 +542,11 @@
     porting to Python 3 via an intermediate stage of Unicode literals in
     Python 2, we enforce this here.
     """
-    # Based on twisted.python.compat.nativeString, but using a different
-    # encoding.
-    if not isinstance(s, (bytes, unicode)):
-        raise TypeError('%r is neither bytes nor unicode' % s)
-    if six.PY3:
-        if isinstance(s, bytes):
-            return s.decode('ISO-8859-1')
-        else:
-            # Ensure we're limited to ISO-8859-1.
-            s.encode('ISO-8859-1')
-    else:
-        if isinstance(s, unicode):
-            return s.encode('ISO-8859-1')
-        # Bytes objects are always decodable as ISO-8859-1.
-    return s
+    result = six.ensure_str(s, encoding='ISO-8859-1')
+    if six.PY3 and isinstance(s, six.text_type):
+        # Ensure we're limited to ISO-8859-1.
+        result.encode('ISO-8859-1')
+    return result
 
 
 class LaunchpadBrowserRequestMixin:
@@ -1311,9 +1301,7 @@
             # Try to retrieve a consumer based on the User-Agent
             # header.
             anonymous_request = True
-            consumer_key = request.getHeader('User-Agent', '')
-            if isinstance(consumer_key, bytes):
-                consumer_key = consumer_key.decode('UTF-8')
+            consumer_key = six.ensure_text(request.getHeader('User-Agent', ''))
             if consumer_key == u'':
                 consumer_key = u'anonymous client'
             consumer = consumers.getByKey(consumer_key)

=== modified file 'lib/lp/services/webservice/configuration.py'
--- lib/lp/services/webservice/configuration.py	2016-09-14 11:13:06 +0000
+++ lib/lp/services/webservice/configuration.py	2018-12-10 14:09:32 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """A configuration class describing the Launchpad web service."""
@@ -9,6 +9,7 @@
 ]
 
 from lazr.restful.simple import BaseWebServiceConfiguration
+import six
 from zope.component import getUtility
 
 from lp.app import versioninfo
@@ -64,8 +65,8 @@
         """See `IWebServiceConfiguration`."""
         # The request is going to try to decode the 'PATH_INFO' using utf-8,
         # so if it is currently unicode, encode it.
-        if isinstance(environ.get('PATH_INFO'), unicode):
-            environ['PATH_INFO'] = environ['PATH_INFO'].encode('utf-8')
+        if 'PATH_INFO' in environ:
+            environ['PATH_INFO'] = six.ensure_binary(environ['PATH_INFO'])
         request = WebServiceClientRequest(body_instream, environ)
         request.setPublication(WebServicePublication(None))
         return request

=== modified file 'lib/lp/snappy/model/snapstoreclient.py'
--- lib/lp/snappy/model/snapstoreclient.py	2018-05-02 23:55:51 +0000
+++ lib/lp/snappy/model/snapstoreclient.py	2018-12-10 14:09:32 +0000
@@ -24,6 +24,7 @@
 from pymacaroons import Macaroon
 import requests
 from requests_toolbelt import MultipartEncoder
+import six
 from zope.component import getUtility
 from zope.interface import implementer
 from zope.security.proxy import removeSecurityProxy
@@ -189,9 +190,8 @@
                     error_message = "\n".join(
                         error["message"]
                         for error in response_data["error_list"])
-        detail = requests_error.response.content
-        if isinstance(detail, bytes):
-            detail = detail.decode("UTF-8", errors="replace")
+        detail = six.ensure_text(
+            requests_error.response.content, errors="replace")
         can_retry = requests_error.response.status_code in (502, 503)
         return error_class(error_message, detail=detail, can_retry=can_retry)
 

=== modified file 'lib/lp/soyuz/mail/packageupload.py'
--- lib/lp/soyuz/mail/packageupload.py	2016-11-04 10:44:22 +0000
+++ lib/lp/soyuz/mail/packageupload.py	2018-12-10 14:09:32 +0000
@@ -1,4 +1,4 @@
-# Copyright 2011-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2011-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
@@ -9,6 +9,7 @@
 from collections import OrderedDict
 import os.path
 
+import six
 from zope.component import getUtility
 from zope.security.proxy import isinstance as zope_isinstance
 
@@ -620,7 +621,5 @@
             debug(self.logger, "  Bcc: %s" % ctrl.headers['Bcc'])
         debug(self.logger, "  Body:")
         for line in ctrl.body.splitlines():
-            if isinstance(line, bytes):
-                line = line.decode('utf-8', 'replace')
-            debug(self.logger, line)
+            debug(self.logger, six.ensure_text(line, errors="replace"))
         return ctrl

=== modified file 'lib/lp/soyuz/model/packageset.py'
--- lib/lp/soyuz/model/packageset.py	2015-09-27 23:00:53 +0000
+++ lib/lp/soyuz/model/packageset.py	2018-12-10 14:09:32 +0000
@@ -1,10 +1,11 @@
-# Copyright 2009-2014 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
 __all__ = ['Packageset', 'PackagesetSet']
 
 import pytz
+import six
 from storm.expr import SQL
 from storm.locals import (
     DateTime,
@@ -377,8 +378,7 @@
     def getByName(self, distroseries, name):
         """See `IPackagesetSet`."""
         store = IStore(Packageset)
-        if not isinstance(name, unicode):
-            name = unicode(name, 'utf-8')
+        name = six.ensure_text(name)
         package_set = store.find(
             Packageset, Packageset.name == name,
             Packageset.distroseries == distroseries).one()

=== modified file 'lib/lp/testing/_webservice.py'
--- lib/lp/testing/_webservice.py	2016-01-26 15:14:01 +0000
+++ lib/lp/testing/_webservice.py	2018-12-10 14:09:32 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2010-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
@@ -19,6 +19,7 @@
     Credentials,
     )
 from launchpadlib.launchpad import Launchpad
+import six
 import transaction
 from zope.component import getUtility
 import zope.testing.cleanup
@@ -133,8 +134,7 @@
     """
     # XXX cjwatson 2016-01-22: Callers should be updated to pass Unicode
     # directly, but that's a big change.
-    if isinstance(consumer_name, bytes):
-        consumer_name = unicode(consumer_name)
+    consumer_name = six.ensure_text(consumer_name)
     if person is None:
         token = AnonymousAccessToken()
         credentials = Credentials(consumer_name, access_token=token)

=== modified file 'lib/lp/testing/pages.py'
--- lib/lp/testing/pages.py	2018-05-13 10:35:52 +0000
+++ lib/lp/testing/pages.py	2018-12-10 14:09:32 +0000
@@ -32,6 +32,7 @@
     OAuthToken,
     )
 from lazr.restful.testing.webservice import WebServiceCaller
+import six
 import transaction
 from zope.app.testing.functional import (
     HTTPCaller,
@@ -76,6 +77,7 @@
     stop,
     )
 
+
 SAMPLEDATA_ACCESS_SECRETS = {
     u'salgado-read-nonprivate': u'secret',
     u'salgado-change-anything': u'test',
@@ -140,8 +142,7 @@
         if oauth_consumer_key is not None and oauth_access_key is not None:
             # XXX cjwatson 2016-01-25: Callers should be updated to pass
             # Unicode directly, but that's a big change.
-            if isinstance(oauth_consumer_key, bytes):
-                oauth_consumer_key = unicode(oauth_consumer_key)
+            oauth_consumer_key = six.ensure_text(oauth_consumer_key)
             self.consumer = OAuthConsumer(oauth_consumer_key, u'')
             if oauth_access_secret is None:
                 oauth_access_secret = SAMPLEDATA_ACCESS_SECRETS.get(

=== modified file 'lib/lp/translations/model/potranslation.py'
--- lib/lp/translations/model/potranslation.py	2015-10-14 16:23:18 +0000
+++ lib/lp/translations/model/potranslation.py	2018-12-10 14:09:32 +0000
@@ -1,9 +1,10 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
 __all__ = ['POTranslation']
 
+import six
 from sqlobject import (
     SQLObjectNotFound,
     StringCol,
@@ -48,13 +49,10 @@
         """Return a POTranslation object for the given translation, or create
         it if it doesn't exist.
         """
-        if isinstance(key, str):
-            # If this is not a unicode object, it had better be ASCII or
-            # UTF-8.
-            # XXX: JeroenVermeulen 2008-06-06 bug=237868: non-ascii str
-            # strings should be contained in the parser or the browser
-            # code.
-            key = key.decode('UTF-8')
+        # If this is not a unicode object, it had better be ASCII or UTF-8.
+        # XXX: JeroenVermeulen 2008-06-06 bug=237868: non-ascii str strings
+        # should be contained in the parser or the browser code.
+        key = six.ensure_text(key)
 
         try:
             return cls.byTranslation(key)

=== modified file 'lib/lp/translations/utilities/translationmerger.py'
--- lib/lp/translations/utilities/translationmerger.py	2018-02-02 10:06:24 +0000
+++ lib/lp/translations/utilities/translationmerger.py	2018-12-10 14:09:32 +0000
@@ -11,6 +11,7 @@
 
 from operator import methodcaller
 
+import six
 from storm.locals import (
     ClassAlias,
     Store,
@@ -273,9 +274,7 @@
         subset = self.template_set.getSharingSubset(
                 product=product, distribution=distribution,
                 sourcepackagename=sourcepackagename)
-        template_regex = self.options.template_names
-        if isinstance(template_regex, str):
-            template_regex = template_regex.decode('utf-8')
+        template_regex = six.ensure_text(self.options.template_names)
         equivalence_classes = subset.groupEquivalentPOTemplates(template_regex)
 
         class_count = len(equivalence_classes)


Follow ups