← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:pyupgrade-py3-services-2 into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:pyupgrade-py3-services-2 into launchpad:master.

Commit message:
lp.services.[n-z]*: Apply "pyupgrade --py3-plus"

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/413793
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:pyupgrade-py3-services-2 into launchpad:master.
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index 9042104..0177d98 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -36,3 +36,7 @@ fbed83f22424df8fa5647349493f78937a520db5
 7c02bb8d73dd3875e338ba7945d3f7f86d1fc743
 # apply pyupgrade --py3-plus to lp.scripts
 9d8a9b57add8d2ec9331d2c90353887f65314dfb
+# apply pyupgrade --py3-plus to lp.services.[a-m]*
+46f47b6eb925ce6d784c7a0ed47653da7a974110
+# apply pyupgrade --py3-plus to lp.services.[n-z]*
+f3f15787ebabe305fbf3e3ae6c0fd8ca7dfb4465
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 5531052..d633860 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -54,7 +54,7 @@ repos:
             |oci
             |registry
             |scripts
-            |services/[a-m]
+            |services
           )/
 -   repo: https://github.com/PyCQA/isort
     rev: 5.9.2
diff --git a/lib/lp/services/oauth/browser/__init__.py b/lib/lp/services/oauth/browser/__init__.py
index b0b5bb9..6bd11a6 100644
--- a/lib/lp/services/oauth/browser/__init__.py
+++ b/lib/lp/services/oauth/browser/__init__.py
@@ -15,7 +15,6 @@ from datetime import (
 from lazr.restful import HTTPResource
 import pytz
 import simplejson
-import six
 from zope.component import getUtility
 from zope.formlib.form import (
     Action,
@@ -86,7 +85,7 @@ class OAuthRequestTokenView(LaunchpadFormView, JSONTokenMixin):
         consumer_key = form.get('oauth_consumer_key')
         if not consumer_key:
             self.request.unauthorized(OAUTH_CHALLENGE)
-            return u''
+            return ''
 
         consumer_set = getUtility(IOAuthConsumerSet)
         consumer = consumer_set.getByKey(consumer_key)
@@ -94,7 +93,7 @@ class OAuthRequestTokenView(LaunchpadFormView, JSONTokenMixin):
             consumer = consumer_set.new(key=consumer_key)
 
         if not check_oauth_signature(self.request, consumer, None):
-            return u''
+            return ''
 
         token, secret = consumer.newRequestToken()
         if self.request.headers.get('Accept') == HTTPResource.JSON_TYPE:
@@ -107,7 +106,7 @@ class OAuthRequestTokenView(LaunchpadFormView, JSONTokenMixin):
                 ]
             return self.getJSONRepresentation(
                 permissions, token, secret=secret)
-        return u'oauth_token=%s&oauth_token_secret=%s' % (token.key, secret)
+        return 'oauth_token=%s&oauth_token_secret=%s' % (token.key, secret)
 
 
 def token_exists_and_is_not_reviewed(form, action):
@@ -340,14 +339,14 @@ class OAuthAuthorizeTokenView(LaunchpadFormView, JSONTokenMixin):
                          '("%s") not supported for desktop-wide use.')
                          % action.label)
 
-        super(OAuthAuthorizeTokenView, self).initialize()
+        super().initialize()
 
     def render(self):
         if self.request.headers.get('Accept') == HTTPResource.JSON_TYPE:
             permissions = [action.permission
                            for action in self.visible_actions]
             return self.getJSONRepresentation(permissions, self.token)
-        return super(OAuthAuthorizeTokenView, self).render()
+        return super().render()
 
     def storeTokenContext(self):
         """Store the context given by the consumer in this view."""
@@ -415,7 +414,7 @@ class OAuthAccessTokenView(LaunchpadView):
 
     def _set_status_and_error(self, error):
         self.request.response.setStatus(403)
-        return six.text_type(error)
+        return str(error)
 
     def __call__(self):
         """Create an access token and respond with its key/secret/context.
@@ -430,20 +429,20 @@ class OAuthAccessTokenView(LaunchpadView):
 
         if consumer is None:
             self.request.unauthorized(OAUTH_CHALLENGE)
-            return u''
+            return ''
 
         token = consumer.getRequestToken(form.get('oauth_token'))
         if token is None:
             self.request.unauthorized(OAUTH_CHALLENGE)
-            return u'No request token specified.'
+            return 'No request token specified.'
 
         if not check_oauth_signature(self.request, consumer, token):
-            return u'Invalid OAuth signature.'
+            return 'Invalid OAuth signature.'
 
         if not token.is_reviewed:
             self.request.unauthorized(OAUTH_CHALLENGE)
             return (
-                u"Request token has not yet been reviewed. Try again later.")
+                "Request token has not yet been reviewed. Try again later.")
 
         if token.permission == OAuthPermission.UNAUTHORIZED:
             return self._set_status_and_error(
@@ -457,6 +456,6 @@ class OAuthAccessTokenView(LaunchpadView):
         context_name = None
         if access_token.context is not None:
             context_name = access_token.context.name
-        body = u'oauth_token=%s&oauth_token_secret=%s&lp.context=%s' % (
+        body = 'oauth_token=%s&oauth_token_secret=%s&lp.context=%s' % (
             access_token.key, access_secret, context_name)
         return body
diff --git a/lib/lp/services/oauth/model.py b/lib/lp/services/oauth/model.py
index c7b44ad..3b48de6 100644
--- a/lib/lp/services/oauth/model.py
+++ b/lib/lp/services/oauth/model.py
@@ -96,10 +96,10 @@ class OAuthConsumer(OAuthBase, StormBase):
     date_created = DateTime(tzinfo=pytz.UTC, allow_none=False, default=UTC_NOW)
     disabled = Bool(allow_none=False, default=False)
     key = Unicode(allow_none=False)
-    _secret = Unicode(name='secret', allow_none=True, default=u'')
+    _secret = Unicode(name='secret', allow_none=True, default='')
 
     def __init__(self, key, secret):
-        super(OAuthConsumer, self).__init__()
+        super().__init__()
         self.key = key
         self._secret = sha256_digest(secret)
 
@@ -190,7 +190,7 @@ class OAuthConsumer(OAuthBase, StormBase):
 class OAuthConsumerSet:
     """See `IOAuthConsumerSet`."""
 
-    def new(self, key, secret=u''):
+    def new(self, key, secret=''):
         """See `IOAuthConsumerSet`."""
         assert self.getByKey(key) is None, (
             "The key '%s' is already in use by another consumer." % key)
@@ -218,7 +218,7 @@ class OAuthAccessToken(OAuthBase, StormBase):
     date_created = DateTime(tzinfo=pytz.UTC, allow_none=False, default=UTC_NOW)
     date_expires = DateTime(tzinfo=pytz.UTC, allow_none=True, default=None)
     key = Unicode(allow_none=False)
-    _secret = Unicode(name='secret', allow_none=True, default=u'')
+    _secret = Unicode(name='secret', allow_none=True, default='')
 
     permission = DBEnum(enum=AccessLevel, allow_none=False)
 
@@ -232,10 +232,10 @@ class OAuthAccessToken(OAuthBase, StormBase):
     distribution_id = Int(name='distribution', allow_none=True, default=None)
     distribution = Reference(distribution_id, 'Distribution.id')
 
-    def __init__(self, consumer, permission, key, secret=u'', person=None,
+    def __init__(self, consumer, permission, key, secret='', person=None,
                  date_expires=None, product=None, projectgroup=None,
                  distribution=None, sourcepackagename=None):
-        super(OAuthAccessToken, self).__init__()
+        super().__init__()
         self.consumer = consumer
         self.permission = permission
         self.key = key
@@ -287,7 +287,7 @@ class OAuthRequestToken(OAuthBase, StormBase):
     date_created = DateTime(tzinfo=pytz.UTC, allow_none=False, default=UTC_NOW)
     date_expires = DateTime(tzinfo=pytz.UTC, allow_none=True, default=None)
     key = Unicode(allow_none=False)
-    _secret = Unicode(name='secret', allow_none=True, default=u'')
+    _secret = Unicode(name='secret', allow_none=True, default='')
 
     permission = DBEnum(enum=OAuthPermission, allow_none=True, default=None)
     date_reviewed = DateTime(tzinfo=pytz.UTC, allow_none=True, default=None)
@@ -302,10 +302,10 @@ class OAuthRequestToken(OAuthBase, StormBase):
     distribution_id = Int(name='distribution', allow_none=True, default=None)
     distribution = Reference(distribution_id, 'Distribution.id')
 
-    def __init__(self, consumer, key, secret=u'', permission=None, person=None,
+    def __init__(self, consumer, key, secret='', permission=None, person=None,
                  date_expires=None, product=None, projectgroup=None,
                  distribution=None, sourcepackagename=None):
-        super(OAuthRequestToken, self).__init__()
+        super().__init__()
         self.consumer = consumer
         self.permission = permission
         self.key = key
diff --git a/lib/lp/services/oauth/tests/test_tokens.py b/lib/lp/services/oauth/tests/test_tokens.py
index d36f125..9a839df 100644
--- a/lib/lp/services/oauth/tests/test_tokens.py
+++ b/lib/lp/services/oauth/tests/test_tokens.py
@@ -14,7 +14,6 @@ from datetime import (
 import hashlib
 
 import pytz
-import six
 from zope.component import getUtility
 from zope.security.interfaces import Unauthorized
 from zope.security.proxy import removeSecurityProxy
@@ -47,7 +46,7 @@ class TestOAuth(TestCaseWithFactory):
 
     def setUp(self):
         """Set up some convenient data objects and timestamps."""
-        super(TestOAuth, self).setUp()
+        super().setUp()
 
         self.person = self.factory.makePerson()
         self.consumer = self.factory.makeOAuthConsumer()
@@ -61,7 +60,7 @@ class TestConsumerSet(TestOAuth):
     """Tests of the utility that manages OAuth consumers."""
 
     def setUp(self):
-        super(TestConsumerSet, self).setUp()
+        super().setUp()
         self.consumers = getUtility(IOAuthConsumerSet)
 
     def test_interface(self):
@@ -69,7 +68,7 @@ class TestConsumerSet(TestOAuth):
 
     def test_new(self):
         consumer = self.consumers.new(
-            self.factory.getUniqueUnicode(u"oauthconsumerkey"))
+            self.factory.getUniqueUnicode("oauthconsumerkey"))
         verifyObject(IOAuthConsumer, consumer)
 
     def test_new_wont_create_duplicate_consumer(self):
@@ -83,7 +82,7 @@ class TestConsumerSet(TestOAuth):
     def test_getByKey_returns_none_for_nonexistent_consumer(self):
         # There is no consumer called "oauthconsumerkey-nonexistent".
         nonexistent_key = self.factory.getUniqueUnicode(
-            u"oauthconsumerkey-nonexistent")
+            "oauthconsumerkey-nonexistent")
         self.assertEqual(self.consumers.getByKey(nonexistent_key), None)
 
 
@@ -92,7 +91,7 @@ class TestRequestTokenSet(TestOAuth):
 
     def setUp(self):
         """Set up a reference to the token list."""
-        super(TestRequestTokenSet, self).setUp()
+        super().setUp()
         self.tokens = getUtility(IOAuthRequestTokenSet)
 
     def test_getByKey(self):
@@ -100,7 +99,7 @@ class TestRequestTokenSet(TestOAuth):
         self.assertEqual(token, self.tokens.getByKey(token.key))
 
     def test_getByKey_returns_none_for_unused_key(self):
-        self.assertIsNone(self.tokens.getByKey(u"no-such-token"))
+        self.assertIsNone(self.tokens.getByKey("no-such-token"))
 
 
 class TestRequestTokens(TestOAuth):
@@ -109,7 +108,7 @@ class TestRequestTokens(TestOAuth):
     def test_newRequestToken(self):
         request_token, secret = self.consumer.newRequestToken()
         verifyObject(IOAuthRequestToken, request_token)
-        self.assertIsInstance(secret, six.text_type)
+        self.assertIsInstance(secret, str)
         self.assertEqual(
             removeSecurityProxy(request_token)._secret,
             hashlib.sha256(secret.encode('ASCII')).hexdigest())
@@ -147,7 +146,7 @@ class TestRequestTokens(TestOAuth):
         self.assertIsNone(consumer_2.getRequestToken(token_1.key))
 
     def test_getRequestToken_for_nonexistent_key_returns_none(self):
-        self.assertIsNone(self.consumer.getRequestToken(u"no-such-token"))
+        self.assertIsNone(self.consumer.getRequestToken("no-such-token"))
 
     def test_isSecretValid(self):
         token, secret = self.consumer.newRequestToken()
@@ -283,7 +282,7 @@ class TestAccessTokens(TestOAuth):
         request_token, access_token, access_secret = (
             self._exchange_request_token_for_access_token())
         verifyObject(IOAuthAccessToken, access_token)
-        self.assertIsInstance(access_secret, six.text_type)
+        self.assertIsInstance(access_secret, str)
         self.assertEqual(
             removeSecurityProxy(access_token)._secret,
             hashlib.sha256(access_secret.encode('ASCII')).hexdigest())
@@ -359,7 +358,7 @@ class TestAccessTokens(TestOAuth):
         request_token.review(self.person, OAuthPermission.WRITE_PRIVATE)
         token, secret = request_token.createAccessToken()
         self.assertTrue(token.isSecretValid(secret))
-        self.assertFalse(token.isSecretValid(secret + u'a'))
+        self.assertFalse(token.isSecretValid(secret + 'a'))
 
     def test_get_access_tokens_for_person(self):
         """It's possible to get a person's access tokens."""
@@ -396,7 +395,7 @@ class TestAccessTokens(TestOAuth):
 class TestHelperFunctions(TestOAuth):
 
     def setUp(self):
-        super(TestHelperFunctions, self).setUp()
+        super().setUp()
         self.context = self.factory.makeProduct()
 
     def test_oauth_access_token_for_creates_nonexistent_token(self):
diff --git a/lib/lp/services/openid/browser/openiddiscovery.py b/lib/lp/services/openid/browser/openiddiscovery.py
index 2781061..6949874 100644
--- a/lib/lp/services/openid/browser/openiddiscovery.py
+++ b/lib/lp/services/openid/browser/openiddiscovery.py
@@ -58,7 +58,7 @@ class XRDSContentNegotiationMixin:
             accept_content = six.ensure_text(
                 self.request.get('HTTP_ACCEPT', ''), encoding='ISO-8859-1')
             acceptable = getAcceptable(accept_content,
-                                       [u'text/html', YADIS_CONTENT_TYPE])
+                                       ['text/html', YADIS_CONTENT_TYPE])
             # Return the XRDS document if it is preferred to text/html.
             for mtype in acceptable:
                 if mtype == 'text/html':
@@ -73,7 +73,7 @@ class XRDSContentNegotiationMixin:
             # and chain to the default render() method.
             self.request.response.setHeader(
                 YADIS_HEADER_NAME, '%s/+xrds' % canonical_url(self.context))
-        return super(XRDSContentNegotiationMixin, self).render()
+        return super().render()
 
     @cachedproperty
     def openid_server_url(self):
diff --git a/lib/lp/services/openid/extensions/tests/test_macaroon.py b/lib/lp/services/openid/extensions/tests/test_macaroon.py
index c217a14..5d57ce1 100644
--- a/lib/lp/services/openid/extensions/tests/test_macaroon.py
+++ b/lib/lp/services/openid/extensions/tests/test_macaroon.py
@@ -27,7 +27,7 @@ class TestGetMacaroonNS(TestCase):
     layer = ZopelessDatabaseLayer
 
     def setUp(self):
-        super(TestGetMacaroonNS, self).setUp()
+        super().setUp()
         params = {
             'openid.mode': 'checkid_setup',
             'openid.trust_root': 'http://localhost/',
@@ -57,7 +57,7 @@ class TestMacaroonRequest(TestCaseWithFactory):
     layer = ZopelessDatabaseLayer
 
     def setUp(self):
-        super(TestMacaroonRequest, self).setUp()
+        super().setUp()
         self.caveat_id = self.factory.getUniqueUnicode()
         self.req = MacaroonRequest(self.caveat_id)
 
diff --git a/lib/lp/services/openid/fetcher.py b/lib/lp/services/openid/fetcher.py
index 22c5a18..88343a1 100644
--- a/lib/lp/services/openid/fetcher.py
+++ b/lib/lp/services/openid/fetcher.py
@@ -27,8 +27,7 @@ class WSGIFriendlyUrllib2Fetcher(Urllib2Fetcher):
             headers = {
                 wsgi_native_string(key): wsgi_native_string(value)
                 for key, value in headers.items()}
-        return super(WSGIFriendlyUrllib2Fetcher, self).fetch(
-            url, body=body, headers=headers)
+        return super().fetch(url, body=body, headers=headers)
 
 
 def set_default_openid_fetcher():
diff --git a/lib/lp/services/openid/model/baseopenidstore.py b/lib/lp/services/openid/model/baseopenidstore.py
index 6cc18cb..470caff 100644
--- a/lib/lp/services/openid/model/baseopenidstore.py
+++ b/lib/lp/services/openid/model/baseopenidstore.py
@@ -38,7 +38,7 @@ class BaseStormOpenIDAssociation:
     assoc_type = Unicode()
 
     def __init__(self, server_url, association):
-        super(BaseStormOpenIDAssociation, self).__init__()
+        super().__init__()
         self.server_url = six.ensure_text(server_url)
         self.handle = six.ensure_text(association.handle, 'ASCII')
         self.update(association)
@@ -68,7 +68,7 @@ class BaseStormOpenIDNonce:
     salt = Unicode()
 
     def __init__(self, server_url, timestamp, salt):
-        super(BaseStormOpenIDNonce, self).__init__()
+        super().__init__()
         self.server_url = server_url
         self.timestamp = timestamp
         self.salt = salt
diff --git a/lib/lp/services/openid/tests/test_baseopenidstore.py b/lib/lp/services/openid/tests/test_baseopenidstore.py
index 5e86ef9..96984a8 100644
--- a/lib/lp/services/openid/tests/test_baseopenidstore.py
+++ b/lib/lp/services/openid/tests/test_baseopenidstore.py
@@ -24,22 +24,22 @@ class BaseStormOpenIDStoreTestsMixin:
         self.assertIsInstance(self.store, BaseStormOpenIDStore)
 
     def test_storeAssociation(self):
-        self.store.storeAssociation(u'server-url\xA9', Association(
+        self.store.storeAssociation('server-url\xA9', Association(
             b'handle', b'secret', 42, 600, 'HMAC-SHA1'))
         db_assoc = IMasterStore(self.store.Association).get(
-            self.store.Association, (u'server-url\xA9', u'handle'))
-        self.assertEqual(db_assoc.server_url, u'server-url\xA9')
-        self.assertEqual(db_assoc.handle, u'handle')
+            self.store.Association, ('server-url\xA9', 'handle'))
+        self.assertEqual(db_assoc.server_url, 'server-url\xA9')
+        self.assertEqual(db_assoc.handle, 'handle')
         self.assertEqual(db_assoc.secret, b'secret')
         self.assertEqual(db_assoc.issued, 42)
         self.assertEqual(db_assoc.lifetime, 600)
-        self.assertEqual(db_assoc.assoc_type, u'HMAC-SHA1')
+        self.assertEqual(db_assoc.assoc_type, 'HMAC-SHA1')
 
     def test_storeAssociation_update_existing(self):
         self.store.storeAssociation('server-url', Association(
             b'handle', b'secret', 42, 600, 'HMAC-SHA1'))
         db_assoc = IMasterStore(self.store.Association).get(
-            self.store.Association, (u'server-url', u'handle'))
+            self.store.Association, ('server-url', 'handle'))
         self.assertNotEqual(db_assoc, None)
 
         # Now update the association with new information.
@@ -48,7 +48,7 @@ class BaseStormOpenIDStoreTestsMixin:
         self.assertEqual(db_assoc.secret, b'secret2')
         self.assertEqual(db_assoc.issued, 420)
         self.assertEqual(db_assoc.lifetime, 900)
-        self.assertEqual(db_assoc.assoc_type, u'HMAC-SHA256')
+        self.assertEqual(db_assoc.assoc_type, 'HMAC-SHA256')
 
     def test_getAssociation(self):
         timestamp = int(time.time())
@@ -79,7 +79,7 @@ class BaseStormOpenIDStoreTestsMixin:
 
         store = IMasterStore(self.store.Association)
         db_assoc = store.get(
-            self.store.Association, (u'server-url', u'handle'))
+            self.store.Association, ('server-url', 'handle'))
         self.assertEqual(db_assoc, None)
 
     def test_getAssociation_no_handle(self):
@@ -115,7 +115,7 @@ class BaseStormOpenIDStoreTestsMixin:
             self.store.useNonce('server-url', timestamp, 'salt'), True)
         storm_store = IMasterStore(self.store.Nonce)
         new_nonce = storm_store.get(
-            self.store.Nonce, (u'server-url', timestamp, u'salt'))
+            self.store.Nonce, ('server-url', timestamp, 'salt'))
         self.assertIsNot(None, new_nonce)
 
         self.assertEqual(
diff --git a/lib/lp/services/openid/tests/test_openidconsumer.py b/lib/lp/services/openid/tests/test_openidconsumer.py
index 6238c3b..1d5fff7 100644
--- a/lib/lp/services/openid/tests/test_openidconsumer.py
+++ b/lib/lp/services/openid/tests/test_openidconsumer.py
@@ -19,5 +19,5 @@ class OpenIDConsumerStoreTests(BaseStormOpenIDStoreTestsMixin, TestCase):
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        super(OpenIDConsumerStoreTests, self).setUp()
+        super().setUp()
         self.store = getUtility(IOpenIDConsumerStore)
diff --git a/lib/lp/services/osutils.py b/lib/lp/services/osutils.py
index 1a4e193..2e8494b 100644
--- a/lib/lp/services/osutils.py
+++ b/lib/lp/services/osutils.py
@@ -26,8 +26,6 @@ from signal import (
     )
 import time
 
-import six
-
 
 def remove_tree(path):
     """Remove the tree at 'path' from disk."""
@@ -41,7 +39,7 @@ def set_environ(new_values):
     :return: a dict of the old values
     """
     old_values = {}
-    for name, value in six.iteritems(new_values):
+    for name, value in new_values.items():
         old_values[name] = os.environ.get(name)
         if value is None:
             if old_values[name] is not None:
@@ -92,7 +90,7 @@ def open_for_writing(filename, mode, dirmode=0o777):
     """
     try:
         return open(filename, mode)
-    except IOError as e:
+    except OSError as e:
         if e.errno == errno.ENOENT:
             os.makedirs(os.path.dirname(filename), mode=dirmode)
             return open(filename, mode)
diff --git a/lib/lp/services/pidfile.py b/lib/lp/services/pidfile.py
index 6271fa9..71f8942 100644
--- a/lib/lp/services/pidfile.py
+++ b/lib/lp/services/pidfile.py
@@ -82,7 +82,7 @@ def get_pid(service_name, use_config=None):
         with open(pidfile) as f:
             pid = f.read()
         return int(pid)
-    except IOError:
+    except OSError:
         return None
     except ValueError:
         raise ValueError("Invalid PID %s" % repr(pid))
diff --git a/lib/lp/services/profile/tests.py b/lib/lp/services/profile/tests.py
index 1b41785..5d18aed 100644
--- a/lib/lp/services/profile/tests.py
+++ b/lib/lp/services/profile/tests.py
@@ -171,7 +171,7 @@ class TestCleanupProfiler(BaseTest):
             profile._profilers.profiler = None
         profile._profilers.actions = None
         profile._profilers.profiling = False
-        super(TestCleanupProfiler, self).tearDown()
+        super().tearDown()
 
 
 class TestRequestStartHandler(TestCleanupProfiler):
@@ -358,7 +358,7 @@ class TestRequestStartHandler(TestCleanupProfiler):
 class BaseRequestEndHandlerTest(BaseTest):
 
     def setUp(self):
-        super(BaseRequestEndHandlerTest, self).setUp()
+        super().setUp()
         self.profile_dir = self.makeTemporaryDirectory()
         self.memory_profile_log = os.path.join(self.profile_dir, 'memory_log')
         self.pushConfig('profiling', profile_dir=self.profile_dir)
diff --git a/lib/lp/services/propertycache.py b/lib/lp/services/propertycache.py
index 8e587d9..41d599d 100644
--- a/lib/lp/services/propertycache.py
+++ b/lib/lp/services/propertycache.py
@@ -16,7 +16,6 @@ __all__ = [
 
 from functools import partial
 
-import six
 from zope.interface import (
     implementer,
     Interface,
@@ -134,7 +133,7 @@ def cachedproperty(name_or_function):
 
     See `doc/propertycache.txt` for usage.
     """
-    if isinstance(name_or_function, six.string_types):
+    if isinstance(name_or_function, str):
         name = name_or_function
         return partial(CachedProperty, name=name)
     else:
diff --git a/lib/lp/services/rabbit/server.py b/lib/lp/services/rabbit/server.py
index eb53320..136a941 100644
--- a/lib/lp/services/rabbit/server.py
+++ b/lib/lp/services/rabbit/server.py
@@ -20,7 +20,7 @@ class RabbitServer(rabbitfixture.server.RabbitServer):
     """
 
     def setUp(self):
-        super(RabbitServer, self).setUp()
+        super().setUp()
         self.config.service_config = dedent("""\
             [rabbitmq]
             host: localhost:%d
diff --git a/lib/lp/services/scripts/base.py b/lib/lp/services/scripts/base.py
index ff55baa..9abdbb2 100644
--- a/lib/lp/services/scripts/base.py
+++ b/lib/lp/services/scripts/base.py
@@ -382,8 +382,7 @@ class LaunchpadCronScript(LaunchpadScript):
 
     def __init__(self, name=None, dbuser=None, test_args=None, logger=None,
                  ignore_cron_control=False):
-        super(LaunchpadCronScript, self).__init__(
-            name, dbuser, test_args=test_args, logger=logger)
+        super().__init__(name, dbuser, test_args=test_args, logger=logger)
 
         # self.name is used instead of the name argument, since it may have
         # have been overridden by command-line parameters or by
diff --git a/lib/lp/services/scripts/model/scriptactivity.py b/lib/lp/services/scripts/model/scriptactivity.py
index 4125f2e..728252c 100644
--- a/lib/lp/services/scripts/model/scriptactivity.py
+++ b/lib/lp/services/scripts/model/scriptactivity.py
@@ -39,7 +39,7 @@ class ScriptActivity(StormBase):
     date_completed = DateTime(tzinfo=pytz.UTC, allow_none=False)
 
     def __init__(self, name, hostname, date_started, date_completed):
-        super(ScriptActivity, self).__init__()
+        super().__init__()
         self.name = name
         self.hostname = hostname
         self.date_started = date_started
diff --git a/lib/lp/services/scripts/tests/test_cronscript_enabled.py b/lib/lp/services/scripts/tests/test_cronscript_enabled.py
index 77ca538..b542014 100644
--- a/lib/lp/services/scripts/tests/test_cronscript_enabled.py
+++ b/lib/lp/services/scripts/tests/test_cronscript_enabled.py
@@ -17,7 +17,7 @@ from lp.testing import TestCase
 class TestCronscriptEnabled(TestCase):
 
     def setUp(self):
-        super(TestCronscriptEnabled, self).setUp()
+        super().setUp()
         self.log = BufferLogger()
 
     def makeConfig(self, body):
diff --git a/lib/lp/services/scripts/tests/test_feature_controller.py b/lib/lp/services/scripts/tests/test_feature_controller.py
index afc8c70..bab3eed 100644
--- a/lib/lp/services/scripts/tests/test_feature_controller.py
+++ b/lib/lp/services/scripts/tests/test_feature_controller.py
@@ -23,7 +23,7 @@ class FakeScript(LaunchpadScript):
     observed_feature_controller = object()
 
     def __init__(self, name):
-        super(FakeScript, self).__init__(name=name, test_args=[])
+        super().__init__(name=name, test_args=[])
 
     def main(self):
         self.observed_feature_controller = get_relevant_feature_controller()
@@ -39,12 +39,12 @@ class TestScriptFeatureController(TestCase):
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        super(TestScriptFeatureController, self).setUp()
+        super().setUp()
         self.original_controller = get_relevant_feature_controller()
 
     def tearDown(self):
         install_feature_controller(self.original_controller)
-        super(TestScriptFeatureController, self).tearDown()
+        super().tearDown()
 
     def test_script_installs_script_feature_controller(self):
         script = FakeScript(name="bongo")
diff --git a/lib/lp/services/signing/model/signingkey.py b/lib/lp/services/signing/model/signingkey.py
index d61c102..7fe0eba 100644
--- a/lib/lp/services/signing/model/signingkey.py
+++ b/lib/lp/services/signing/model/signingkey.py
@@ -76,7 +76,7 @@ class SigningKey(StormBase):
         :param fingerprint: The key's fingerprint
         :param public_key: The key's public key (raw; not base64-encoded)
         """
-        super(SigningKey, self).__init__()
+        super().__init__()
         self.key_type = key_type
         self.fingerprint = fingerprint
         self.public_key = public_key
@@ -168,7 +168,7 @@ class ArchiveSigningKey(StormBase):
 
     def __init__(self, archive=None, earliest_distro_series=None,
                  signing_key=None):
-        super(ArchiveSigningKey, self).__init__()
+        super().__init__()
         self.archive = archive
         self.signing_key = signing_key
         self.key_type = signing_key.key_type
diff --git a/lib/lp/services/signing/testing/fakesigning.py b/lib/lp/services/signing/testing/fakesigning.py
index fdbb3b5..3da5291 100644
--- a/lib/lp/services/signing/testing/fakesigning.py
+++ b/lib/lp/services/signing/testing/fakesigning.py
@@ -21,13 +21,13 @@ from nacl.utils import random
 from twisted.web import resource
 
 
-class ServiceKeyResource(resource.Resource, object):
+class ServiceKeyResource(resource.Resource):
     """Resource implementing /service-key."""
 
     isLeaf = True
 
     def __init__(self, service_public_key):
-        super(ServiceKeyResource, self).__init__()
+        super().__init__()
         self.service_public_key = service_public_key
 
     def render_GET(self, request):
@@ -38,7 +38,7 @@ class ServiceKeyResource(resource.Resource, object):
             }).encode("UTF-8")
 
 
-class NonceResource(resource.Resource, object):
+class NonceResource(resource.Resource):
     """Resource implementing /nonce.
 
     Note that this fake signing service does not check that nonces are only
@@ -48,7 +48,7 @@ class NonceResource(resource.Resource, object):
     isLeaf = True
 
     def __init__(self):
-        super(NonceResource, self).__init__()
+        super().__init__()
         self.nonces = []
 
     def render_POST(self, request):
@@ -58,11 +58,11 @@ class NonceResource(resource.Resource, object):
         return json.dumps({"nonce": nonce}).encode("UTF-8")
 
 
-class BoxedAuthenticationResource(resource.Resource, object):
+class BoxedAuthenticationResource(resource.Resource):
     """Base for resources that use boxed authentication."""
 
     def __init__(self, service_private_key, client_public_key):
-        super(BoxedAuthenticationResource, self).__init__()
+        super().__init__()
         self.box = Box(service_private_key, client_public_key)
 
     def _decrypt(self, request):
@@ -84,8 +84,7 @@ class GenerateResource(BoxedAuthenticationResource):
     isLeaf = True
 
     def __init__(self, service_private_key, client_public_key, keys):
-        super(GenerateResource, self).__init__(
-            service_private_key, client_public_key)
+        super().__init__(service_private_key, client_public_key)
         self.keys = keys
         self.requests = []
 
@@ -112,8 +111,7 @@ class SignResource(BoxedAuthenticationResource):
     isLeaf = True
 
     def __init__(self, service_private_key, client_public_key, keys):
-        super(SignResource, self).__init__(
-            service_private_key, client_public_key)
+        super().__init__(service_private_key, client_public_key)
         self.keys = keys
         self.requests = []
 
@@ -138,8 +136,7 @@ class InjectResource(BoxedAuthenticationResource):
     isLeaf = True
 
     def __init__(self, service_private_key, client_public_key, keys):
-        super(InjectResource, self).__init__(
-            service_private_key, client_public_key)
+        super().__init__(service_private_key, client_public_key)
         self.keys = keys
         self.requests = []
 
@@ -157,7 +154,7 @@ class InjectResource(BoxedAuthenticationResource):
             request, json.dumps(response_payload).encode("UTF-8"))
 
 
-class SigningServiceResource(resource.Resource, object):
+class SigningServiceResource(resource.Resource):
     """Root resource for the fake signing service."""
 
     def __init__(self):
diff --git a/lib/lp/services/signing/testing/fixture.py b/lib/lp/services/signing/testing/fixture.py
index 2d59d1c..635834f 100644
--- a/lib/lp/services/signing/testing/fixture.py
+++ b/lib/lp/services/signing/testing/fixture.py
@@ -48,7 +48,7 @@ class SigningServiceFixture(TacTestFixture):
                 config.root, "logs", "fakesigning-%s.pid" % self.daemon_port)
         assert self.daemon_port is not None
 
-        super(SigningServiceFixture, self).setUp(
+        super().setUp(
             spew=spew, umask=umask,
             python_path=os.path.join(config.root, "bin", "py"),
             twistd_script=os.path.join(config.root, "bin", "twistd"))
diff --git a/lib/lp/services/signing/tests/test_proxy.py b/lib/lp/services/signing/tests/test_proxy.py
index 807e6ef..bbe8499 100644
--- a/lib/lp/services/signing/tests/test_proxy.py
+++ b/lib/lp/services/signing/tests/test_proxy.py
@@ -69,7 +69,7 @@ class SigningServiceResponseFactory:
         self.b64_generated_public_key = base64.b64encode(
             bytes(self.generated_public_key)).decode("UTF-8")
         self.generated_fingerprint = (
-            u'338D218488DFD597D8FCB9C328C3E9D9ADA16CEE')
+            '338D218488DFD597D8FCB9C328C3E9D9ADA16CEE')
 
         self.signed_msg_template = b"%d::signed!"
 
diff --git a/lib/lp/services/signing/tests/test_signingkey.py b/lib/lp/services/signing/tests/test_signingkey.py
index 53011ec..8ae81a8 100644
--- a/lib/lp/services/signing/tests/test_signingkey.py
+++ b/lib/lp/services/signing/tests/test_signingkey.py
@@ -44,7 +44,7 @@ class TestSigningKey(TestCaseWithFactory, TestWithFixtures):
     layer = ZopelessDatabaseLayer
 
     def setUp(self, *args, **kwargs):
-        super(TestSigningKey, self).setUp(*args, **kwargs)
+        super().setUp(*args, **kwargs)
         self.signing_service = SigningServiceResponseFactory()
 
         client = removeSecurityProxy(getUtility(ISigningServiceClient))
@@ -72,7 +72,7 @@ class TestSigningKey(TestCaseWithFactory, TestWithFixtures):
     def test_generate_signing_key_saves_correctly(self):
         self.signing_service.addResponses(self)
 
-        key = SigningKey.generate(SigningKeyType.UEFI, u"this is my key")
+        key = SigningKey.generate(SigningKeyType.UEFI, "this is my key")
         self.assertIsInstance(key, SigningKey)
 
         store = IMasterStore(SigningKey)
@@ -100,7 +100,7 @@ class TestSigningKey(TestCaseWithFactory, TestWithFixtures):
 
         key = SigningKey.inject(
             SigningKeyType.KMOD, bytes(priv_key), bytes(pub_key),
-            u"This is a test key", created_at)
+            "This is a test key", created_at)
         self.assertIsInstance(key, SigningKey)
 
         store = IMasterStore(SigningKey)
@@ -114,7 +114,7 @@ class TestSigningKey(TestCaseWithFactory, TestWithFixtures):
         self.assertEqual(
             self.signing_service.generated_fingerprint, db_key.fingerprint)
         self.assertEqual(bytes(pub_key), db_key.public_key)
-        self.assertEqual(u"This is a test key", db_key.description)
+        self.assertEqual("This is a test key", db_key.description)
         self.assertEqual(created_at, db_key.date_created)
 
     @responses.activate
@@ -127,7 +127,7 @@ class TestSigningKey(TestCaseWithFactory, TestWithFixtures):
 
         key = SigningKey.inject(
             SigningKeyType.KMOD, bytes(priv_key), bytes(pub_key),
-            u"This is a test key", created_at)
+            "This is a test key", created_at)
         self.assertIsInstance(key, SigningKey)
 
         store = IMasterStore(SigningKey)
@@ -136,7 +136,7 @@ class TestSigningKey(TestCaseWithFactory, TestWithFixtures):
         # This should give back the same key
         new_key = SigningKey.inject(
             SigningKeyType.KMOD, bytes(priv_key), bytes(pub_key),
-            u"This is a test key with another description", created_at)
+            "This is a test key with another description", created_at)
         store.flush()
 
         self.assertEqual(key.id, new_key.id)
@@ -147,9 +147,9 @@ class TestSigningKey(TestCaseWithFactory, TestWithFixtures):
         self.signing_service.addResponses(self)
 
         s = SigningKey(
-            SigningKeyType.UEFI, u"a fingerprint",
+            SigningKeyType.UEFI, "a fingerprint",
             bytes(self.signing_service.generated_public_key),
-            description=u"This is my key!")
+            description="This is my key!")
         signed = s.sign(b"secure message", "message_name")
 
         # Checks if the returned value is actually the returning value from
@@ -163,7 +163,7 @@ class TestSigningKey(TestCaseWithFactory, TestWithFixtures):
                     self.signing_service._decryptPayload,
                     MatchesDict({
                         "key-type": Equals("UEFI"),
-                        "fingerprint": Equals(u"a fingerprint"),
+                        "fingerprint": Equals("a fingerprint"),
                         "message-name": Equals("message_name"),
                         "message": Equals(
                             base64.b64encode(
@@ -177,9 +177,9 @@ class TestSigningKey(TestCaseWithFactory, TestWithFixtures):
         self.signing_service.addResponses(self)
 
         s = SigningKey(
-            SigningKeyType.OPENPGP, u"a fingerprint",
+            SigningKeyType.OPENPGP, "a fingerprint",
             bytes(self.signing_service.generated_public_key),
-            description=u"This is my key!")
+            description="This is my key!")
         s.sign(b"secure message", "message_name")
         s.sign(b"another message", "another_name", mode=SigningMode.CLEAR)
 
@@ -192,7 +192,7 @@ class TestSigningKey(TestCaseWithFactory, TestWithFixtures):
                     self.signing_service._decryptPayload,
                     MatchesDict({
                         "key-type": Equals("OPENPGP"),
-                        "fingerprint": Equals(u"a fingerprint"),
+                        "fingerprint": Equals("a fingerprint"),
                         "message-name": Equals("message_name"),
                         "message": Equals(
                             base64.b64encode(
@@ -207,7 +207,7 @@ class TestSigningKey(TestCaseWithFactory, TestWithFixtures):
                     self.signing_service._decryptPayload,
                     MatchesDict({
                         "key-type": Equals("OPENPGP"),
-                        "fingerprint": Equals(u"a fingerprint"),
+                        "fingerprint": Equals("a fingerprint"),
                         "message-name": Equals("another_name"),
                         "message": Equals(
                             base64.b64encode(
@@ -220,10 +220,10 @@ class TestSigningKey(TestCaseWithFactory, TestWithFixtures):
         self.signing_service.addResponses(self)
 
         s = SigningKey(
-            SigningKeyType.UEFI, u"a fingerprint",
+            SigningKeyType.UEFI, "a fingerprint",
             bytes(self.signing_service.generated_public_key),
-            description=u"This is my key!")
-        self.assertIsNone(s.addAuthorization(u"another-client"))
+            description="This is my key!")
+        self.assertIsNone(s.addAuthorization("another-client"))
 
         self.assertEqual(3, len(responses.calls))
         self.assertThat(
@@ -234,8 +234,8 @@ class TestSigningKey(TestCaseWithFactory, TestWithFixtures):
                     self.signing_service._decryptPayload,
                     MatchesDict({
                         "key-type": Equals("UEFI"),
-                        "fingerprint": Equals(u"a fingerprint"),
-                        "client-name": Equals(u"another-client"),
+                        "fingerprint": Equals("a fingerprint"),
+                        "client-name": Equals("another-client"),
                         }))))
 
 
@@ -243,7 +243,7 @@ class TestArchiveSigningKey(TestCaseWithFactory):
     layer = ZopelessDatabaseLayer
 
     def setUp(self, *args, **kwargs):
-        super(TestArchiveSigningKey, self).setUp(*args, **kwargs)
+        super().setUp(*args, **kwargs)
         self.signing_service = SigningServiceResponseFactory()
 
         client = removeSecurityProxy(getUtility(ISigningServiceClient))
@@ -273,7 +273,7 @@ class TestArchiveSigningKey(TestCaseWithFactory):
         distro_series = archive.distribution.series[0]
 
         arch_key = getUtility(IArchiveSigningKeySet).generate(
-            SigningKeyType.UEFI, u"some description", archive,
+            SigningKeyType.UEFI, "some description", archive,
             earliest_distro_series=distro_series)
 
         store = Store.of(arch_key)
@@ -288,7 +288,7 @@ class TestArchiveSigningKey(TestCaseWithFactory):
             earliest_distro_series=distro_series))
 
         self.assertThat(db_arch_key.signing_key, MatchesStructure.byEquality(
-            key_type=SigningKeyType.UEFI, description=u"some description",
+            key_type=SigningKeyType.UEFI, description="some description",
             fingerprint=self.signing_service.generated_fingerprint,
             public_key=bytes(self.signing_service.generated_public_key)))
 
@@ -305,7 +305,7 @@ class TestArchiveSigningKey(TestCaseWithFactory):
         now = datetime.now().replace(tzinfo=utc)
         arch_key = getUtility(IArchiveSigningKeySet).inject(
             SigningKeyType.UEFI, bytes(priv_key), bytes(pub_key),
-            u"Some description", now, archive,
+            "Some description", now, archive,
             earliest_distro_series=distro_series)
 
         store = Store.of(arch_key)
@@ -320,7 +320,7 @@ class TestArchiveSigningKey(TestCaseWithFactory):
             earliest_distro_series=distro_series))
 
         self.assertThat(db_arch_key.signing_key, MatchesStructure.byEquality(
-            key_type=SigningKeyType.UEFI, description=u"Some description",
+            key_type=SigningKeyType.UEFI, description="Some description",
             fingerprint=self.signing_service.generated_fingerprint,
             public_key=bytes(pub_key)))
 
@@ -337,7 +337,7 @@ class TestArchiveSigningKey(TestCaseWithFactory):
         now = datetime.now().replace(tzinfo=utc)
         arch_key = getUtility(IArchiveSigningKeySet).inject(
             SigningKeyType.UEFI, bytes(priv_key), bytes(pub_key),
-            u"Some description", now, archive,
+            "Some description", now, archive,
             earliest_distro_series=distro_series)
 
         store = Store.of(arch_key)
@@ -351,7 +351,7 @@ class TestArchiveSigningKey(TestCaseWithFactory):
             earliest_distro_series=distro_series))
 
         self.assertThat(db_arch_key.signing_key, MatchesStructure.byEquality(
-            key_type=SigningKeyType.UEFI, description=u"Some description",
+            key_type=SigningKeyType.UEFI, description="Some description",
             fingerprint=self.signing_service.generated_fingerprint,
             public_key=bytes(pub_key)))
 
@@ -361,7 +361,7 @@ class TestArchiveSigningKey(TestCaseWithFactory):
 
         another_arch_key = getUtility(IArchiveSigningKeySet).inject(
             SigningKeyType.UEFI, bytes(priv_key), bytes(pub_key),
-            u"Another description", now, another_archive)
+            "Another description", now, another_archive)
 
         rs = store.find(ArchiveSigningKey)
         self.assertEqual(2, rs.count())
diff --git a/lib/lp/services/sitesearch/tests/test_bing.py b/lib/lp/services/sitesearch/tests/test_bing.py
index 5ae18ac..18e9e00 100644
--- a/lib/lp/services/sitesearch/tests/test_bing.py
+++ b/lib/lp/services/sitesearch/tests/test_bing.py
@@ -37,7 +37,7 @@ class TestBingSearchService(TestCase):
     layer = BingLaunchpadFunctionalLayer
 
     def setUp(self):
-        super(TestBingSearchService, self).setUp()
+        super().setUp()
         self.search_service = BingSearchService()
         self.base_path = os.path.normpath(
             os.path.join(os.path.dirname(__file__), 'data'))
@@ -91,7 +91,7 @@ class TestBingSearchService(TestCase):
         """
         file_name = os.path.join(
             self.base_path, 'bingsearchservice-incompatible-matches.json')
-        with open(file_name, 'r') as response_file:
+        with open(file_name) as response_file:
             response = json.loads(response_file.read())
         self.assertEqual('~25', response['webPages']['totalEstimatedMatches'])
 
@@ -106,7 +106,7 @@ class TestBingSearchService(TestCase):
         """
         file_name = os.path.join(
             self.base_path, 'bingsearchservice-negative-total.json')
-        with open(file_name, 'r') as response_file:
+        with open(file_name) as response_file:
             response = json.loads(response_file.read())
         self.assertEqual(-25, response['webPages']['totalEstimatedMatches'])
 
@@ -122,7 +122,7 @@ class TestBingSearchService(TestCase):
         """
         file_name = os.path.join(
             self.base_path, 'bingsearchservice-missing-title.json')
-        with open(file_name, 'r') as response_file:
+        with open(file_name) as response_file:
             response = json.loads(response_file.read())
         self.assertThat(response['webPages']['value'], HasLength(2))
 
@@ -142,7 +142,7 @@ class TestBingSearchService(TestCase):
         """
         file_name = os.path.join(
             self.base_path, 'bingsearchservice-missing-summary.json')
-        with open(file_name, 'r') as response_file:
+        with open(file_name) as response_file:
             response = json.loads(response_file.read())
         self.assertThat(response['webPages']['value'], HasLength(2))
 
@@ -160,7 +160,7 @@ class TestBingSearchService(TestCase):
         """
         file_name = os.path.join(
             self.base_path, 'bingsearchservice-missing-url.json')
-        with open(file_name, 'r') as response_file:
+        with open(file_name) as response_file:
             response = json.loads(response_file.read())
         self.assertThat(response['webPages']['value'], HasLength(2))
 
@@ -182,7 +182,7 @@ class TestBingSearchService(TestCase):
         """
         file_name = os.path.join(
             self.base_path, 'bingsearchservice-no-meaningful-results.json')
-        with open(file_name, 'r') as response_file:
+        with open(file_name) as response_file:
             response = json.loads(response_file.read())
         self.assertThat(response['webPages']['value'], HasLength(1))
 
diff --git a/lib/lp/services/sitesearch/tests/test_pagematch.py b/lib/lp/services/sitesearch/tests/test_pagematch.py
index ed88889..fadc22b 100644
--- a/lib/lp/services/sitesearch/tests/test_pagematch.py
+++ b/lib/lp/services/sitesearch/tests/test_pagematch.py
@@ -16,12 +16,12 @@ class TestPageMatchURLHandling(TestCase):
 
     def test_attributes(self):
         p = PageMatch(
-            u'Unicode Titles in Launchpad',
+            'Unicode Titles in Launchpad',
             'http://example.com/unicode-titles',
-            u'Unicode Titles is a modest project using Unicode.')
+            'Unicode Titles is a modest project using Unicode.')
         self.assertThat(p, MatchesStructure.byEquality(
-            title=u'Unicode Titles in Launchpad',
-            summary=u'Unicode Titles is a modest project using Unicode.',
+            title='Unicode Titles in Launchpad',
+            summary='Unicode Titles is a modest project using Unicode.',
             url='http://example.com/unicode-titles',
             ))
 
@@ -31,9 +31,9 @@ class TestPageMatchURLHandling(TestCase):
         key config.vhost.mainsite.hostname.
         """
         p = PageMatch(
-            u'Bug #456 in Unicode title: "testrunner hates Unicode"',
+            'Bug #456 in Unicode title: "testrunner hates Unicode"',
             'https://bugs.launchpad.net/unicode-titles/+bug/456',
-            u'The Zope testrunner likes ASCII more than Unicode.')
+            'The Zope testrunner likes ASCII more than Unicode.')
         self.assertEqual(
             'http://bugs.launchpad.test/unicode-titles/+bug/456', p.url)
 
@@ -42,9 +42,9 @@ class TestPageMatchURLHandling(TestCase):
         slashes.
         """
         p = PageMatch(
-            u'Ubuntu in Launchpad',
+            'Ubuntu in Launchpad',
             'https://launchpad.net/ubuntu/',
-            u'Ubuntu also includes more software than any other operating')
+            'Ubuntu also includes more software than any other operating')
         self.assertEqual('http://launchpad.test/ubuntu', p.url)
 
     def test_rewrite_url_exceptions(self):
@@ -54,9 +54,9 @@ class TestPageMatchURLHandling(TestCase):
         that site will be preserved.
         """
         p = PageMatch(
-            u'OpenID',
+            'OpenID',
             'https://help.launchpad.net/OpenID',
-            u'Launchpad uses OpenID.')
+            'Launchpad uses OpenID.')
         self.assertEqual('https://help.launchpad.net/OpenID', p.url)
 
     def test_rewrite_url_handles_invalid_data(self):
diff --git a/lib/lp/services/sitesearch/testservice.py b/lib/lp/services/sitesearch/testservice.py
index 5a475c1..cd70236 100644
--- a/lib/lp/services/sitesearch/testservice.py
+++ b/lib/lp/services/sitesearch/testservice.py
@@ -90,7 +90,7 @@ def service_is_available(host, port, timeout=2.0):
     try:
         try:
             sock.connect((host, port))
-        except socket.error:
+        except OSError:
             return False
         else:
             return True
@@ -113,7 +113,7 @@ def wait_for_service(host, port, timeout=15.0):
         while True:
             try:
                 sock.connect((host, port))
-            except socket.error as err:
+            except OSError as err:
                 if err.args[0] in [errno.ECONNREFUSED, errno.ECONNABORTED]:
                     elapsed = (time.time() - start)
                     if elapsed > timeout:
@@ -145,7 +145,7 @@ def wait_for_service_shutdown(host, port, seconds_to_wait=10.0):
             try:
                 sock.connect((host, port))
                 sock.close()
-            except socket.error as err:
+            except OSError as err:
                 if err.args[0] == errno.ECONNREFUSED:
                     # Success!  The socket is closed.
                     return
@@ -192,7 +192,7 @@ def kill_running_process(service_name, host, port):
     """Find and kill any running web service processes."""
     try:
         pid = get_pid(service_name)
-    except IOError:
+    except OSError:
         # We could not find an existing pidfile.
         return
     except ValueError:
diff --git a/lib/lp/services/spriteutils.py b/lib/lp/services/spriteutils.py
index 1ec6fa6..37e27fc 100644
--- a/lib/lp/services/spriteutils.py
+++ b/lib/lp/services/spriteutils.py
@@ -157,7 +157,7 @@ class SpriteUtil:
             abs_filename = os.path.join(css_dir, sprite['filename'])
             try:
                 sprite_images[sprite['filename']] = Image.open(abs_filename)
-            except IOError:
+            except OSError:
                 print(
                     "Error opening '%s' for %s css rule" % (
                         abs_filename, sprite['rule'].selectorText),
diff --git a/lib/lp/services/statsd/tests/test_numbercruncher.py b/lib/lp/services/statsd/tests/test_numbercruncher.py
index e2ed4be..0885072 100644
--- a/lib/lp/services/statsd/tests/test_numbercruncher.py
+++ b/lib/lp/services/statsd/tests/test_numbercruncher.py
@@ -46,7 +46,7 @@ class TestNumberCruncher(StatsMixin, TestCaseWithFactory):
     run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=20)
 
     def setUp(self):
-        super(TestNumberCruncher, self).setUp()
+        super().setUp()
         self.setUpStats()
         # Deactivate sampledata builders; we only want statistics for the
         # builders explicitly created in these tests.
diff --git a/lib/lp/services/temporaryblobstorage/browser.py b/lib/lp/services/temporaryblobstorage/browser.py
index d0cda60..2373936 100644
--- a/lib/lp/services/temporaryblobstorage/browser.py
+++ b/lib/lp/services/temporaryblobstorage/browser.py
@@ -44,7 +44,7 @@ class TemporaryBlobStorageAddView(LaunchpadFormView):
         # we need the action's name to be FORM_SUBMIT.
         self.actions = [action for action in self.actions]
         self.actions[0].__name__ = 'FORM_SUBMIT'
-        super(TemporaryBlobStorageAddView, self).initialize()
+        super().initialize()
 
     # NOTE: This action is named FORM_SUBMIT because apport depends on it
     # being named like that.
@@ -86,7 +86,7 @@ class TemporaryBlobStorageURL:
     @property
     def path(self):
         """Return the path component of the URL."""
-        return u'temporary-blobs/%s' % self.context.uuid
+        return 'temporary-blobs/%s' % self.context.uuid
 
 
 class TemporaryBlobStorageNavigation(GetitemNavigation):
diff --git a/lib/lp/services/temporaryblobstorage/model.py b/lib/lp/services/temporaryblobstorage/model.py
index a793e88..f2b0732 100644
--- a/lib/lp/services/temporaryblobstorage/model.py
+++ b/lib/lp/services/temporaryblobstorage/model.py
@@ -12,7 +12,6 @@ from io import BytesIO
 import uuid
 
 import pytz
-import six
 from storm.locals import (
     DateTime,
     Int,
@@ -51,7 +50,7 @@ class TemporaryBlobStorage(StormBase):
     date_created = DateTime(tzinfo=pytz.UTC, allow_none=False, default=DEFAULT)
 
     def __init__(self, uuid, file_alias):
-        super(TemporaryBlobStorage, self).__init__()
+        super().__init__()
         self.uuid = uuid
         self.file_alias = file_alias
 
@@ -122,13 +121,13 @@ class TemporaryStorageManager:
 
         # create the BLOB and return the UUID
 
-        new_uuid = six.text_type(uuid.uuid1())
+        new_uuid = str(uuid.uuid1())
 
         # We use a random filename, so only things that can look up the
         # secret can retrieve the original data (which is why we don't use
         # the UUID we return to the user as the filename, nor the filename
         # of the object they uploaded).
-        secret = six.text_type(uuid.uuid1())
+        secret = str(uuid.uuid1())
 
         file_alias = getUtility(ILibraryFileAliasSet).create(
                 secret, len(blob), BytesIO(blob),
diff --git a/lib/lp/services/testing/customresult.py b/lib/lp/services/testing/customresult.py
index e8105da..9ee03f4 100644
--- a/lib/lp/services/testing/customresult.py
+++ b/lib/lp/services/testing/customresult.py
@@ -10,7 +10,6 @@ __all__ = [
 
 from unittest import TestSuite
 
-import six
 from zope.testrunner import find
 
 
@@ -66,7 +65,7 @@ def filter_tests(list_name, reorder_tests=False):
         test_lookup = {}
         # Multiple unique testcases can be represented by a single id and they
         # must be tracked separately.
-        for layer_name, suite in six.iteritems(tests_by_layer_name):
+        for layer_name, suite in tests_by_layer_name.items():
             for testcase in suite:
                 layer_to_tests = test_lookup.setdefault(
                     testcase.id(), {})
diff --git a/lib/lp/services/testing/parallel.py b/lib/lp/services/testing/parallel.py
index 3d3464d..e84b2ea 100644
--- a/lib/lp/services/testing/parallel.py
+++ b/lib/lp/services/testing/parallel.py
@@ -56,11 +56,11 @@ class GatherIDs(TestResult):
     """Gather test ids from a test run."""
 
     def __init__(self):
-        super(GatherIDs, self).__init__()
+        super().__init__()
         self.ids = []
 
     def startTest(self, test):
-        super(GatherIDs, self).startTest(test)
+        super().startTest(test)
         self.ids.append(test.id())
 
 
@@ -74,7 +74,7 @@ def find_tests(argv):
     load_list = find_load_list(argv)
     if load_list:
         # just use the load_list
-        with open(load_list, 'rt') as list_file:
+        with open(load_list) as list_file:
             return [id for id in list_file.read().split('\n') if id]
     # run in --list-tests mode
     argv = prepare_argv(argv) + ['--list-tests', '--subunit']
diff --git a/lib/lp/services/testing/tests/test_customresult.py b/lib/lp/services/testing/tests/test_customresult.py
index fa46e63..63afdaf 100644
--- a/lib/lp/services/testing/tests/test_customresult.py
+++ b/lib/lp/services/testing/tests/test_customresult.py
@@ -18,7 +18,7 @@ NEWLINE = '\n'
 class FakeTestCase(unittest.TestCase):
     """A minimal TestCase that can be instantiated."""
     def __init__(self, name, *args, **kwargs):
-        super(FakeTestCase, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         self.name = name
 
     def id(self):
diff --git a/lib/lp/services/testing/tests/test_parallel.py b/lib/lp/services/testing/tests/test_parallel.py
index 4613c2b..7c254ce 100644
--- a/lib/lp/services/testing/tests/test_parallel.py
+++ b/lib/lp/services/testing/tests/test_parallel.py
@@ -37,7 +37,7 @@ class TestListTestCase(TestCase, TestWithFixtures):
             args = info['args']
             load_list = find_load_list(args)
             self.assertNotEqual(None, load_list)
-            with open(load_list, 'rt') as testlist:
+            with open(load_list) as testlist:
                 contents = testlist.readlines()
             self.assertEqual(['foo\n', 'bar\n'], contents)
             return {'stdout': io.BytesIO(), 'stdin': io.BytesIO()}
diff --git a/lib/lp/services/tests/test_command_spawner.py b/lib/lp/services/tests/test_command_spawner.py
index 79b35be..e66f505 100644
--- a/lib/lp/services/tests/test_command_spawner.py
+++ b/lib/lp/services/tests/test_command_spawner.py
@@ -314,7 +314,7 @@ class TestOutputLineHandler(TestCase):
     """Unit tests for `OutputLineHandler`."""
 
     def setUp(self):
-        super(TestOutputLineHandler, self).setUp()
+        super().setUp()
         self.handler = OutputLineHandler(FakeMethod())
 
     def _getLines(self):
diff --git a/lib/lp/services/tests/test_encoding.py b/lib/lp/services/tests/test_encoding.py
index ed44acc..bd1e027 100644
--- a/lib/lp/services/tests/test_encoding.py
+++ b/lib/lp/services/tests/test_encoding.py
@@ -18,13 +18,13 @@ class TestWSGINativeString(TestCase):
         self.assertRaises(TypeError, wsgi_native_string, object())
 
     def test_bytes_iso_8859_1(self):
-        self.assertEqual(u'foo\xfe', wsgi_native_string(b'foo\xfe'))
+        self.assertEqual('foo\xfe', wsgi_native_string(b'foo\xfe'))
 
     def test_unicode_iso_8859_1(self):
-        self.assertEqual(u'foo\xfe', wsgi_native_string(u'foo\xfe'))
+        self.assertEqual('foo\xfe', wsgi_native_string('foo\xfe'))
 
     def test_unicode_not_iso_8859_1(self):
-        self.assertRaises(UnicodeEncodeError, wsgi_native_string, u'foo\u2014')
+        self.assertRaises(UnicodeEncodeError, wsgi_native_string, 'foo\u2014')
 
 
 def test_suite():
diff --git a/lib/lp/services/tests/test_timeout.py b/lib/lp/services/tests/test_timeout.py
index 9faa4c5..38797cd 100644
--- a/lib/lp/services/tests/test_timeout.py
+++ b/lib/lp/services/tests/test_timeout.py
@@ -152,7 +152,7 @@ class TestTimeout(TestCase):
         """The cleanup parameter can also be a string in which case it will be
         interpreted as the name of an instance method.
         """
-        class expirable_socket(object):
+        class expirable_socket:
             def __init__(self):
                 self.closed = False
                 self.sockets = socket.socketpair()
@@ -411,7 +411,7 @@ class TestTimeout(TestCase):
 
     def test_urlfetch_does_not_support_ftp_urls_by_default(self):
         """urlfetch() does not support ftp urls by default."""
-        url = u'ftp://localhost/'
+        url = 'ftp://localhost/'
         e = self.assertRaises(InvalidSchema, urlfetch, url)
         self.assertEqual(
             "No connection adapters were found for {!r}".format(url), str(e))
@@ -447,7 +447,7 @@ class TestTimeout(TestCase):
         """urlfetch() does not support file urls by default."""
         test_path = self.useFixture(TempDir()).join('file')
         write_file(test_path, b'')
-        url = u'file://' + test_path
+        url = 'file://' + test_path
         e = self.assertRaises(InvalidSchema, urlfetch, url)
         self.assertEqual(
             "No connection adapters were found for {!r}".format(url), str(e))
diff --git a/lib/lp/services/tests/test_utils.py b/lib/lp/services/tests/test_utils.py
index cabedb7..5e38373 100644
--- a/lib/lp/services/tests/test_utils.py
+++ b/lib/lp/services/tests/test_utils.py
@@ -42,7 +42,7 @@ class TestAutoDecorate(TestCase):
     """Tests for AutoDecorate."""
 
     def setUp(self):
-        super(TestAutoDecorate, self).setUp()
+        super().setUp()
         self.log = None
 
     def decorator_1(self, f):
@@ -280,7 +280,7 @@ class TestFileExists(TestCase):
     """Tests for `file_exists`."""
 
     def setUp(self):
-        super(TestFileExists, self).setUp()
+        super().setUp()
         self.useTempDir()
 
     def test_finds_file(self):
diff --git a/lib/lp/services/timeout.py b/lib/lp/services/timeout.py
index ff88dbe..53819b0 100644
--- a/lib/lp/services/timeout.py
+++ b/lib/lp/services/timeout.py
@@ -36,8 +36,6 @@ from requests.adapters import (
     )
 from requests_file import FileAdapter
 from requests_toolbelt.downloadutils import stream
-import six
-from six import reraise
 from urllib3.connectionpool import (
     HTTPConnectionPool,
     HTTPSConnectionPool,
@@ -142,7 +140,7 @@ class ThreadCapturingResult(Thread):
     """
 
     def __init__(self, target, args, kwargs, **opt):
-        super(ThreadCapturingResult, self).__init__(**opt)
+        super().__init__(**opt)
         self.target = target
         self.args = args
         self.kwargs = kwargs
@@ -186,7 +184,7 @@ class with_timeout:
         """
         # If the cleanup function is specified by name, the function but be a
         # method, so defined in a class definition context.
-        if isinstance(cleanup, six.string_types):
+        if isinstance(cleanup, str):
             frame = sys._getframe(1)
             f_locals = frame.f_locals
 
@@ -202,7 +200,7 @@ class with_timeout:
         """Wraps the method."""
         def cleanup(t, args):
             if self.cleanup is not None:
-                if isinstance(self.cleanup, six.string_types):
+                if isinstance(self.cleanup, str):
                     # 'self' will be first positional argument.
                     getattr(args[0], self.cleanup)()
                 else:
@@ -239,7 +237,7 @@ class with_timeout:
                 # Remove the cyclic reference for faster GC.
                 del t.exc_info
                 try:
-                    reraise(exc_info[0], exc_info[1], tb=exc_info[2])
+                    raise exc_info[1].with_traceback(None)
                 finally:
                     # Avoid traceback reference cycles.
                     del exc_info
@@ -252,7 +250,7 @@ class CleanableConnectionPoolMixin:
     """Enhance urllib3's connection pools to support forced socket cleanup."""
 
     def __init__(self, *args, **kwargs):
-        super(CleanableConnectionPoolMixin, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         self._all_connections = []
         self._all_connections_mutex = Lock()
 
@@ -260,7 +258,7 @@ class CleanableConnectionPoolMixin:
         with self._all_connections_mutex:
             if self._all_connections is None:
                 raise ClosedPoolError(self, "Pool is closed.")
-            conn = super(CleanableConnectionPoolMixin, self)._new_conn()
+            conn = super()._new_conn()
             self._all_connections.append(conn)
             return conn
 
@@ -275,7 +273,7 @@ class CleanableConnectionPoolMixin:
                     sock.close()
                     conn.sock = None
             self._all_connections = None
-        super(CleanableConnectionPoolMixin, self).close()
+        super().close()
 
 
 class CleanableHTTPConnectionPool(
@@ -298,7 +296,7 @@ class CleanablePoolManager(PoolManager):
     """A version of urllib3's PoolManager supporting forced socket cleanup."""
 
     def __init__(self, *args, **kwargs):
-        super(CleanablePoolManager, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         self.pool_classes_by_scheme = cleanable_pool_classes_by_scheme
 
 
diff --git a/lib/lp/services/twistedsupport/testing.py b/lib/lp/services/twistedsupport/testing.py
index f9d9d92..9ccba30 100644
--- a/lib/lp/services/twistedsupport/testing.py
+++ b/lib/lp/services/twistedsupport/testing.py
@@ -19,7 +19,7 @@ class TReqFixture(Fixture):
     """A `treq` client that handles test cleanup."""
 
     def __init__(self, reactor, pool=None):
-        super(TReqFixture, self).__init__()
+        super().__init__()
         self.reactor = reactor
         if pool is None:
             pool = HTTPConnectionPool(reactor, persistent=False)
diff --git a/lib/lp/services/twistedsupport/tests/test_loggingsupport.py b/lib/lp/services/twistedsupport/tests/test_loggingsupport.py
index ccc66a5..e5dbc4e 100644
--- a/lib/lp/services/twistedsupport/tests/test_loggingsupport.py
+++ b/lib/lp/services/twistedsupport/tests/test_loggingsupport.py
@@ -18,7 +18,7 @@ UTC = pytz.utc
 class TestLaunchpadLogFile(TestCase):
 
     def setUp(self):
-        super(TestLaunchpadLogFile, self).setUp()
+        super().setUp()
         self.temp_dir = self.useFixture(TempDir()).path
 
     def testInitialization(self):
diff --git a/lib/lp/services/twistedsupport/tests/test_processmonitor.py b/lib/lp/services/twistedsupport/tests/test_processmonitor.py
index 91196f7..dbf50f7 100644
--- a/lib/lp/services/twistedsupport/tests/test_processmonitor.py
+++ b/lib/lp/services/twistedsupport/tests/test_processmonitor.py
@@ -88,7 +88,7 @@ class ProcessTestsMixin:
         self.protocol.processEnded(failure.Failure(exc))
 
     def setUp(self):
-        super(ProcessTestsMixin, self).setUp()
+        super().setUp()
         self.termination_deferred = defer.Deferred()
         self.clock = task.Clock()
         self.protocol = self.makeProtocol()
diff --git a/lib/lp/services/twistedsupport/xmlrpc.py b/lib/lp/services/twistedsupport/xmlrpc.py
index 209f360..2aaecf3 100644
--- a/lib/lp/services/twistedsupport/xmlrpc.py
+++ b/lib/lp/services/twistedsupport/xmlrpc.py
@@ -46,7 +46,7 @@ class DeferredBlockingProxy(BlockingProxy):
 
     def callRemote(self, method_name, *args, **kwargs):
         return defer.maybeDeferred(
-            super(DeferredBlockingProxy, self).callRemote,
+            super().callRemote,
             method_name, *args, **kwargs)
 
 
diff --git a/lib/lp/services/utils.py b/lib/lp/services/utils.py
index 2de70b1..c2017d1 100644
--- a/lib/lp/services/utils.py
+++ b/lib/lp/services/utils.py
@@ -123,7 +123,7 @@ def value_string(item):
     elif zope_isinstance(item, bytes):
         return six.ensure_text(item)
     else:
-        return six.text_type(item)
+        return str(item)
 
 
 def text_delta(instance_delta, delta_names, state_names, interface):
@@ -311,14 +311,14 @@ def obfuscate_structure(o):
         elements, and dict keys and values have undergone obfuscate_email
         recursively.
     """
-    if isinstance(o, six.string_types):
+    if isinstance(o, str):
         return obfuscate_email(o)
     elif isinstance(o, (list, tuple)):
         return [obfuscate_structure(value) for value in o]
     elif isinstance(o, (dict)):
         return {
             obfuscate_structure(key): obfuscate_structure(value)
-            for key, value in six.iteritems(o)}
+            for key, value in o.items()}
     else:
         return o
 
diff --git a/lib/lp/services/verification/browser/logintoken.py b/lib/lp/services/verification/browser/logintoken.py
index eb78c59..8d13422 100644
--- a/lib/lp/services/verification/browser/logintoken.py
+++ b/lib/lp/services/verification/browser/logintoken.py
@@ -102,7 +102,7 @@ class LoginTokenView(LaunchpadView):
                 str(self.request.URL), self.PAGES[self.context.tokentype])
             self.request.response.redirect(url)
         else:
-            return super(LoginTokenView, self).render()
+            return super().render()
 
 
 class BaseTokenView:
@@ -194,11 +194,11 @@ class ClaimTeamView(
             # Let's pretend the claimed profile provides ITeam while we
             # render/process this page, so that it behaves like a team.
             directlyProvides(removeSecurityProxy(self.claimed_profile), ITeam)
-        super(ClaimTeamView, self).initialize()
+        super().initialize()
 
     def setUpWidgets(self, context=None):
         self.form_fields['teamowner'].for_display = True
-        super(ClaimTeamView, self).setUpWidgets(context=self.claimed_profile)
+        super().setUpWidgets(context=self.claimed_profile)
         alsoProvides(self.widgets['teamowner'], IAlwaysSubmittedWidget)
 
     @property
@@ -257,7 +257,7 @@ class ValidateGPGKeyView(BaseTokenView, LaunchpadFormView):
         if not self.redirectIfInvalidOrConsumedToken():
             if self.context.tokentype == LoginTokenType.VALIDATESIGNONLYGPG:
                 self.field_names = ['text_signature']
-        super(ValidateGPGKeyView, self).initialize()
+        super().initialize()
 
     def validate(self, data):
         self.gpg_key = self._getGPGKey()
@@ -382,7 +382,7 @@ class ValidateEmailView(BaseTokenView, LaunchpadFormView):
     def initialize(self):
         if self.redirectIfInvalidOrConsumedToken():
             return
-        super(ValidateEmailView, self).initialize()
+        super().initialize()
 
     def validate(self, data):
         """Make sure the email address this token refers to is not in use."""
@@ -490,7 +490,7 @@ class MergePeopleView(BaseTokenView, LaunchpadFormView):
         self.redirectIfInvalidOrConsumedToken()
         self.dupe = getUtility(IPersonSet).getByEmail(
             self.context.email, filter_status=False)
-        super(MergePeopleView, self).initialize()
+        super().initialize()
 
     @action(_('Cancel'), name='cancel', validator='validate_cancel')
     def cancel_action(self, action, data):
diff --git a/lib/lp/services/verification/browser/tests/test_logintoken.py b/lib/lp/services/verification/browser/tests/test_logintoken.py
index f48e67d..88b80de 100644
--- a/lib/lp/services/verification/browser/tests/test_logintoken.py
+++ b/lib/lp/services/verification/browser/tests/test_logintoken.py
@@ -111,10 +111,10 @@ class TestClaimTeamView(TestCaseWithFactory):
             requester=self.claimer, requesteremail=None,
             email=self.claimee_email, tokentype=LoginTokenType.TEAMCLAIM)
         msgs = self._claimToken(token1)
-        self.assertEqual([u'Team claimed successfully'], msgs)
+        self.assertEqual(['Team claimed successfully'], msgs)
         msgs = self._claimToken(token2)
         self.assertEqual(
-            [u'claimee has already been converted to a team.'], msgs)
+            ['claimee has already been converted to a team.'], msgs)
 
 
 class MergePeopleViewTestCase(TestCaseWithFactory):
diff --git a/lib/lp/services/verification/model/logintoken.py b/lib/lp/services/verification/model/logintoken.py
index 31ecb99..d887683 100644
--- a/lib/lp/services/verification/model/logintoken.py
+++ b/lib/lp/services/verification/model/logintoken.py
@@ -80,7 +80,7 @@ class LoginToken(SQLBase):
             self._plaintext_token = token
             kwargs['_token'] = hashlib.sha256(
                 token.encode('UTF-8')).hexdigest()
-        super(LoginToken, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
 
     _plaintext_token = None
 
diff --git a/lib/lp/services/webapp/adapter.py b/lib/lp/services/webapp/adapter.py
index c4b8f72..26b4b6d 100644
--- a/lib/lp/services/webapp/adapter.py
+++ b/lib/lp/services/webapp/adapter.py
@@ -21,7 +21,6 @@ from psycopg2.extensions import (
     QueryCanceledError,
     )
 import pytz
-from six import reraise
 from storm.database import register_scheme
 from storm.databases.postgres import (
     Postgres,
@@ -100,7 +99,7 @@ class LaunchpadTimeoutError(TimeoutError):
     """
 
     def __init__(self, statement, params, original_error):
-        super(LaunchpadTimeoutError, self).__init__(statement, params)
+        super().__init__(statement, params)
         self.original_error = original_error
 
     def __str__(self):
@@ -144,7 +143,7 @@ class CommitLogger:
 
     def afterCompletion(self, txn):
         action = get_request_timeline(get_current_browser_request()).start(
-            u"SQL-nostore", u'Transaction completed, status: %s' % txn.status)
+            "SQL-nostore", 'Transaction completed, status: %s' % txn.status)
         action.finish()
 
 
@@ -157,14 +156,14 @@ class FilteredTimeline(Timeline):
     """
 
     def __init__(self, actions=None, detail_filter=None, **kwargs):
-        super(FilteredTimeline, self).__init__(actions=actions, **kwargs)
+        super().__init__(actions=actions, **kwargs)
         self.detail_filter = detail_filter
 
     def start(self, category, detail, allow_nested=False):
         """See `Timeline`."""
         if self.detail_filter is not None:
             detail = self.detail_filter(category, detail)
-        return super(FilteredTimeline, self).start(category, detail)
+        return super().start(category, detail)
 
 
 def set_request_started(
@@ -444,7 +443,7 @@ class LaunchpadDatabase(Postgres):
     _dsn_user_re = re.compile('user=[^ ]*')
 
     def __init__(self, uri):
-        super(LaunchpadDatabase, self).__init__(uri)
+        super().__init__(uri)
         # A unique name for this database connection.
         self.name = uri.database
 
@@ -487,7 +486,7 @@ class LaunchpadDatabase(Postgres):
         else:
             self._isolation = isolation_level_map[dbconfig.isolation_level]
 
-        raw_connection = super(LaunchpadDatabase, self).raw_connect()
+        raw_connection = super().raw_connect()
 
         # Set read only mode for the session.
         # An alternative would be to use the _ro users generated by
@@ -532,7 +531,7 @@ class LaunchpadSessionDatabase(Postgres):
                 self._dsn += ' host=%s' % config.launchpad_session.dbhost
 
         flags = _get_dirty_commit_flags()
-        raw_connection = super(LaunchpadSessionDatabase, self).raw_connect()
+        raw_connection = super().raw_connect()
         if hasattr(raw_connection, 'auto_close'):
             raw_connection.auto_close = False
         raw_connection.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
@@ -567,7 +566,7 @@ class LaunchpadTimeoutTracer(PostgresTimeoutTracer):
         try:
             if self.get_remaining_time() is None:
                 return
-            super(LaunchpadTimeoutTracer, self).connection_raw_execute(
+            super().connection_raw_execute(
                 connection, raw_cursor, statement, params)
         except (RequestExpired, TimeoutError):
             # XXX bug=636801 Robert Colins 20100914 This is duplicated
@@ -583,7 +582,7 @@ class LaunchpadTimeoutTracer(PostgresTimeoutTracer):
             info = sys.exc_info()
             transaction.doom()
             try:
-                reraise(info[0], info[1], tb=info[2])
+                raise info[1].with_traceback(None)
             finally:
                 # Avoid traceback reference cycles.
                 del info
@@ -655,12 +654,12 @@ class LaunchpadStatementTracer:
                 # SQL execution.
                 connection._lp_statement_info = (
                     int(time() * 1000),
-                    u'SQL-%s' % connection._database.name,
+                    'SQL-%s' % connection._database.name,
                     statement_to_log)
                 connection._lp_statement_action = None
             return
         action = get_request_timeline(get_current_browser_request()).start(
-            u'SQL-%s' % connection._database.name, statement_to_log)
+            'SQL-%s' % connection._database.name, statement_to_log)
         connection._lp_statement_action = action
 
     def connection_raw_execute_success(self, connection, raw_cursor,
diff --git a/lib/lp/services/webapp/authentication.py b/lib/lp/services/webapp/authentication.py
index e965fcc..6e0ba21 100644
--- a/lib/lp/services/webapp/authentication.py
+++ b/lib/lp/services/webapp/authentication.py
@@ -256,7 +256,7 @@ class LaunchpadPrincipal:
 
     def __init__(self, id, title, description, account,
                  access_level=AccessLevel.WRITE_PRIVATE, scope_url=None):
-        self.id = six.text_type(id)
+        self.id = str(id)
         self.title = title
         self.description = description
         self.access_level = access_level
diff --git a/lib/lp/services/webapp/batching.py b/lib/lp/services/webapp/batching.py
index 6f0707e..894e6a6 100644
--- a/lib/lp/services/webapp/batching.py
+++ b/lib/lp/services/webapp/batching.py
@@ -118,7 +118,7 @@ class UpperBatchNavigationView(LaunchpadView):
     def render(self):
         if self.context.currentBatch():
             return LaunchpadView.render(self)
-        return u""
+        return ""
 
 
 class LowerBatchNavigationView(UpperBatchNavigationView):
@@ -130,7 +130,7 @@ class BatchNavigator(lazr.batchnavigator.BatchNavigator):
     def __init__(self, results, request, start=0, size=None, callback=None,
                  transient_parameters=None, force_start=False,
                  range_factory=None, hide_counts=False):
-        super(BatchNavigator, self).__init__(results, request,
+        super().__init__(results, request,
             start=start, size=size, callback=callback,
             transient_parameters=transient_parameters,
             force_start=force_start, range_factory=range_factory)
diff --git a/lib/lp/services/webapp/canonicalurl.py b/lib/lp/services/webapp/canonicalurl.py
index 66011de..8f16e22 100644
--- a/lib/lp/services/webapp/canonicalurl.py
+++ b/lib/lp/services/webapp/canonicalurl.py
@@ -18,7 +18,7 @@ from zope.component import queryAdapter
 from lp.services.webapp.publisher import canonical_url_iterator
 
 
-def nearest_context_with_adapter(obj, interface, name=u''):
+def nearest_context_with_adapter(obj, interface, name=''):
     """Find the nearest adapter in the url chain between obj and interface.
 
     The function looks upward though the canonical url chain and returns a
@@ -34,7 +34,7 @@ def nearest_context_with_adapter(obj, interface, name=u''):
     return (None, None)
 
 
-def nearest_adapter(obj, interface, name=u''):
+def nearest_adapter(obj, interface, name=''):
     """Find the nearest adapter in the url chain between obj and interface.
 
     The function looks upward though the canonical url chain and returns
diff --git a/lib/lp/services/webapp/error.py b/lib/lp/services/webapp/error.py
index 4458601..b8823fd 100644
--- a/lib/lp/services/webapp/error.py
+++ b/lib/lp/services/webapp/error.py
@@ -64,7 +64,7 @@ class SystemErrorView(LaunchpadView):
     safe_to_show_in_restricted_mode = False
 
     def __init__(self, context, request):
-        super(SystemErrorView, self).__init__(context, request)
+        super().__init__(context, request)
         self.request.response.removeAllNotifications()
         if self.response_code is not None:
             self.request.response.setStatus(self.response_code)
@@ -280,9 +280,9 @@ class OpenIdDiscoveryFailureView(SystemErrorView):
 class DisconnectionErrorView(SystemErrorView):
 
     response_code = http.client.SERVICE_UNAVAILABLE
-    reason = u'our database being temporarily offline'
+    reason = 'our database being temporarily offline'
 
 
 class OperationalErrorView(DisconnectionErrorView):
 
-    reason = u'our database having temporary operational issues'
+    reason = 'our database having temporary operational issues'
diff --git a/lib/lp/services/webapp/errorlog.py b/lib/lp/services/webapp/errorlog.py
index 8c66532..7327c8f 100644
--- a/lib/lp/services/webapp/errorlog.py
+++ b/lib/lp/services/webapp/errorlog.py
@@ -111,7 +111,7 @@ def attach_exc_info(report, context):
         return
     report['type'] = getattr(info[0], '__name__', info[0])
     report['value'] = oops.createhooks.safe_unicode(info[1])
-    if not isinstance(info[2], six.string_types):
+    if not isinstance(info[2], str):
         tb_text = ''.join(format_exception(*info,
                                            **{'as_html': False}))
     else:
@@ -135,8 +135,8 @@ def attach_feature_info(report, context):
     request = context.get('http_request')
     features = getattr(request, 'features', None)
     if features is not None:
-        report['features.usedFlags'] = u'%r' % features.usedFlags()
-        report['features.usedScopes'] = u'%r' % features.usedScopes()
+        report['features.usedFlags'] = '%r' % features.usedFlags()
+        report['features.usedScopes'] = '%r' % features.usedScopes()
 
 
 def attach_http_request(report, context):
@@ -192,7 +192,7 @@ def attach_http_request(report, context):
     if principal is not None and principal is not missing:
         username = ', '.join([
                 six.ensure_text(login),
-                six.text_type(request.principal.id),
+                str(request.principal.id),
                 six.ensure_text(request.principal.title),
                 six.ensure_text(request.principal.description)])
         report['username'] = username
@@ -203,7 +203,7 @@ def attach_http_request(report, context):
     for key, value in request.items():
         if _is_sensitive(request, key):
             value = '<hidden>'
-        if not isinstance(value, six.string_types):
+        if not isinstance(value, str):
             value = oops.createhooks.safe_unicode(value)
         # keys need to be unicode objects. The form items (a subset of
         # request.items) are generally just the url query_string url decoded,
@@ -215,7 +215,7 @@ def attach_http_request(report, context):
         args = request.getPositionalArguments()
         # Request variables are strings: this could move to its own key and be
         # raw.
-        report['req_vars']['xmlrpc args'] = six.text_type(args)
+        report['req_vars']['xmlrpc args'] = str(args)
 
 
 def attach_ignore_from_exception(report, context):
diff --git a/lib/lp/services/webapp/escaping.py b/lib/lp/services/webapp/escaping.py
index 1e1c502..ebeb2e7 100644
--- a/lib/lp/services/webapp/escaping.py
+++ b/lib/lp/services/webapp/escaping.py
@@ -51,7 +51,7 @@ def html_escape(message):
         if isinstance(raw, bytes):
             raw = six.ensure_text(raw)
         else:
-            raw = six.text_type(raw)
+            raw = str(raw)
         for needle, replacement in HTML_REPLACEMENTS:
             raw = raw.replace(needle, replacement)
         return raw
@@ -93,7 +93,7 @@ class structured:
         if isinstance(text, bytes):
             text = six.ensure_text(text)
         else:
-            text = six.text_type(text)
+            text = str(text)
         self.text = text
         if reps and kwreps:
             raise TypeError(
@@ -103,7 +103,7 @@ class structured:
             self.escapedtext = text % tuple(html_escape(rep) for rep in reps)
         elif kwreps:
             self.escapedtext = text % {
-                k: html_escape(v) for k, v in six.iteritems(kwreps)}
+                k: html_escape(v) for k, v in kwreps.items()}
         else:
             self.escapedtext = text
 
diff --git a/lib/lp/services/webapp/haproxy.py b/lib/lp/services/webapp/haproxy.py
index f79c6e8..d4be199 100644
--- a/lib/lp/services/webapp/haproxy.py
+++ b/lib/lp/services/webapp/haproxy.py
@@ -64,7 +64,7 @@ class HAProxyStatusView:
         if going_down_flag:
             self.request.response.setStatus(
                 config.haproxy_status_view.going_down_status)
-            return u"May day! May day! I'm going down. Stop the flood gate."
+            return "May day! May day! I'm going down. Stop the flood gate."
         else:
             self.request.response.setStatus(200)
-            return u"Everything is groovy. Keep them coming!"
+            return "Everything is groovy. Keep them coming!"
diff --git a/lib/lp/services/webapp/interfaces.py b/lib/lp/services/webapp/interfaces.py
index f9f1c7b..88da6f2 100644
--- a/lib/lp/services/webapp/interfaces.py
+++ b/lib/lp/services/webapp/interfaces.py
@@ -281,9 +281,9 @@ class IFavicon(Interface):
     """A favicon."""
 
     path = TextLine(
-        title=u"The name of the file containing the favicon data.",
+        title="The name of the file containing the favicon data.",
         required=True)
-    data = Bytes(title=u"The favicon data.", required=True)
+    data = Bytes(title="The favicon data.", required=True)
 
 
 # XXX kiko 2007-02-08: this needs reconsideration if we are to make it a truly
@@ -311,7 +311,7 @@ class ILaunchBag(Interface):
     time_zone = Attribute("The user's time zone")
 
     developer = Bool(
-        title=u'True if a member of the launchpad developers celebrity'
+        title='True if a member of the launchpad developers celebrity'
         )
 
 
@@ -432,7 +432,7 @@ class ILaunchpadBrowserApplicationRequest(
     """The request interface to the application for LP browser requests."""
 
     form_ng = Object(
-        title=u'IBrowserFormNG object containing the submitted form data',
+        title='IBrowserFormNG object containing the submitted form data',
         schema=IBrowserFormNG)
 
 
@@ -636,7 +636,7 @@ class INotificationList(Interface):
 class INotificationRequest(Interface):
 
     notifications = Object(
-        description=u"""
+        description="""
             Notifications received from previous request as well as any
             notifications added in the current request
             """,
@@ -672,7 +672,7 @@ class INotificationResponse(Interface):
         """
 
     notifications = Object(
-            description=u"Notifications generated by current request",
+            description="Notifications generated by current request",
             schema=INotificationList
             )
 
@@ -707,7 +707,7 @@ class IErrorReportEvent(IObjectEvent):
 
 class IErrorReportRequest(Interface):
     oopsid = TextLine(
-        description=u"""an identifier for the exception, or None if no
+        description="""an identifier for the exception, or None if no
         exception has occurred""")
 
 
diff --git a/lib/lp/services/webapp/login.py b/lib/lp/services/webapp/login.py
index 82f38e1..08e693a 100644
--- a/lib/lp/services/webapp/login.py
+++ b/lib/lp/services/webapp/login.py
@@ -288,7 +288,7 @@ class OpenIDCallbackView(OpenIDLogin):
 
     def _gather_params(self, request):
         params = dict(request.form)
-        for key, value in six.iteritems(request.query_string_params):
+        for key, value in request.query_string_params.items():
             if len(value) > 1:
                 raise ValueError(
                     'Did not expect multi-valued fields.')
@@ -422,7 +422,7 @@ class OpenIDCallbackView(OpenIDLogin):
             # already logged in, so we just add a notification message and
             # redirect.
             self.request.response.addInfoNotification(
-                _(u'Your authentication failed but you were already '
+                _('Your authentication failed but you were already '
                    'logged into Launchpad.'))
             self._redirect()
             # No need to return anything as we redirect above.
@@ -432,7 +432,7 @@ class OpenIDCallbackView(OpenIDLogin):
                 self.context, self.request, self.openid_response)()
 
     def __call__(self):
-        retval = super(OpenIDCallbackView, self).__call__()
+        retval = super().__call__()
         # The consumer.complete() call in initialize() will create entries in
         # OpenIDConsumerNonce to prevent replay attacks, but since this will
         # be a GET request, the transaction would be rolled back, so we need
@@ -454,7 +454,7 @@ class OpenIDLoginErrorView(LaunchpadView):
 
     def __init__(self, context, request, openid_response=None,
                  login_error=None):
-        super(OpenIDLoginErrorView, self).__init__(context, request)
+        super().__init__(context, request)
         assert self.account is None, (
             "Don't try to render this page when the user is logged in.")
         if login_error:
diff --git a/lib/lp/services/webapp/menu.py b/lib/lp/services/webapp/menu.py
index 7ebdd4b..627502a 100644
--- a/lib/lp/services/webapp/menu.py
+++ b/lib/lp/services/webapp/menu.py
@@ -74,7 +74,7 @@ def get_current_view(request=None):
 
 def get_facet(view):
     """Return the view's facet name."""
-    return getattr(removeSecurityProxy(view), '__launchpad_facetname__', u'')
+    return getattr(removeSecurityProxy(view), '__launchpad_facetname__', '')
 
 
 @implementer(ILinkData)
@@ -348,12 +348,12 @@ class FacetMenu(MenuBase):
         return IFacetLink(MenuBase._get_link(self, name))
 
     def initLink(self, linkname, request_url=None):
-        link = super(FacetMenu, self).initLink(linkname, request_url)
+        link = super().initLink(linkname, request_url)
         link.url = link.url.ensureNoSlash()
         return link
 
     def updateLink(self, link, request_url=None, selectedfacetname=None):
-        super(FacetMenu, self).updateLink(link, request_url)
+        super().updateLink(link, request_url)
         if selectedfacetname is None:
             selectedfacetname = self.defaultlink
         if (selectedfacetname is not None and
@@ -385,12 +385,12 @@ class NavigationMenu(MenuBase):
     disabled = False
 
     def initLink(self, linkname, request_url):
-        link = super(NavigationMenu, self).initLink(linkname, request_url)
+        link = super().initLink(linkname, request_url)
         link.url = link.url.ensureNoSlash()
         return link
 
     def updateLink(self, link, request_url=None, view=None):
-        super(NavigationMenu, self).updateLink(link, request_url)
+        super().updateLink(link, request_url)
         # The link should be unlinked if it is the current URL, or if
         # the menu for the current view is the link's menu.
         if view is None:
@@ -410,9 +410,7 @@ class NavigationMenu(MenuBase):
         if request_url is None:
             request_url = URI(request.getURL())
 
-        for link in super(NavigationMenu, self).iterlinks(
-            request_url=request_url, view=view):
-            yield link
+        yield from super().iterlinks(request_url=request_url, view=view)
 
     def _is_current_url(self, request_url, link_url):
         """Determines if <link_url> is the current URL.
diff --git a/lib/lp/services/webapp/metazcml.py b/lib/lp/services/webapp/metazcml.py
index 6f043c0..d8feae0 100644
--- a/lib/lp/services/webapp/metazcml.py
+++ b/lib/lp/services/webapp/metazcml.py
@@ -64,7 +64,7 @@ from lp.services.webapp.publisher import RenamedView
 class IAuthorizationsDirective(Interface):
     """Set up authorizations as given in a module."""
 
-    module = GlobalObject(title=u'module', required=True)
+    module = GlobalObject(title='module', required=True)
 
 
 def _isAuthorization(module_member):
@@ -90,15 +90,15 @@ def authorizations(_context, module):
 class ISecuredUtilityDirective(Interface):
     """Configure a utility with security directives."""
 
-    class_ = GlobalObject(title=u'class', required=False)
+    class_ = GlobalObject(title='class', required=False)
 
     provides = GlobalObject(
-        title=u'interface this utility provides',
+        title='interface this utility provides',
         required=True)
 
-    component = GlobalObject(title=u'component', required=False)
+    component = GlobalObject(title='component', required=False)
 
-    name = TextLine(title=u"Name", required=False)
+    name = TextLine(title="Name", required=False)
 
 
 class PermissionCollectingContext:
@@ -163,29 +163,29 @@ class IURLDirective(Interface):
     """Say how to compute canonical urls."""
 
     for_ = GlobalObject(
-        title=u"Specification of the object that has this canonical url",
+        title="Specification of the object that has this canonical url",
         required=True)
 
     urldata = GlobalObject(
-        title=u"Adapter to ICanonicalUrlData for this object.",
+        title="Adapter to ICanonicalUrlData for this object.",
         required=False)
 
     path_expression = TextLine(
-        title=u"TALES expression that evaluates to the path"
-               " relative to the parent object.",
+        title="TALES expression that evaluates to the path"
+              " relative to the parent object.",
         required=False)
 
     attribute_to_parent = PythonIdentifier(
-        title=u"Name of the attribute that gets you to the parent object",
+        title="Name of the attribute that gets you to the parent object",
         required=False)
 
     parent_utility = GlobalObject(
-        title=u"Interface of the utility that is the parent of the object",
+        title="Interface of the utility that is the parent of the object",
         required=False)
 
     rootsite = PythonIdentifier(
-        title=u"Name of the site this URL has as its root."
-               "None for 'use the request'.",
+        title="Name of the site this URL has as its root."
+              "None for 'use the request'.",
         required=False)
 
 
@@ -197,11 +197,11 @@ class IGlueDirective(Interface):
     generic mechanism, what that 'something' is isn't important.
     """
     module = GlobalObject(
-        title=u"Module in which the classes are found.")
+        title="Module in which the classes are found.")
 
     classes = Tokens(
         value_type=PythonIdentifier(),
-        title=u"Space separated list of classes to register.",
+        title="Space separated list of classes to register.",
         required=True)
 
 
@@ -213,7 +213,7 @@ class INavigationDirective(IGlueDirective):
     """Hook up traversal etc."""
 
     layer = GlobalInterface(
-        title=u"The layer where this navigation is going to be available.",
+        title="The layer where this navigation is going to be available.",
         required=False)
 
 
@@ -224,11 +224,11 @@ class IFeedsDirective(IGlueDirective):
 class IFaviconDirective(Interface):
 
     for_ = GlobalObject(
-        title=u"Specification of the object that has this favicon",
+        title="Specification of the object that has this favicon",
         required=True)
 
     file = Path(
-        title=u"Path to the image file",
+        title="Path to the image file",
         required=True)
 
 
@@ -429,7 +429,7 @@ class IAssociatedWithAFacet(Interface):
     """A zcml schema for something that can be associated with a facet."""
 
     facet = TextLine(
-        title=u"The name of the facet this page is associated with.",
+        title="The name of the facet this page is associated with.",
         required=False)
 
 
@@ -510,29 +510,29 @@ class IRenamedPageDirective(Interface):
     """
 
     for_ = GlobalObject(
-        title=u"Specification of the object that has the renamed page",
+        title="Specification of the object that has the renamed page",
         required=True)
 
     layer = GlobalInterface(
-        title=u"The layer the renamed page is in.",
-        description=u"""
+        title="The layer the renamed page is in.",
+        description="""
         A skin is composed of layers. It is common to put skin
         specific views in a layer named after the skin. If the 'layer'
         attribute is not supplied, it defaults to 'default'.""",
         required=False)
 
     name = zope.schema.TextLine(
-        title=u"The name of the old page.",
-        description=u"The name shows up in URLs/paths. For example 'foo'.",
+        title="The name of the old page.",
+        description="The name shows up in URLs/paths. For example 'foo'.",
         required=True)
 
     new_name = zope.schema.TextLine(
-        title=u"The name the page was renamed to.",
-        description=u"The name shows up in URLs/paths. For example 'foo'.",
+        title="The name the page was renamed to.",
+        description="The name shows up in URLs/paths. For example 'foo'.",
         required=True)
 
     rootsite = PythonIdentifier(
-        title=u"Name of the site this URL has as its root."
+        title="Name of the site this URL has as its root."
                "None for 'use the request'.",
         required=False)
 
@@ -570,7 +570,7 @@ class ICallDirective(Interface):
     """
 
     callable = GlobalObject(
-        title=u"The thing that will be called.", required=True)
+        title="The thing that will be called.", required=True)
 
 
 def call(_context, callable):
@@ -580,8 +580,8 @@ def call(_context, callable):
 class IDefineLaunchpadPermissionDirective(IPermissionDirective):
 
     access_level = TextLine(
-        title=u"Access level", required=False,
-        description=u"Either read or write")
+        title="Access level", required=False,
+        description="Either read or write")
 
 
 class ILaunchpadPermission(IPermission):
@@ -596,7 +596,7 @@ class LaunchpadPermission(Permission):
         assert access_level in ["read", "write"], (
             "Unknown access level (%s). Must be either read or write."
             % access_level)
-        super(LaunchpadPermission, self).__init__(id, title, description)
+        super().__init__(id, title, description)
         self.access_level = access_level
 
 
diff --git a/lib/lp/services/webapp/notifications.py b/lib/lp/services/webapp/notifications.py
index 8dcf009..9e76431 100644
--- a/lib/lp/services/webapp/notifications.py
+++ b/lib/lp/services/webapp/notifications.py
@@ -224,8 +224,7 @@ class NotificationResponse:
             allowUnauthenticatedSession(self._request)
             session = ISession(self)[SESSION_KEY]
             session['notifications'] = self._notifications
-        return super(NotificationResponse, self).redirect(
-            location, status, trusted=trusted)
+        return super().redirect(location, status, trusted=trusted)
 
     def addDebugNotification(self, msg):
         """See `INotificationResponse`."""
@@ -279,12 +278,11 @@ class NotificationList(list):
 
     def __init__(self):
         self.created = datetime.utcnow()
-        super(NotificationList, self).__init__()
+        super().__init__()
 
     def __getitem__(self, index_or_levelname):
         if isinstance(index_or_levelname, int):
-            return super(NotificationList, self).__getitem__(
-                index_or_levelname)
+            return super().__getitem__(index_or_levelname)
 
         level = getattr(
                 BrowserNotificationLevel, index_or_levelname.upper(), None
diff --git a/lib/lp/services/webapp/pgsession.py b/lib/lp/services/webapp/pgsession.py
index 2e0ff6c..51ef456 100644
--- a/lib/lp/services/webapp/pgsession.py
+++ b/lib/lp/services/webapp/pgsession.py
@@ -52,8 +52,7 @@ class Python2FriendlyUnpickler(pickle._Unpickler):
 
             return datetime_factory
         else:
-            return super(Python2FriendlyUnpickler, self).find_class(
-                module, name)
+            return super().find_class(module, name)
 
 
 class PGSessionBase:
diff --git a/lib/lp/services/webapp/publication.py b/lib/lp/services/webapp/publication.py
index fafe9b5..31079d6 100644
--- a/lib/lp/services/webapp/publication.py
+++ b/lib/lp/services/webapp/publication.py
@@ -228,7 +228,7 @@ class LaunchpadBrowserPublication(
             # record of traversed objects.  This is mostly a ZODB thing that
             # we don't care about, so just use something minimal that fits
             # the syntax.
-            txn.user = u"/ %s" % (request.principal.id,)
+            txn.user = "/ %s" % (request.principal.id,)
 
         return txn
 
@@ -264,7 +264,7 @@ class LaunchpadBrowserPublication(
         threadrequestfile = open_for_writing(
             'logs/thread-%s.request' % threadid, 'wb')
         try:
-            request_txt = six.text_type(request)
+            request_txt = str(request)
         except Exception:
             request_txt = 'Exception converting request to string\n\n'
             try:
@@ -315,7 +315,7 @@ class LaunchpadBrowserPublication(
         # this way.  Even though PATH_INFO is always present in real requests,
         # we need to tread carefully (``get``) because of test requests in our
         # automated tests.
-        if request.get('PATH_INFO') not in [u'/+opstats', u'/+haproxy']:
+        if request.get('PATH_INFO') not in ['/+opstats', '/+haproxy']:
             principal = auth_utility.authenticate(request)
         if principal is not None:
             assert principal.person is not None
@@ -773,8 +773,7 @@ class LaunchpadBrowserPublication(
         We must restart the request timer.  Otherwise we can get OOPS errors
         from our exception views inappropriately.
         """
-        super(LaunchpadBrowserPublication,
-              self).beginErrorHandlingTransaction(request, ob, note)
+        super().beginErrorHandlingTransaction(request, ob, note)
         # XXX: gary 2008-11-04 bug=293614: As the bug describes, we want to
         # only clear the SQL records and timeout when we are preparing for a
         # view (or a side effect). Otherwise, we don't want to clear the
diff --git a/lib/lp/services/webapp/publisher.py b/lib/lp/services/webapp/publisher.py
index bba5b8c..c0f00aa 100644
--- a/lib/lp/services/webapp/publisher.py
+++ b/lib/lp/services/webapp/publisher.py
@@ -438,7 +438,7 @@ class LaunchpadView(UserAttributeCache):
         self.initialize()
         if self._isRedirected():
             # Don't render the page on redirects.
-            return u''
+            return ''
         else:
             return self.render()
 
@@ -766,12 +766,12 @@ def canonical_url(
     else:
         root_url = request.getRootURL(rootsite)
 
-    path = u'/'.join(reversed(urlparts))
+    path = '/'.join(reversed(urlparts))
     if ((path_only_if_possible and
          request is not None and
          root_url.startswith(request.getApplicationURL()))
         or force_local_path):
-        return u'/' + path
+        return '/' + path
     return six.ensure_text(root_url + path)
 
 
@@ -821,7 +821,7 @@ def get_raw_form_value_from_current_request(field, field_name):
     # Zope wrongly encodes any form element that doesn't look like a file,
     # so re-fetch the file content if it has been encoded.
     if request and field_name in request.form and isinstance(
-            request.form[field_name], six.text_type):
+            request.form[field_name], str):
         request._environ['wsgi.input'].seek(0)
         fs = FieldStorage(fp=request._body_instream, environ=request._environ)
         return fs[field_name].value
@@ -1099,7 +1099,7 @@ class RedirectionView(URLDereferencingMixin):
 
     def __call__(self):
         self.request.response.redirect(self.target, status=self.status)
-        return u''
+        return ''
 
     def browserDefault(self, request):
         return self, ()
@@ -1181,7 +1181,7 @@ class RenamedView:
 
         self.request.response.redirect(target_url, status=301)
 
-        return u''
+        return ''
 
     def publishTraverse(self, request, name):
         """See zope.publisher.interfaces.browser.IBrowserPublisher."""
diff --git a/lib/lp/services/webapp/servers.py b/lib/lp/services/webapp/servers.py
index eef5ede..32ebf7f 100644
--- a/lib/lp/services/webapp/servers.py
+++ b/lib/lp/services/webapp/servers.py
@@ -114,7 +114,7 @@ from lp.testopenid.interfaces.server import ITestOpenIDApplication
 from lp.xmlrpc.interfaces import IPrivateApplication
 
 
-class StepsToGo(six.Iterator):
+class StepsToGo:
     """
 
     >>> class FakeRequest:
@@ -412,7 +412,7 @@ class XMLRPCRequestPublicationFactory(VirtualHostRequestPublicationFactory):
 
     def __init__(self, vhost_name, request_factory, publication_factory,
                  port=None):
-        super(XMLRPCRequestPublicationFactory, self).__init__(
+        super().__init__(
             vhost_name, request_factory, publication_factory, port, ['POST'])
 
     def checkRequest(self, environment):
@@ -421,8 +421,7 @@ class XMLRPCRequestPublicationFactory(VirtualHostRequestPublicationFactory):
         Accept only requests where the MIME type is text/xml.
         """
         request_factory, publication_factory = (
-            super(XMLRPCRequestPublicationFactory, self).checkRequest(
-                environment))
+            super().checkRequest(environment))
         if request_factory is None:
             mime_type = environment.get('CONTENT_TYPE')
             if mime_type.split(';')[0].strip() != 'text/xml':
@@ -445,7 +444,7 @@ class WebServiceRequestPublicationFactory(
                  port=None):
         """This factory accepts requests that use all five major HTTP methods.
         """
-        super(WebServiceRequestPublicationFactory, self).__init__(
+        super().__init__(
             vhost_name, request_factory, publication_factory, port)
 
 
@@ -466,9 +465,7 @@ class VHostWebServiceRequestPublicationFactory(
         if self.isWebServicePath(environment.get('PATH_INFO', '')):
             return WebServiceRequestPublicationFactory.default_methods
         else:
-            return super(
-                VHostWebServiceRequestPublicationFactory,
-                self).getAcceptableMethods(environment)
+            return super().getAcceptableMethods(environment)
 
     def getRequestAndPublicationFactories(self, environment):
         """See `VirtualHostRequestPublicationFactory`.
@@ -479,9 +476,7 @@ class VHostWebServiceRequestPublicationFactory(
         if self.isWebServicePath(environment.get('PATH_INFO', '')):
             return WebServiceClientRequest, WebServicePublication
         else:
-            return super(
-                VHostWebServiceRequestPublicationFactory,
-                self).getRequestAndPublicationFactories(environment)
+            return super().getRequestAndPublicationFactories(environment)
 
     def isWebServicePath(self, path):
         """Does the path refer to a web service resource?"""
@@ -553,8 +548,7 @@ class LaunchpadBrowserRequestMixin:
 
     def getURL(self, level=0, path_only=False, include_query=False):
         """See `IBasicLaunchpadRequest`."""
-        sup = super(LaunchpadBrowserRequestMixin, self)
-        url = sup.getURL(level, path_only)
+        url = super().getURL(level, path_only)
         if include_query:
             query_string = self.get('QUERY_STRING')
             if query_string is not None and len(query_string) > 0:
@@ -585,8 +579,7 @@ class BasicLaunchpadRequest(LaunchpadBrowserRequestMixin):
             if isinstance(pi, bytes):
                 pi = pi.decode('utf-8', 'replace')
             environ['PATH_INFO'] = pi.encode('utf-8')
-        super(BasicLaunchpadRequest, self).__init__(
-            body_instream, environ, response)
+        super().__init__(body_instream, environ, response)
         # Now replace PATH_INFO with the version decoded by sane_environment.
         if 'PATH_INFO' in self._environ:
             environ['PATH_INFO'] = self._environ['PATH_INFO']
@@ -630,7 +623,7 @@ class BasicLaunchpadRequest(LaunchpadBrowserRequestMixin):
 
     def retry(self):
         """See IPublisherRequest."""
-        new_request = super(BasicLaunchpadRequest, self).retry()
+        new_request = super().retry()
         # Propagate the list of keys we have set in the WSGI environment.
         new_request._wsgi_keys = self._wsgi_keys
         return new_request
@@ -682,7 +675,7 @@ class LaunchpadBrowserRequest(BasicLaunchpadRequest, BrowserRequest,
         return LaunchpadBrowserResponse()
 
     def _decode(self, text):
-        text = super(LaunchpadBrowserRequest, self)._decode(text)
+        text = super()._decode(text)
         if isinstance(text, bytes):
             # BrowserRequest._decode failed to do so with the user-specified
             # charsets, so decode as UTF-8 with replacements, since we always
@@ -831,7 +824,7 @@ class LaunchpadBrowserResponse(NotificationResponse, BrowserResponse):
     # Note that NotificationResponse defines a 'redirect' method which
     # needs to override the 'redirect' method in BrowserResponse
     def __init__(self, header_output=None, http_transaction=None):
-        super(LaunchpadBrowserResponse, self).__init__()
+        super().__init__()
 
     def _validateHeader(self, name, value):
         name = str(name)
@@ -844,12 +837,11 @@ class LaunchpadBrowserResponse(NotificationResponse, BrowserResponse):
 
     def setHeader(self, name, value, literal=False):
         name, value = self._validateHeader(name, value)
-        super(LaunchpadBrowserResponse, self).setHeader(
-            name, value, literal=literal)
+        super().setHeader(name, value, literal=literal)
 
     def addHeader(self, name, value):
         name, value = self._validateHeader(name, value)
-        super(LaunchpadBrowserResponse, self).addHeader(name, value)
+        super().addHeader(name, value)
 
     def redirect(self, location, status=None, trusted=True,
                  temporary_if_possible=False):
@@ -878,8 +870,8 @@ class LaunchpadBrowserResponse(NotificationResponse, BrowserResponse):
                 status = 307
             else:
                 status = 303
-        super(LaunchpadBrowserResponse, self).redirect(
-            six.ensure_str(six.text_type(location)),
+        super().redirect(
+            six.ensure_str(str(location)),
             status=status, trusted=trusted)
 
 
@@ -959,7 +951,7 @@ class LaunchpadTestRequest(LaunchpadBrowserRequestMixin,
             if value is not None:
                 value = wsgi_native_string(value)
             native_kw[key] = value
-        super(LaunchpadTestRequest, self).__init__(
+        super().__init__(
             body_instream=body_instream, environ=environ, form=form,
             skin=skin, outstream=outstream,
             REQUEST_METHOD=wsgi_native_string(method), **native_kw)
@@ -1079,7 +1071,7 @@ class FeedsPublication(LaunchpadBrowserPublication):
         """
         # LaunchpadImageFolder is imported here to avoid an import loop.
         from lp.app.browser.launchpad import LaunchpadImageFolder
-        result = super(FeedsPublication, self).traverseName(request, ob, name)
+        result = super().traverseName(request, ob, name)
         if len(request.stepstogo) == 0:
             # The url has been fully traversed. Now we can check that
             # the result is a feed, a favicon, an image, or a redirection.
@@ -1134,8 +1126,7 @@ class WebServicePublication(WebServicePublicationMixin,
         See https://dev.launchpad.net/Foundations/Webservice for more
         information about WebService page IDs.
         """
-        pageid = super(WebServicePublication, self).constructPageID(
-            view, context)
+        pageid = super().constructPageID(view, context)
         if ICollectionResource.providedBy(view):
             # collection_identifier is a way to differentiate between
             # CollectionResource objects. CollectionResource objects are
@@ -1150,7 +1141,7 @@ class WebServicePublication(WebServicePublicationMixin,
                 pageid += ':' + collection_identifier
         op = (view.request.get('ws.op')
             or view.request.query_string_params.get('ws.op'))
-        if op and isinstance(op, six.string_types):
+        if op and isinstance(op, str):
             pageid += ':' + op
         return pageid
 
@@ -1174,7 +1165,7 @@ class WebServicePublication(WebServicePublicationMixin,
             # A redirection should be served as is.
             return ob
         else:
-            return super(WebServicePublication, self).getResource(request, ob)
+            return super().getResource(request, ob)
 
     def _getPrincipalFromAccessToken(self, request):
         """Authenticate a request using a personal access token."""
@@ -1222,8 +1213,8 @@ class WebServicePublication(WebServicePublicationMixin,
             # header.
             anonymous_request = True
             consumer_key = six.ensure_text(request.getHeader('User-Agent', ''))
-            if consumer_key == u'':
-                consumer_key = u'anonymous client'
+            if consumer_key == '':
+                consumer_key = 'anonymous client'
             consumer = consumers.getByKey(consumer_key)
 
         if consumer is None:
@@ -1293,7 +1284,7 @@ class WebServicePublication(WebServicePublicationMixin,
         request_path = request.get('PATH_INFO', '')
         web_service_config = getUtility(IWebServiceConfiguration)
         if request_path.startswith("/%s" % web_service_config.path_override):
-            return super(WebServicePublication, self).getPrincipal(request)
+            return super().getPrincipal(request)
 
         if request._auth is not None and request._auth.startswith("Token "):
             return self._getPrincipalFromAccessToken(request)
@@ -1316,8 +1307,7 @@ class WebServiceClientRequest(LaunchpadWebServiceRequestTraversal,
     """Request type for a resource published through the web service."""
 
     def __init__(self, body_instream, environ, response=None):
-        super(WebServiceClientRequest, self).__init__(
-            body_instream, environ, response)
+        super().__init__(body_instream, environ, response)
         # Web service requests use content negotiation, so we put
         # 'Accept' in the Vary header. They don't use cookies, so
         # there's no point in putting 'Cookie' in the Vary header, and
@@ -1355,7 +1345,7 @@ class WebServiceTestRequest(LaunchpadWebServiceRequestTraversal,
             }
         if environ is not None:
             test_environ.update(environ)
-        super(WebServiceTestRequest, self).__init__(
+        super().__init__(
             body_instream=body_instream, environ=test_environ, **kw)
         if version is None:
             version = getUtility(IWebServiceConfiguration).active_versions[-1]
@@ -1393,7 +1383,7 @@ class PublicXMLRPCRequest(BasicLaunchpadRequest, XMLRPCRequest,
         # URLs with mainsite's, so that URLs are meaningful.
         if rootsite in (None, 'xmlrpc', 'xmlrpc_private'):
             rootsite = 'mainsite'
-        return super(PublicXMLRPCRequest, self).getRootURL(rootsite)
+        return super().getRootURL(rootsite)
 
     def _createResponse(self):
         return PublicXMLRPCResponse()
@@ -1428,8 +1418,7 @@ class PrivateXMLRPCPublication(PublicXMLRPCPublication):
         missing = object()
         end_point = getattr(ob, name, missing)
         if end_point is missing:
-            return super(PrivateXMLRPCPublication, self).traverseName(
-                request, ob, name)
+            return super().traverseName(request, ob, name)
         return end_point
 
 
@@ -1476,7 +1465,7 @@ class ProtocolErrorPublication(LaunchpadBrowserPublication):
         :param status: The HTTP status to send
         :param headers: Any HTTP headers that should be sent.
         """
-        super(ProtocolErrorPublication, self).__init__(None)
+        super().__init__(None)
         self.status = status
         self.headers = headers
 
diff --git a/lib/lp/services/webapp/sorting.py b/lib/lp/services/webapp/sorting.py
index f0ae561..e39c464 100644
--- a/lib/lp/services/webapp/sorting.py
+++ b/lib/lp/services/webapp/sorting.py
@@ -9,8 +9,6 @@ __all__ = ['expand_numbers',
 
 import re
 
-import six
-
 
 def expand_numbers(unicode_text, fill_digits=4):
     """Return a copy of the string with numbers zero filled.
@@ -25,7 +23,7 @@ def expand_numbers(unicode_text, fill_digits=4):
     branch-0002-0003.0012
 
     """
-    assert(isinstance(unicode_text, six.text_type))
+    assert(isinstance(unicode_text, str))
 
     def substitute_filled_numbers(match):
         return match.group(0).zfill(fill_digits)
@@ -36,7 +34,7 @@ def expand_numbers(unicode_text, fill_digits=4):
 # strings in reversed order.  So ord(u'0') -> u'9' and
 # so on.
 reversed_numbers_table = dict(
-  zip(map(ord, u'0123456789'), reversed(u'0123456789')))
+  zip(map(ord, '0123456789'), reversed('0123456789')))
 
 
 def _reversed_number_sort_key(text):
@@ -52,8 +50,8 @@ def _reversed_number_sort_key(text):
     bzr-9.86
 
     """
-    assert isinstance(text, six.text_type)
-    assert isinstance(text, six.text_type)
+    assert isinstance(text, str)
+    assert isinstance(text, str)
     return text.translate(reversed_numbers_table)
 
 
@@ -79,7 +77,7 @@ def sorted_version_numbers(sequence, key=_identity):
 
     >>> class series:
     ...   def __init__(self, name):
-    ...     self.name = six.ensure_text(name)
+    ...     self.name = name
     >>> bzr_versions = [series('0.9'), series('0.10'), series('0.11'),
     ...                 series('bzr-0.9'), series('bzr-0.10'),
     ...                 series('bzr-0.11'), series('foo')]
@@ -158,7 +156,7 @@ def sorted_dotted_numbers(sequence, key=_identity):
 
     >>> class series:
     ...   def __init__(self, name):
-    ...     self.name = six.ensure_text(name)
+    ...     self.name = name
     >>> bzr_versions = [series('0.9'), series('0.10'), series('0.11'),
     ...                 series('bzr-0.9'), series('bzr-0.10'),
     ...                 series('bzr-0.11'), series('foo')]
diff --git a/lib/lp/services/webapp/tests/test_authorization.py b/lib/lp/services/webapp/tests/test_authorization.py
index abf54ea..3b27dcc 100644
--- a/lib/lp/services/webapp/tests/test_authorization.py
+++ b/lib/lp/services/webapp/tests/test_authorization.py
@@ -221,7 +221,7 @@ class TestCheckPermissionCaching(TestCase):
 
     def setUp(self):
         """Register a new permission and a fake store selector."""
-        super(TestCheckPermissionCaching, self).setUp()
+        super().setUp()
         self.factory = ObjectFactory()
         self.useFixture(ZopeUtilityFixture(FakeStoreSelector, IStoreSelector))
 
@@ -486,7 +486,7 @@ class LoneObject(LaunchpadContainer):
     _id_counter = count(1)
 
     def __init__(self):
-        super(LoneObject, self).__init__(self)
+        super().__init__(self)
         self.id = next(LoneObject._id_counter)
 
     def getParentContainers(self):
@@ -520,7 +520,7 @@ class TestPrecachePermissionForObjects(TestCase):
     def test_precaching_permissions(self):
         # The precache_permission_for_objects function updates the security
         # policy cache for the permission specified.
-        class Boring(object):
+        class Boring:
             """A boring, but weakref-able object."""
         objects = [Boring(), Boring()]
         request = LaunchpadTestRequest()
@@ -532,7 +532,7 @@ class TestPrecachePermissionForObjects(TestCase):
 
     def test_default_request(self):
         # If no request is provided, the current interaction is used.
-        class Boring(object):
+        class Boring:
             """A boring, but weakref-able object."""
         obj = Boring()
         request = LaunchpadTestRequest()
@@ -551,7 +551,7 @@ class TestIterAuthorization(TestCase):
     layer = ZopelessLayer
 
     def setUp(self):
-        super(TestIterAuthorization, self).setUp()
+        super().setUp()
         self.object = Object()
         self.principal = FakeLaunchpadPrincipal()
         self.permission = "docking.Permission"
diff --git a/lib/lp/services/webapp/tests/test_authutility.py b/lib/lp/services/webapp/tests/test_authutility.py
index a4a05a3..0ef13ae 100644
--- a/lib/lp/services/webapp/tests/test_authutility.py
+++ b/lib/lp/services/webapp/tests/test_authutility.py
@@ -33,12 +33,12 @@ from lp.testing.fixture import (
 
 
 @implementer(IPerson)
-class DummyPerson(object):
+class DummyPerson:
     is_valid_person = True
 
 
 @implementer(IAccount)
-class DummyAccount(object):
+class DummyAccount:
     person = DummyPerson()
 
 
@@ -47,7 +47,7 @@ Bruce.person = Bruce.account.person
 
 
 @implementer(IPlacelessLoginSource)
-class DummyPlacelessLoginSource(object):
+class DummyPlacelessLoginSource:
 
     def getPrincipalByLogin(self, id):
         return Bruce
diff --git a/lib/lp/services/webapp/tests/test_batching.py b/lib/lp/services/webapp/tests/test_batching.py
index cf5aa63..19dbf34 100644
--- a/lib/lp/services/webapp/tests/test_batching.py
+++ b/lib/lp/services/webapp/tests/test_batching.py
@@ -43,7 +43,7 @@ class TestStormRangeFactory(TestCaseWithFactory):
     layer = LaunchpadFunctionalLayer
 
     def setUp(self):
-        super(TestStormRangeFactory, self).setUp()
+        super().setUp()
         self.error_messages = []
 
     def makeStormResultSet(self):
diff --git a/lib/lp/services/webapp/tests/test_breadcrumbs.py b/lib/lp/services/webapp/tests/test_breadcrumbs.py
index 000ec61..49d29a5 100644
--- a/lib/lp/services/webapp/tests/test_breadcrumbs.py
+++ b/lib/lp/services/webapp/tests/test_breadcrumbs.py
@@ -81,7 +81,7 @@ class TestExtraBreadcrumbForLeafPageOnHierarchyView(BaseBreadcrumbTestCase):
     """
 
     def setUp(self):
-        super(TestExtraBreadcrumbForLeafPageOnHierarchyView, self).setUp()
+        super().setUp()
         login('test@xxxxxxxxxxxxx')
         self.product = self.factory.makeProduct(name='crumb-tester')
         self.product_url = canonical_url(self.product)
@@ -134,7 +134,7 @@ class TestExtraFacetBreadcrumbsOnHierarchyView(BaseBreadcrumbTestCase):
     """
 
     def setUp(self):
-        super(TestExtraFacetBreadcrumbsOnHierarchyView, self).setUp()
+        super().setUp()
         login('test@xxxxxxxxxxxxx')
         self.product = self.factory.makeProduct(name='crumb-tester')
         self.product_url = canonical_url(self.product)
diff --git a/lib/lp/services/webapp/tests/test_dbpolicy.py b/lib/lp/services/webapp/tests/test_dbpolicy.py
index b47c3ba..8855268 100644
--- a/lib/lp/services/webapp/tests/test_dbpolicy.py
+++ b/lib/lp/services/webapp/tests/test_dbpolicy.py
@@ -85,14 +85,14 @@ class BaseDatabasePolicyTestCase(ImplicitDatabasePolicyTestCase):
     policy = None
 
     def setUp(self):
-        super(BaseDatabasePolicyTestCase, self).setUp()
+        super().setUp()
         if self.policy is None:
             self.policy = BaseDatabasePolicy()
         getUtility(IStoreSelector).push(self.policy)
 
     def tearDown(self):
         getUtility(IStoreSelector).pop()
-        super(BaseDatabasePolicyTestCase, self).tearDown()
+        super().tearDown()
 
     def test_correctly_implements_IDatabasePolicy(self):
         self.assertProvides(self.policy, IDatabasePolicy)
@@ -170,7 +170,7 @@ class LaunchpadDatabasePolicyTestCase(StandbyDatabasePolicyTestCase):
     def setUp(self):
         request = LaunchpadTestRequest(SERVER_URL='http://launchpad.test')
         self.policy = LaunchpadDatabasePolicy(request)
-        super(LaunchpadDatabasePolicyTestCase, self).setUp()
+        super().setUp()
 
 
 class LayerDatabasePolicyTestCase(TestCase):
@@ -356,7 +356,7 @@ class TestFastDowntimeRollout(TestCase):
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        super(TestFastDowntimeRollout, self).setUp()
+        super().setUp()
 
         self.primary_dbname = DatabaseLayer._db_fixture.dbname
         self.standby_dbname = self.primary_dbname + '_standby'
diff --git a/lib/lp/services/webapp/tests/test_error.py b/lib/lp/services/webapp/tests/test_error.py
index 3a360f0..87cac31 100644
--- a/lib/lp/services/webapp/tests/test_error.py
+++ b/lib/lp/services/webapp/tests/test_error.py
@@ -143,8 +143,7 @@ class TestDatabaseErrorViews(TestCase):
 
         class Disconnects(Equals):
             def __init__(self, message):
-                super(Disconnects, self).__init__(
-                    ('DisconnectionError', message))
+                super().__init__(('DisconnectionError', message))
 
         browser = Browser()
         browser.raiseHttpErrors = False
@@ -232,7 +231,7 @@ class TestDatabaseErrorViews(TestCase):
         # Test setup.
         self.useFixture(FakeLogger('SiteError', level=logging.CRITICAL))
 
-        class BrokenView(object):
+        class BrokenView:
             """A view that raises an OperationalError"""
             def __call__(self, *args, **kw):
                 raise OperationalError()
diff --git a/lib/lp/services/webapp/tests/test_errorlog.py b/lib/lp/services/webapp/tests/test_errorlog.py
index 7fb92f4..01da94a 100644
--- a/lib/lp/services/webapp/tests/test_errorlog.py
+++ b/lib/lp/services/webapp/tests/test_errorlog.py
@@ -13,7 +13,6 @@ from lazr.batchnavigator.interfaces import InvalidBatchSizeError
 from lazr.restful.declarations import error_status
 import oops_amqp
 import pytz
-import six
 from talisker.logs import logging_context
 import testtools
 from timeline.timeline import Timeline
@@ -61,7 +60,7 @@ class TestErrorReportingUtility(TestCaseWithFactory):
     layer = LaunchpadZopelessLayer
 
     def setUp(self):
-        super(TestErrorReportingUtility, self).setUp()
+        super().setUp()
         # ErrorReportingUtility reads the global config to get the
         # current error directory.
         tempdir = self.useFixture(TempDir()).path
@@ -139,7 +138,7 @@ class TestErrorReportingUtility(TestCaseWithFactory):
                 form={
                     'name1': 'value3 \xa7',
                     'name2': 'value2',
-                    u'\N{BLACK SQUARE}': u'value4',
+                    '\N{BLACK SQUARE}': 'value4',
                     })
         request.setInWSGIEnvironment('launchpad.pageid', 'IFoo:+foo-template')
 
@@ -151,7 +150,7 @@ class TestErrorReportingUtility(TestCaseWithFactory):
         # topic is obtained from the request
         self.assertEqual('IFoo:+foo-template', report['topic'])
         self.assertEqual(
-            u'account-name, 42, account-name, description |\u25a0|',
+            'account-name, 42, account-name, description |\u25a0|',
             report['username'])
         self.assertEqual('http://localhost:9000/foo', report['url'])
         self.assertEqual({
@@ -160,7 +159,7 @@ class TestErrorReportingUtility(TestCaseWithFactory):
             'HTTP_COOKIE': '<hidden>',
             'HTTP_HOST': '127.0.0.1',
             'SERVER_URL': 'http://localhost:9000/foo',
-            u'\u25a0': 'value4',
+            '\u25a0': 'value4',
             'lp': '<hidden>',
             'name1': 'value3 \xa7',
             'name2': 'value2',
@@ -187,7 +186,7 @@ class TestErrorReportingUtility(TestCaseWithFactory):
             report = self.assertStatementCount(
                 0, utility.raising, sys.exc_info(), request)
         self.assertEqual(
-            u'my-username, 42, account-name, description |\u25a0|',
+            'my-username, 42, account-name, description |\u25a0|',
             report['username'])
         self.assertEqual(report['id'], logging_context.flat['oopsid'])
 
@@ -212,7 +211,7 @@ class TestErrorReportingUtility(TestCaseWithFactory):
         except ArbitraryException:
             report = utility.raising(sys.exc_info(), request)
         self.assertEqual(
-            u'account-name, 42, account-name, description |\u25a0|',
+            'account-name, 42, account-name, description |\u25a0|',
             report['username'])
         self.assertEqual(report['id'], logging_context.flat['oopsid'])
 
@@ -243,7 +242,7 @@ class TestErrorReportingUtility(TestCaseWithFactory):
             if isinstance(key, bytes):
                 key.decode('utf8')
             else:
-                self.assertIsInstance(key, six.text_type)
+                self.assertIsInstance(key, str)
 
     def test_raising_with_webservice_request(self):
         # Test ErrorReportingUtility.raising() with a WebServiceRequest
@@ -586,16 +585,16 @@ class TestSensitiveRequestVariables(testtools.TestCase):
 
 class TestRequestWithUnauthenticatedPrincipal(TestRequest):
     principal = UnauthenticatedPrincipal(
-        u'Anonymous', u'Anonymous', u'Anonymous User')
+        'Anonymous', 'Anonymous', 'Anonymous User')
 
 
 class TestRequestWithPrincipal(TestRequest):
     def __init__(self, account=None, *args, **kw):
-        super(TestRequestWithPrincipal, self).__init__(*args, **kw)
+        super().__init__(*args, **kw)
         self.setPrincipal(LaunchpadPrincipal(
-            42, u'account-name',
+            42, 'account-name',
             # non-ASCII description
-            u'description |\N{BLACK SQUARE}|',
+            'description |\N{BLACK SQUARE}|',
             account))
 
     def setInWSGIEnvironment(self, key, value):
@@ -662,5 +661,5 @@ class TestHooks(testtools.TestCase):
             'http_request': {'SIMPLE': 'string', 'COMPLEX': complexthing}}
         attach_http_request(report, context)
         self.assertEqual(
-            {'SIMPLE': 'string', 'COMPLEX': six.text_type(complexthing)},
+            {'SIMPLE': 'string', 'COMPLEX': str(complexthing)},
             report['req_vars'])
diff --git a/lib/lp/services/webapp/tests/test_forgiving_vocabulary.py b/lib/lp/services/webapp/tests/test_forgiving_vocabulary.py
index e90b319..9a94476 100644
--- a/lib/lp/services/webapp/tests/test_forgiving_vocabulary.py
+++ b/lib/lp/services/webapp/tests/test_forgiving_vocabulary.py
@@ -11,7 +11,7 @@ class TestForgivingSimpleVocabulary(TestCase):
     """Tests for ForgivingSimpleVocabulary."""
 
     def setUp(self):
-        super(TestForgivingSimpleVocabulary, self).setUp()
+        super().setUp()
         self.term_1 = SimpleTerm('term-1', 'term-1', 'My first term')
         self.term_2 = SimpleTerm('term-2', 'term-2', 'My second term')
         self.vocabulary = ForgivingSimpleVocabulary(
diff --git a/lib/lp/services/webapp/tests/test_haproxy.py b/lib/lp/services/webapp/tests/test_haproxy.py
index 51e7c5a..ca3744c 100644
--- a/lib/lp/services/webapp/tests/test_haproxy.py
+++ b/lib/lp/services/webapp/tests/test_haproxy.py
@@ -28,21 +28,21 @@ class HAProxyIntegrationTest(TestCase):
         self.addCleanup(haproxy.set_going_down_flag, self.original_flag)
 
     def test_HAProxyStatusView_all_good_returns_200(self):
-        result = http(u'GET /+haproxy HTTP/1.0', handle_errors=False)
+        result = http('GET /+haproxy HTTP/1.0', handle_errors=False)
         self.assertEqual(200, result.getStatus())
 
     def test_authenticated_HAProxyStatusView_works(self):
         # We don't use authenticated requests, but this keeps us from
         # generating oopses.
         result = http(
-            u'GET /+haproxy HTTP/1.0\n'
-            u'Authorization: Basic Zm9vLmJhckBjYW5vbmljYWwuY29tOnRlc3Q=\n',
+            'GET /+haproxy HTTP/1.0\n'
+            'Authorization: Basic Zm9vLmJhckBjYW5vbmljYWwuY29tOnRlc3Q=\n',
             handle_errors=False)
         self.assertEqual(200, result.getStatus())
 
     def test_HAProxyStatusView_going_down_returns_500(self):
         haproxy.set_going_down_flag(True)
-        result = http(u'GET /+haproxy HTTP/1.0', handle_errors=False)
+        result = http('GET /+haproxy HTTP/1.0', handle_errors=False)
         self.assertEqual(500, result.getStatus())
 
     def test_haproxy_url_uses_DatabaseBlocked_policy(self):
@@ -64,5 +64,5 @@ class HAProxyIntegrationTest(TestCase):
             '''))
         self.addCleanup(config.pop, 'change_haproxy_status_code')
         haproxy.set_going_down_flag(True)
-        result = http(u'GET /+haproxy HTTP/1.0', handle_errors=False)
+        result = http('GET /+haproxy HTTP/1.0', handle_errors=False)
         self.assertEqual(499, result.getStatus())
diff --git a/lib/lp/services/webapp/tests/test_login.py b/lib/lp/services/webapp/tests/test_login.py
index f7b150b..61357d4 100644
--- a/lib/lp/services/webapp/tests/test_login.py
+++ b/lib/lp/services/webapp/tests/test_login.py
@@ -109,7 +109,7 @@ class StubbedOpenIDCallbackView(OpenIDCallbackView):
     login_called = False
 
     def login(self, account):
-        super(StubbedOpenIDCallbackView, self).login(account)
+        super().login(account)
         self.login_called = True
         current_policy = getUtility(IStoreSelector).get_current()
         if not isinstance(current_policy, PrimaryDatabasePolicy):
@@ -280,7 +280,7 @@ class TestOpenIDCallbackView(TestCaseWithFactory):
             environ={'PATH_INFO': '/'})
         view = OpenIDCallbackView(context=None, request=None)
         params = view._gather_params(request)
-        self.assertEqual(params['foo'], u'\u16dd')
+        self.assertEqual(params['foo'], '\u16dd')
 
     def test_unexpected_multivalue_fields(self):
         # The parameter gatering doesn't expect to find multi-valued form
@@ -330,7 +330,7 @@ class TestOpenIDCallbackView(TestCaseWithFactory):
         # seen, we automatically register an account with that identity
         # because someone who registered on login.lp.net or login.u.c should
         # be able to login here without any further steps.
-        identifier = u'4w7kmzU'
+        identifier = '4w7kmzU'
         account_set = getUtility(IAccountSet)
         self.assertRaises(
             LookupError, account_set.getByOpenIDIdentifier, identifier)
@@ -358,7 +358,7 @@ class TestOpenIDCallbackView(TestCaseWithFactory):
         # When we get a positive assertion about an identity URL we've never
         # seen but whose email address is already registered, we just change
         # the identity URL that's associated with the existing email address.
-        identifier = u'4w7kmzU'
+        identifier = '4w7kmzU'
         email = 'test@xxxxxxxxxxx'
         person = self.factory.makePerson(
             displayname='Test account', email=email,
@@ -601,7 +601,7 @@ class TestOpenIDCallbackView(TestCaseWithFactory):
         with IAccountSet_getByOpenIDIdentifier_monkey_patched():
             self.assertRaises(
                 AssertionError,
-                getUtility(IAccountSet).getByOpenIDIdentifier, u'foo')
+                getUtility(IAccountSet).getByOpenIDIdentifier, 'foo')
 
     def test_logs_to_timeline(self):
         # Completing an OpenID association *can* make an HTTP request to the
@@ -823,8 +823,8 @@ class TestOpenIDLogin(TestCaseWithFactory):
         # Sometimes the form params are unicode because a decode('utf8')
         # worked in the form machinery... and if so they cannot be trivially
         # quoted but must be encoded first.
-        key = quote(u'key\xf3'.encode('utf8'))
-        value = quote(u'value\xf3'.encode('utf8'))
+        key = quote('key\xf3'.encode())
+        value = quote('value\xf3'.encode())
         query_string = "%s=%s" % (key, value)
         self.assertThat(query_string, ForwardsCorrectly())
 
diff --git a/lib/lp/services/webapp/tests/test_menu.py b/lib/lp/services/webapp/tests/test_menu.py
index fde5ebf..27b2f16 100644
--- a/lib/lp/services/webapp/tests/test_menu.py
+++ b/lib/lp/services/webapp/tests/test_menu.py
@@ -40,7 +40,7 @@ class TestMenuBaseLinkCaching(TestCase):
 
     def tearDown(self):
         logout()
-        super(TestMenuBaseLinkCaching, self).tearDown()
+        super().tearDown()
 
     def test_no_cache_when_there_is_no_request(self):
         # Calling login() would cause a new interaction to be setup with a
diff --git a/lib/lp/services/webapp/tests/test_navigation.py b/lib/lp/services/webapp/tests/test_navigation.py
index c62f739..1c80061 100644
--- a/lib/lp/services/webapp/tests/test_navigation.py
+++ b/lib/lp/services/webapp/tests/test_navigation.py
@@ -83,7 +83,7 @@ class IThing(Interface):
 
 
 @implementer(IThing)
-class Thing(object):
+class Thing:
     pass
 
 
diff --git a/lib/lp/services/webapp/tests/test_notifications.py b/lib/lp/services/webapp/tests/test_notifications.py
index 7ecccc2..7ee572e 100644
--- a/lib/lp/services/webapp/tests/test_notifications.py
+++ b/lib/lp/services/webapp/tests/test_notifications.py
@@ -33,10 +33,10 @@ class MockSession(dict):
 
     def __getitem__(self, key):
         try:
-            return super(MockSession, self).__getitem__(key)
+            return super().__getitem__(key)
         except KeyError:
             self[key] = MockSessionData()
-            return super(MockSession, self).__getitem__(key)
+            return super().__getitem__(key)
 
 
 @implementer(ISessionData)
diff --git a/lib/lp/services/webapp/tests/test_pageid.py b/lib/lp/services/webapp/tests/test_pageid.py
index 9f3c3f6..ea302b2 100644
--- a/lib/lp/services/webapp/tests/test_pageid.py
+++ b/lib/lp/services/webapp/tests/test_pageid.py
@@ -41,16 +41,16 @@ class FakeCollectionResourceView(FakeView):
     """A view object that provides ICollectionResource."""
 
     def __init__(self):
-        super(FakeCollectionResourceView, self).__init__()
+        super().__init__()
         self.type_url = (
-            u'https://launchpad.test/api/devel/#milestone-page-resource')
+            'https://launchpad.test/api/devel/#milestone-page-resource')
 
 
 class LaunchpadBrowserPublicationPageIDTestCase(TestCase):
     """Ensure that the web service enhances the page ID correctly."""
 
     def setUp(self):
-        super(LaunchpadBrowserPublicationPageIDTestCase, self).setUp()
+        super().setUp()
         self.publication = LaunchpadBrowserPublication(db=None)
         self.view = FakeView()
         self.context = FakeContext()
@@ -100,7 +100,7 @@ class TestWebServicePageIDs(TestCase):
     """Ensure that the web service enhances the page ID correctly."""
 
     def setUp(self):
-        super(TestWebServicePageIDs, self).setUp()
+        super().setUp()
         self.publication = WebServicePublication(db=None)
         self.view = FakeView()
         self.context = FakeContext()
@@ -136,7 +136,7 @@ class TestCollectionResourcePageIDs(TestCase):
     """Ensure page ids for collections display the origin page resource."""
 
     def setUp(self):
-        super(TestCollectionResourcePageIDs, self).setUp()
+        super().setUp()
         self.publication = WebServicePublication(db=None)
         self.view = FakeCollectionResourceView()
         self.context = FakeContext()
@@ -156,7 +156,7 @@ class TestPageIdCorners(TestCase):
     """Ensure that the page ID generation handles corner cases well."""
 
     def setUp(self):
-        super(TestPageIdCorners, self).setUp()
+        super().setUp()
         self.publication = WebServicePublication(db=None)
         self.view = FakeView()
         self.context = FakeContext()
diff --git a/lib/lp/services/webapp/tests/test_pgsession.py b/lib/lp/services/webapp/tests/test_pgsession.py
index f1e7613..b982ccc 100644
--- a/lib/lp/services/webapp/tests/test_pgsession.py
+++ b/lib/lp/services/webapp/tests/test_pgsession.py
@@ -41,7 +41,7 @@ class TestPgSession(TestCase):
     layer = LaunchpadFunctionalLayer
 
     def setUp(self):
-        super(TestPgSession, self).setUp()
+        super().setUp()
         self.sdc = PGSessionDataContainer()
         self.addCleanup(delattr, self, 'sdc')
         LaunchpadLayer.resetSessionDb()
@@ -170,8 +170,8 @@ class TestPgSession(TestCase):
     def test_datetime_compatibility(self):
         # datetime objects serialized by either Python 2 or 3 can be
         # unserialized as part of the session.
-        client_id = u'Client Id #1'
-        product_id = u'Product Id'
+        client_id = 'Client Id #1'
+        product_id = 'Product Id'
         expected_datetime = datetime(2021, 3, 4, 0, 50, 1, 300000)
 
         session = self.sdc[client_id]
@@ -194,12 +194,12 @@ class TestPgSession(TestCase):
         store = self.sdc.store
         store.execute(
             "SELECT set_session_pkg_data(?, ?, ?, ?)",
-            (session.hashed_client_id, product_id, u'logintime',
+            (session.hashed_client_id, product_id, 'logintime',
              python_2_pickle),
             noresult=True)
         store.execute(
             "SELECT set_session_pkg_data(?, ?, ?, ?)",
-            (session.hashed_client_id, product_id, u'last_write',
+            (session.hashed_client_id, product_id, 'last_write',
              python_3_pickle),
             noresult=True)
 
diff --git a/lib/lp/services/webapp/tests/test_publication.py b/lib/lp/services/webapp/tests/test_publication.py
index 57d63a9..ff6e580 100644
--- a/lib/lp/services/webapp/tests/test_publication.py
+++ b/lib/lp/services/webapp/tests/test_publication.py
@@ -126,7 +126,7 @@ class TestWebServicePublication(TestCaseWithFactory):
         self.assertNotEqual(person.id, person.account.id)
 
         # Create an OAuth access token for our new person.
-        consumer = getUtility(IOAuthConsumerSet).new(u'test-consumer')
+        consumer = getUtility(IOAuthConsumerSet).new('test-consumer')
         request_token, _ = consumer.newRequestToken()
         request_token.review(
             person, permission=OAuthPermission.READ_PUBLIC, context=None)
@@ -293,7 +293,7 @@ class TestBlockingOffsitePosts(TestCase):
     def test_openid_callback_with_query_string(self):
         # An OpenId provider (OP) may post to the +openid-callback URL with a
         # query string and without a referer.  These posts need to be allowed.
-        path_info = u'/+openid-callback?starting_url=...'
+        path_info = '/+openid-callback?starting_url=...'
         request = LaunchpadTestRequest(
             method='POST', environ=dict(PATH_INFO=path_info))
         # this call shouldn't raise an exception
@@ -338,7 +338,7 @@ class TestPublisherStats(StatsMixin, TestCaseWithFactory):
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        super(TestPublisherStats, self).setUp()
+        super().setUp()
         self.setUpStats()
 
     def test_traversal_stats(self):
diff --git a/lib/lp/services/webapp/tests/test_publisher.py b/lib/lp/services/webapp/tests/test_publisher.py
index 728e228..2aa1784 100644
--- a/lib/lp/services/webapp/tests/test_publisher.py
+++ b/lib/lp/services/webapp/tests/test_publisher.py
@@ -44,7 +44,7 @@ class TestLaunchpadView(TestCaseWithFactory):
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        super(TestLaunchpadView, self).setUp()
+        super().setUp()
         flag_info.append(
             ('test_feature', 'boolean', 'documentation', 'default_value_1',
              'title', 'http://wiki.lp.dev/LEP/sample'))
@@ -55,7 +55,7 @@ class TestLaunchpadView(TestCaseWithFactory):
     def tearDown(self):
         flag_info.pop()
         flag_info.pop()
-        super(TestLaunchpadView, self).tearDown()
+        super().tearDown()
 
     def test_getCacheJSON_non_resource_context(self):
         view = LaunchpadView(object(), LaunchpadTestRequest())
@@ -159,13 +159,13 @@ class TestLaunchpadView(TestCaseWithFactory):
     def test_call_render_with_isRedirected(self):
         class TestView(LaunchpadView):
             def render(self):
-                return u'rendered'
+                return 'rendered'
         request = LaunchpadTestRequest()
         view = TestView(object(), request)
         request.response.setStatus(200)
-        self.assertEqual(u'rendered', view())
+        self.assertEqual('rendered', view())
         request.response.setStatus(301)
-        self.assertEqual(u'', view())
+        self.assertEqual('', view())
 
     def test_related_feature_info__default(self):
         # By default, LaunchpadView.related_feature_info is empty.
@@ -195,10 +195,10 @@ class TestLaunchpadView(TestCaseWithFactory):
             {},
             (
                 {
-                    u'flag': u'test_feature',
-                    u'scope': u'default',
-                    u'priority': 0,
-                    u'value': u'on',
+                    'flag': 'test_feature',
+                    'scope': 'default',
+                    'priority': 0,
+                    'value': 'on',
                     },
                 )))
         request = LaunchpadTestRequest()
@@ -219,10 +219,10 @@ class TestLaunchpadView(TestCaseWithFactory):
             {},
             (
                 {
-                    u'flag': u'test_feature',
-                    u'scope': u'pageid:foo',
-                    u'priority': 0,
-                    u'value': u'on',
+                    'flag': 'test_feature',
+                    'scope': 'pageid:foo',
+                    'priority': 0,
+                    'value': 'on',
                     },
                 ),
             override_scope_lookup=lambda scope_name: True))
@@ -244,16 +244,16 @@ class TestLaunchpadView(TestCaseWithFactory):
         # a more restricted scope.
         def makeFeatureDict(flag, value, scope, priority):
             return {
-                u'flag': flag,
-                u'scope': scope,
-                u'priority': priority,
-                u'value': value,
+                'flag': flag,
+                'scope': scope,
+                'priority': priority,
+                'value': value,
                 }
         return (
-            makeFeatureDict('test_feature', default_value, u'default', 0),
-            makeFeatureDict('test_feature', scope_value, u'pageid:foo', 10),
-            makeFeatureDict('test_feature_2', default_value, u'default', 0),
-            makeFeatureDict('test_feature_2', scope_value, u'pageid:bar', 10))
+            makeFeatureDict('test_feature', default_value, 'default', 0),
+            makeFeatureDict('test_feature', scope_value, 'pageid:foo', 10),
+            makeFeatureDict('test_feature_2', default_value, 'default', 0),
+            makeFeatureDict('test_feature_2', scope_value, 'pageid:bar', 10))
 
     def test_related_features__enabled_feature_with_default(self):
         # If a view
@@ -262,7 +262,7 @@ class TestLaunchpadView(TestCaseWithFactory):
         #     but have different values,
         # then the property related_feature_info contains this feature flag.
         self.useFixture(FeatureFixture(
-            {}, self.makeFeatureFlagDictionaries(u'', u'on'),
+            {}, self.makeFeatureFlagDictionaries('', 'on'),
             override_scope_lookup=lambda scope_name: True))
         request = LaunchpadTestRequest()
         view = LaunchpadView(object(), request)
@@ -286,7 +286,7 @@ class TestLaunchpadView(TestCaseWithFactory):
         # Unless related_features forces it to always be beta, and the
         # flag is set.
         self.useFixture(FeatureFixture(
-            {}, self.makeFeatureFlagDictionaries(u'on', u'on'),
+            {}, self.makeFeatureFlagDictionaries('on', 'on'),
             override_scope_lookup=lambda scope_name: True))
         request = LaunchpadTestRequest()
         view = LaunchpadView(object(), request)
@@ -307,7 +307,7 @@ class TestLaunchpadView(TestCaseWithFactory):
         }}, view.related_feature_info)
 
         self.useFixture(FeatureFixture(
-            {}, self.makeFeatureFlagDictionaries(u'on', u''),
+            {}, self.makeFeatureFlagDictionaries('on', ''),
             override_scope_lookup=lambda scope_name: True))
         self.assertEqual({'test_feature': {
             'is_beta': False,
@@ -322,7 +322,7 @@ class TestLaunchpadView(TestCaseWithFactory):
             related_features = {'test_feature': False}
 
         self.useFixture(FeatureFixture(
-            {}, self.makeFeatureFlagDictionaries(u'', u'on'),
+            {}, self.makeFeatureFlagDictionaries('', 'on'),
             override_scope_lookup=lambda scope_name: True))
         request = LaunchpadTestRequest()
         view = TestView(object(), request)
@@ -351,7 +351,7 @@ class TestLaunchpadView(TestCaseWithFactory):
             related_features = {'test_feature_2': False}
 
         self.useFixture(FeatureFixture(
-            {}, self.makeFeatureFlagDictionaries(u'', u'on'),
+            {}, self.makeFeatureFlagDictionaries('', 'on'),
             override_scope_lookup=lambda scope_name: True))
         request = LaunchpadTestRequest()
         view = TestView(object(), request)
@@ -385,7 +385,7 @@ class TestLaunchpadView(TestCaseWithFactory):
     def test_view_privacy(self):
         # View privacy is based on the context.
         @implementer(IPrivacy)
-        class PrivateObject(object):
+        class PrivateObject:
 
             def __init__(self, private):
                 self.private = private
@@ -401,13 +401,13 @@ class TestLaunchpadView(TestCaseWithFactory):
             related_features = {'test_feature': False}
 
         self.useFixture(FeatureFixture(
-            {}, self.makeFeatureFlagDictionaries(u'', u'on'),
+            {}, self.makeFeatureFlagDictionaries('', 'on'),
             override_scope_lookup=lambda scope_name: True))
         request = LaunchpadTestRequest()
         view = TestView(object(), request)
         expected_beta_features = [{
             'url': 'http://wiki.lp.dev/LEP/sample', 'is_beta': True,
-            'value': u'on', 'title': 'title'}]
+            'value': 'on', 'title': 'title'}]
         self.assertEqual(expected_beta_features, view.beta_features)
 
     def test_view_beta_features_mixed(self):
@@ -418,7 +418,7 @@ class TestLaunchpadView(TestCaseWithFactory):
 
         # Select one flag on 'default', one flag not on 'default. 'default'
         # setting determines whether flags correspond to 'beta' features.
-        raw_flag_dicts = self.makeFeatureFlagDictionaries(u'', u'on')
+        raw_flag_dicts = self.makeFeatureFlagDictionaries('', 'on')
         flag_dicts = [raw_flag_dicts[1], raw_flag_dicts[2]]
 
         self.useFixture(FeatureFixture(
@@ -427,7 +427,7 @@ class TestLaunchpadView(TestCaseWithFactory):
         view = TestView(object(), request)
         expected_beta_features = [{
             'url': 'http://wiki.lp.dev/LEP/sample', 'is_beta': True,
-            'value': u'on', 'title': 'title'}]
+            'value': 'on', 'title': 'title'}]
         self.assertEqual(expected_beta_features, view.beta_features)
 
 
diff --git a/lib/lp/services/webapp/tests/test_servers.py b/lib/lp/services/webapp/tests/test_servers.py
index 7b288fe..a24735f 100644
--- a/lib/lp/services/webapp/tests/test_servers.py
+++ b/lib/lp/services/webapp/tests/test_servers.py
@@ -156,7 +156,7 @@ class TestVhostWebserviceFactory(WebServiceTestCase):
         pass
 
     def setUp(self):
-        super(TestVhostWebserviceFactory, self).setUp()
+        super().setUp()
         # XXX We have to use a real hostname.
         self.factory = VHostWebServiceRequestPublicationFactory(
             'bugs', self.VHostTestBrowserRequest, self.VHostTestPublication)
@@ -309,7 +309,7 @@ class TestWebServiceRequestTraversal(WebServiceTestCase):
     testmodule_objects = [IGenericEntry, IGenericCollection]
 
     def setUp(self):
-        super(TestWebServiceRequestTraversal, self).setUp()
+        super().setUp()
 
         # For this test we need to make the URL "/foo" resolve to a
         # resource.  To this end, we'll define a top-level collection
@@ -424,7 +424,7 @@ class TestBasicLaunchpadRequest(TestCase):
         bad_path = b'fnord/trunk\xE4'
         env = {'PATH_INFO': bad_path}
         request = LaunchpadBrowserRequest(io.BytesIO(b''), env)
-        self.assertEqual(u'fnord/trunk\ufffd', request.getHeader('PATH_INFO'))
+        self.assertEqual('fnord/trunk\ufffd', request.getHeader('PATH_INFO'))
 
     def test_baserequest_preserves_path_info_unicode(self):
         # If the request object receives PATH_INFO as Unicode, it is passed
@@ -432,10 +432,10 @@ class TestBasicLaunchpadRequest(TestCase):
         # is required to be a native string, which on Python 2 is bytes.
         # (As explained in BasicLaunchpadRequest.__init__, non-ASCII
         # characters will be rejected later during traversal.)
-        bad_path = u'fnord/trunk\xE4'
+        bad_path = 'fnord/trunk\xE4'
         env = {'PATH_INFO': bad_path}
         request = LaunchpadBrowserRequest(io.BytesIO(b''), env)
-        self.assertEqual(u'fnord/trunk\xE4', request.getHeader('PATH_INFO'))
+        self.assertEqual('fnord/trunk\xE4', request.getHeader('PATH_INFO'))
 
     def test_baserequest_logging_context_no_host_header(self):
         Context.new()
@@ -469,7 +469,7 @@ class TestBasicLaunchpadRequest(TestCase):
         env = {'QUERY_STRING': 'field.title=subproc%E9s '}
         request = LaunchpadBrowserRequest(io.BytesIO(b''), env)
         self.assertEqual(
-            [u'subproc\ufffds '], request.query_string_params['field.title'])
+            ['subproc\ufffds '], request.query_string_params['field.title'])
 
 
 class LaunchpadBrowserResponseHeaderInjection(TestCase):
@@ -642,7 +642,7 @@ class ThingSet:
 class TestLaunchpadBrowserRequest_getNearest(TestCase):
 
     def setUp(self):
-        super(TestLaunchpadBrowserRequest_getNearest, self).setUp()
+        super().setUp()
         self.request = LaunchpadBrowserRequest('', {})
         self.thing_set = ThingSet()
         self.thing = Thing()
@@ -730,7 +730,7 @@ class TestLaunchpadBrowserRequest(TestCase):
         # Encoded query string parameters are properly decoded.
         request = self.prepareRequest({'QUERY_STRING': "a=%C3%A7"})
         self.assertEqual(
-            {'a': [u'\xe7']},
+            {'a': ['\xe7']},
             request.query_string_params,
             "The query_string_params dict correctly interprets encoded "
             "parameters.")
@@ -740,7 +740,7 @@ class TestWebServiceRequestToBrowserRequest(WebServiceTestCase):
 
     def test_unicode_path_info(self):
         web_service_request = WebServiceTestRequest(
-            PATH_INFO=u'/api/devel\u1234'.encode('utf-8'))
+            PATH_INFO='/api/devel\u1234'.encode())
         browser_request = web_service_request_to_browser_request(
             web_service_request)
         self.assertEqual(
diff --git a/lib/lp/services/webapp/tests/test_statementtracer.py b/lib/lp/services/webapp/tests/test_statementtracer.py
index c751b4f..a78ef8a 100644
--- a/lib/lp/services/webapp/tests/test_statementtracer.py
+++ b/lib/lp/services/webapp/tests/test_statementtracer.py
@@ -59,8 +59,7 @@ class StubConnection:
         self._database = type('StubDatabase', (), dict(name='stub-database'))
 
     def to_database(self, params):
-        for param in params:
-            yield param
+        yield from params
 
 
 class StubCursor:
@@ -70,14 +69,14 @@ class StubCursor:
         # most types, but we can't use psycopg2's mogrify without a real
         # connection.
         mangled_params = tuple(
-            repr(p) if isinstance(p, six.string_types) else p for p in params)
+            repr(p) if isinstance(p, str) else p for p in params)
         return statement % tuple(mangled_params)
 
 
 class TestLoggingOutsideOfRequest(TestCase):
 
     def setUp(self):
-        super(TestLoggingOutsideOfRequest, self).setUp()
+        super().setUp()
         self.connection = StubConnection()
         self.cursor = StubCursor()
         original_time = da.time
@@ -284,7 +283,7 @@ class TestLoggingWithinRequest(TestCaseWithFactory):
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        super(TestLoggingWithinRequest, self).setUp()
+        super().setUp()
         self.connection = StubConnection()
         self.person = self.factory.makePerson()
         da.set_request_started(1000.0)
diff --git a/lib/lp/services/webapp/vocabulary.py b/lib/lp/services/webapp/vocabulary.py
index cffb141..309d871 100644
--- a/lib/lp/services/webapp/vocabulary.py
+++ b/lib/lp/services/webapp/vocabulary.py
@@ -57,12 +57,12 @@ class ForgivingSimpleVocabulary(SimpleVocabulary):
         self._default_term = kws.pop('default_term', missing)
         if self._default_term is missing:
             raise TypeError('required argument "default_term" not provided')
-        return super(ForgivingSimpleVocabulary, self).__init__(*args, **kws)
+        return super().__init__(*args, **kws)
 
     def getTerm(self, value):
         """Look up a value, returning the default if it is not found."""
         try:
-            return super(ForgivingSimpleVocabulary, self).getTerm(value)
+            return super().getTerm(value)
         except LookupError:
             return self._default_term
 
@@ -258,7 +258,7 @@ class FilteredVocabularyBase:
                     or func.__name__ == 'search')):
             def do_search(
                     query=None, vocab_filter=None, *args, **kwargs):
-                if isinstance(vocab_filter, six.string_types):
+                if isinstance(vocab_filter, str):
                     for filter in self.supportedFilters():
                         if filter.name == vocab_filter:
                             vocab_filter = filter
@@ -611,6 +611,6 @@ class NamedStormHugeVocabulary(NamedStormVocabulary):
     step_title = "Search"
 
     def __init__(self, context=None):
-        super(NamedStormHugeVocabulary, self).__init__(context)
+        super().__init__(context)
         if self.displayname is None:
             self.displayname = "Select %s" % self.__class__.__name__
diff --git a/lib/lp/services/webapp/wsgi.py b/lib/lp/services/webapp/wsgi.py
index 7ec7966..7597602 100644
--- a/lib/lp/services/webapp/wsgi.py
+++ b/lib/lp/services/webapp/wsgi.py
@@ -27,7 +27,7 @@ from lp.services.config import config
 
 
 @implementer(IParticipation)
-class SystemConfigurationParticipation(object):
+class SystemConfigurationParticipation:
 
     principal = system_user
     interaction = None
diff --git a/lib/lp/services/webhooks/browser.py b/lib/lp/services/webhooks/browser.py
index e2e1b6a..e523bb6 100644
--- a/lib/lp/services/webhooks/browser.py
+++ b/lib/lp/services/webhooks/browser.py
@@ -152,7 +152,7 @@ class WebhookView(LaunchpadEditFormView):
     custom_widget_event_types = LabeledMultiCheckBoxWidget
 
     def initialize(self):
-        super(WebhookView, self).initialize()
+        super().initialize()
         cache = IJSONRequestCache(self.request)
         cache.objects['deliveries'] = list(self.deliveries.batch)
         cache.objects.update(
diff --git a/lib/lp/services/webhooks/interfaces.py b/lib/lp/services/webhooks/interfaces.py
index a63caf1..54f470a 100644
--- a/lib/lp/services/webhooks/interfaces.py
+++ b/lib/lp/services/webhooks/interfaces.py
@@ -40,7 +40,6 @@ from lazr.restful.fields import (
     Reference,
     )
 from lazr.restful.interface import copy_field
-import six
 from zope.interface import (
     Attribute,
     Interface,
@@ -106,8 +105,8 @@ class AnyWebhookEventTypeVocabulary(SimpleVocabulary):
     def __init__(self, context):
         terms = [
             self.createTerm(key, key, value)
-            for key, value in six.iteritems(WEBHOOK_EVENT_TYPES)]
-        super(AnyWebhookEventTypeVocabulary, self).__init__(terms)
+            for key, value in WEBHOOK_EVENT_TYPES.items()]
+        super().__init__(terms)
 
 
 class ValidWebhookEventTypeVocabulary(SimpleVocabulary):
@@ -122,7 +121,7 @@ class ValidWebhookEventTypeVocabulary(SimpleVocabulary):
         terms = [
             self.createTerm(key, key, WEBHOOK_EVENT_TYPES[key])
             for key in target.valid_webhook_event_types]
-        super(ValidWebhookEventTypeVocabulary, self).__init__(terms)
+        super().__init__(terms)
 
 
 @exported_as_webservice_entry(as_of='beta')
diff --git a/lib/lp/services/webhooks/model.py b/lib/lp/services/webhooks/model.py
index 1d1edba..aed49ae 100644
--- a/lib/lp/services/webhooks/model.py
+++ b/lib/lp/services/webhooks/model.py
@@ -24,7 +24,6 @@ from lazr.enum import (
     )
 import psutil
 from pytz import utc
-import six
 from six.moves.urllib.parse import urlsplit
 from storm.expr import Desc
 from storm.properties import (
@@ -178,7 +177,7 @@ class Webhook(StormBase):
         # The correctness of the values is also checked by zope.schema,
         # but best to be safe.
         assert isinstance(event_types, (list, tuple))
-        assert all(isinstance(v, six.string_types) for v in event_types)
+        assert all(isinstance(v, str) for v in event_types)
         updated_data['event_types'] = event_types
         self.json_data = updated_data
 
@@ -353,7 +352,7 @@ class WebhookJob(StormBase):
         :param json_data: The type-specific variables, as a JSON-compatible
             dict.
         """
-        super(WebhookJob, self).__init__()
+        super().__init__()
         self.job = Job(**job_args)
         self.webhook = webhook
         self.job_type = job_type
@@ -422,7 +421,7 @@ def _redact_payload(event_type, payload):
         elif (event_type.startswith("merge-proposal:") and
               key in ("commit_message", "whiteboard", "description")):
             redacted[key] = "<redacted>"
-        elif isinstance(value, six.string_types) and "\n" in value:
+        elif isinstance(value, str) and "\n" in value:
             redacted[key] = "%s\n<redacted>" % value.split("\n", 1)[0]
         else:
             redacted[key] = value
@@ -480,7 +479,7 @@ class WebhookDeliveryJob(WebhookJobDerived):
             for i in addresses:
                 try:
                     addrs.append(
-                        ipaddress.ip_address(six.text_type(i.broadcast)))
+                        ipaddress.ip_address(str(i.broadcast)))
                 except (ValueError, ipaddress.AddressValueError):
                     pass
         return addrs
@@ -494,16 +493,16 @@ class WebhookDeliveryJob(WebhookJobDerived):
             - URL's host matches one of the self.limited_effort_host_patterns
         """
         url = self.webhook.delivery_url
-        netloc = six.text_type(urlsplit(url).netloc)
+        netloc = str(urlsplit(url).netloc)
         if any(i.match(netloc) for i in self.limited_effort_host_patterns):
             return True
         try:
             ip = ipaddress.ip_address(netloc)
         except (ValueError, ipaddress.AddressValueError):
             try:
-                resolved_addr = six.text_type(socket.gethostbyname(netloc))
+                resolved_addr = str(socket.gethostbyname(netloc))
                 ip = ipaddress.ip_address(resolved_addr)
-            except socket.error:
+            except OSError:
                 # If we could not resolve, we limit the effort to delivery.
                 return True
         return (
diff --git a/lib/lp/services/webhooks/payload.py b/lib/lp/services/webhooks/payload.py
index c5f3545..6c43e54 100644
--- a/lib/lp/services/webhooks/payload.py
+++ b/lib/lp/services/webhooks/payload.py
@@ -28,7 +28,7 @@ class WebhookPayloadRequest(LaunchpadBrowserRequest):
     """An internal fake request used while composing webhook payloads."""
 
     def __init__(self):
-        super(WebhookPayloadRequest, self).__init__(BytesIO(), {})
+        super().__init__(BytesIO(), {})
 
 
 @implementer(IAbsoluteURL)
diff --git a/lib/lp/services/webhooks/testing.py b/lib/lp/services/webhooks/testing.py
index f599e63..5e14d1a 100644
--- a/lib/lp/services/webhooks/testing.py
+++ b/lib/lp/services/webhooks/testing.py
@@ -42,11 +42,11 @@ class LogsScheduledWebhooks(MatchesSetwise):
     """
 
     def __init__(self, expected_webhooks):
-        super(LogsScheduledWebhooks, self).__init__(*(
+        super().__init__(*(
             LogsOneScheduledWebhook(webhook, event_type, payload_matcher)
             for webhook, event_type, payload_matcher in expected_webhooks))
 
     def match(self, logger_output):
-        return super(LogsScheduledWebhooks, self).match(
+        return super().match(
             [line for line in logger_output.splitlines()
              if line.startswith("Scheduled <WebhookDeliveryJob ")])
diff --git a/lib/lp/services/webhooks/tests/test_browser.py b/lib/lp/services/webhooks/tests/test_browser.py
index 8175520..12089e1 100644
--- a/lib/lp/services/webhooks/tests/test_browser.py
+++ b/lib/lp/services/webhooks/tests/test_browser.py
@@ -101,7 +101,7 @@ class SnapTestHelpers:
         ]
 
     def setUp(self):
-        super(SnapTestHelpers, self).setUp()
+        super().setUp()
         snap_store_client = FakeMethod()
         snap_store_client.listChannels = FakeMethod(result=[])
         self.useFixture(
@@ -125,7 +125,7 @@ class LiveFSTestHelpers:
     ]
 
     def setUp(self):
-        super(LiveFSTestHelpers, self).setUp()
+        super().setUp()
 
     def makeTarget(self):
         self.useFixture(FeatureFixture({'webhooks.new.enabled': 'true',
@@ -145,7 +145,7 @@ class OCIRecipeTestHelpers:
         ]
 
     def setUp(self):
-        super(OCIRecipeTestHelpers, self).setUp()
+        super().setUp()
 
     def makeTarget(self):
         self.useFixture(FeatureFixture({
@@ -186,7 +186,7 @@ class CharmRecipeTestHelpers:
 class WebhookTargetViewTestHelpers:
 
     def setUp(self):
-        super(WebhookTargetViewTestHelpers, self).setUp()
+        super().setUp()
         self.useFixture(FeatureFixture({'webhooks.new.enabled': 'true'}))
         self.target = self.makeTarget()
         self.owner = self.target.owner
@@ -221,7 +221,7 @@ class TestWebhooksViewBase(WebhookTargetViewTestHelpers):
     def makeHooksAndMatchers(self, count):
         hooks = [
             self.factory.makeWebhook(
-                target=self.target, delivery_url=u'http://example.com/%d' % i)
+                target=self.target, delivery_url='http://example.com/%d' % i)
             for i in range(count)]
         # There is a link to each webhook.
         link_matchers = [
@@ -439,12 +439,12 @@ class TestWebhookAddViewCharmRecipe(
 class WebhookViewTestHelpers:
 
     def setUp(self):
-        super(WebhookViewTestHelpers, self).setUp()
+        super().setUp()
         self.useFixture(FeatureFixture({'webhooks.new.enabled': 'true'}))
         self.target = self.makeTarget()
         self.owner = self.target.owner
         self.webhook = self.factory.makeWebhook(
-            target=self.target, delivery_url=u'http://example.com/original')
+            target=self.target, delivery_url='http://example.com/original')
         login_person(self.owner)
 
     def makeView(self, name, **kwargs):
diff --git a/lib/lp/services/webhooks/tests/test_job.py b/lib/lp/services/webhooks/tests/test_job.py
index d196e34..1103570 100644
--- a/lib/lp/services/webhooks/tests/test_job.py
+++ b/lib/lp/services/webhooks/tests/test_job.py
@@ -297,7 +297,7 @@ class TestWebhookDeliveryJob(TestCaseWithFactory):
     def makeAndRunJob(self, response_status=200, raises=None, mock=True,
                       secret=None, active=True):
         hook = self.factory.makeWebhook(
-            delivery_url=u'http://example.com/ep', secret=secret,
+            delivery_url='http://example.com/ep', secret=secret,
             active=active)
         job = WebhookDeliveryJob.create(hook, 'test', payload={'foo': 'bar'})
 
@@ -447,7 +447,7 @@ class TestWebhookDeliveryJob(TestCaseWithFactory):
         # PubSubHubbub-compatible way.
         with CaptureOops() as oopses:
             job, reqs = self.makeAndRunJob(
-                response_status=200, secret=u'sekrit')
+                response_status=200, secret='sekrit')
         self.assertEqual([
             ('POST', 'http://example.com/ep',
              {'Content-Type': 'application/json',
@@ -633,7 +633,7 @@ class TestWebhookDeliveryJob(TestCaseWithFactory):
 
     def test_automatic_retries(self):
         self.useFixture(MockPatch("psutil.net_if_addrs", return_value={}))
-        hook = self.factory.makeWebhook(delivery_url=u"http://192.168.1.1/";)
+        hook = self.factory.makeWebhook(delivery_url="http://192.168.1.1/";)
         job = WebhookDeliveryJob.create(hook, 'test', payload={'foo': 'bar'})
         client = MockWebhookClient(response_status=503)
         self.useFixture(ZopeUtilityFixture(client, IWebhookClient))
@@ -761,11 +761,11 @@ class TestWebhookDeliveryJob(TestCaseWithFactory):
         self.assertEqual(JobStatus.FAILED, job.status)
 
     def test_retries_matching_url_with_limited_effort(self):
-        self.assertRetriesWithLimitedEffort(u"http://foo.lxd/path";)
+        self.assertRetriesWithLimitedEffort("http://foo.lxd/path";)
 
     def test_retries_localhost_with_limited_effort(self):
-        self.assertRetriesWithLimitedEffort(u"http://localhost/";)
-        self.assertRetriesWithLimitedEffort(u"http://127.0.0.1/";)
+        self.assertRetriesWithLimitedEffort("http://localhost/";)
+        self.assertRetriesWithLimitedEffort("http://127.0.0.1/";)
 
     def test_broadcast_address_with_limited_effort(self):
         """Checks if we limit the delivery effort for broadcast addresses."""
@@ -776,15 +776,15 @@ class TestWebhookDeliveryJob(TestCaseWithFactory):
         WebhookDeliveryJob._get_broadcast_addresses.clean_memo()
         self.addCleanup(WebhookDeliveryJob._get_broadcast_addresses.clean_memo)
 
-        self.assertRetriesWithLimitedEffort(u"http://192.168.1.255/";)
+        self.assertRetriesWithLimitedEffort("http://192.168.1.255/";)
 
     def test_multicast_address_with_limited_effort(self):
-        self.assertRetriesWithLimitedEffort(u"http://224.0.18.255/";)
-        self.assertRetriesWithLimitedEffort(u"http://224.0.0.255/";)
+        self.assertRetriesWithLimitedEffort("http://224.0.18.255/";)
+        self.assertRetriesWithLimitedEffort("http://224.0.0.255/";)
 
     def test_not_resolvable_address_with_limited_effort(self):
         self.assertRetriesWithLimitedEffort(
-            u"http://could.not.resolve.name.invalid/";)
+            "http://could.not.resolve.name.invalid/";)
 
 
 class TestViaCronscript(TestCaseWithFactory):
@@ -792,7 +792,7 @@ class TestViaCronscript(TestCaseWithFactory):
     layer = ZopelessDatabaseLayer
 
     def test_run_from_cronscript(self):
-        hook = self.factory.makeWebhook(delivery_url=u'http://example.com/ep')
+        hook = self.factory.makeWebhook(delivery_url='http://example.com/ep')
         job = WebhookDeliveryJob.create(hook, 'test', payload={'foo': 'bar'})
         self.assertEqual(JobStatus.WAITING, job.status)
         transaction.commit()
@@ -818,7 +818,7 @@ class TestViaCelery(TestCaseWithFactory):
 
     def test_WebhookDeliveryJob(self):
         """WebhookDeliveryJob runs under Celery."""
-        hook = self.factory.makeWebhook(delivery_url=u'http://example.com/ep')
+        hook = self.factory.makeWebhook(delivery_url='http://example.com/ep')
 
         self.useFixture(FeatureFixture(
             {'jobs.celery.enabled_classes': 'WebhookDeliveryJob'}))
diff --git a/lib/lp/services/webhooks/tests/test_model.py b/lib/lp/services/webhooks/tests/test_model.py
index 5fce64d..97e7e41 100644
--- a/lib/lp/services/webhooks/tests/test_model.py
+++ b/lib/lp/services/webhooks/tests/test_model.py
@@ -139,16 +139,16 @@ class TestWebhookSetBase:
         login_person(target.owner)
         person = self.factory.makePerson()
         hook = getUtility(IWebhookSet).new(
-            target, person, u'http://path/to/something', [self.event_type],
-            True, u'sekrit')
+            target, person, 'http://path/to/something', [self.event_type],
+            True, 'sekrit')
         Store.of(hook).flush()
         self.assertEqual(target, hook.target)
         self.assertEqual(person, hook.registrant)
         self.assertIsNot(None, hook.date_created)
         self.assertEqual(hook.date_created, hook.date_last_modified)
-        self.assertEqual(u'http://path/to/something', hook.delivery_url)
+        self.assertEqual('http://path/to/something', hook.delivery_url)
         self.assertEqual(True, hook.active)
-        self.assertEqual(u'sekrit', hook.secret)
+        self.assertEqual('sekrit', hook.secret)
         self.assertEqual([self.event_type], hook.event_types)
 
     def test_getByID(self):
@@ -168,17 +168,17 @@ class TestWebhookSetBase:
         for target, name in ((target1, 'one'), (target2, 'two')):
             for i in range(3):
                 self.factory.makeWebhook(
-                    target, u'http://path/%s/%d' % (name, i))
+                    target, 'http://path/%s/%d' % (name, i))
         with person_logged_in(target1.owner):
             self.assertContentEqual(
-                [u'http://path/one/0', u'http://path/one/1',
-                 u'http://path/one/2'],
+                ['http://path/one/0', 'http://path/one/1',
+                 'http://path/one/2'],
                 [hook.delivery_url for hook in
                 getUtility(IWebhookSet).findByTarget(target1)])
         with person_logged_in(target2.owner):
             self.assertContentEqual(
-                [u'http://path/two/0', u'http://path/two/1',
-                 u'http://path/two/2'],
+                ['http://path/two/0', 'http://path/two/1',
+                 'http://path/two/2'],
                 [hook.delivery_url for hook in
                 getUtility(IWebhookSet).findByTarget(target2)])
 
@@ -187,12 +187,12 @@ class TestWebhookSetBase:
         login_person(target.owner)
         hooks = []
         for i in range(3):
-            hook = self.factory.makeWebhook(target, u'http://path/to/%d' % i)
+            hook = self.factory.makeWebhook(target, 'http://path/to/%d' % i)
             hook.ping()
             hooks.append(hook)
         self.assertEqual(3, IStore(WebhookJob).find(WebhookJob).count())
         self.assertContentEqual(
-            [u'http://path/to/0', u'http://path/to/1', u'http://path/to/2'],
+            ['http://path/to/0', 'http://path/to/1', 'http://path/to/2'],
             [hook.delivery_url for hook in
              getUtility(IWebhookSet).findByTarget(target)])
 
@@ -202,7 +202,7 @@ class TestWebhookSetBase:
         self.assertThat(recorder, HasQueryCount(Equals(4)))
 
         self.assertContentEqual(
-            [u'http://path/to/2'],
+            ['http://path/to/2'],
             [hook.delivery_url for hook in
              getUtility(IWebhookSet).findByTarget(target)])
         self.assertEqual(1, IStore(WebhookJob).find(WebhookJob).count())
diff --git a/lib/lp/services/webhooks/tests/test_webservice.py b/lib/lp/services/webhooks/tests/test_webservice.py
index f738b55..9a77c3b 100644
--- a/lib/lp/services/webhooks/tests/test_webservice.py
+++ b/lib/lp/services/webhooks/tests/test_webservice.py
@@ -48,12 +48,12 @@ class TestWebhook(TestCaseWithFactory):
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        super(TestWebhook, self).setUp()
+        super().setUp()
         target = self.factory.makeGitRepository()
         self.owner = target.owner
         with person_logged_in(self.owner):
             self.webhook = self.factory.makeWebhook(
-                target=target, delivery_url=u'http://example.com/ep')
+                target=target, delivery_url='http://example.com/ep')
             self.webhook_url = api_url(self.webhook)
         self.webservice = webservice_for_person(
             self.owner, permission=OAuthPermission.WRITE_PRIVATE)
@@ -120,7 +120,7 @@ class TestWebhook(TestCaseWithFactory):
                 status=400,
                 body=(
                     "event_types: %r isn't a valid token" %
-                    u'hg:push:0.1').encode('ASCII')))
+                    'hg:push:0.1').encode('ASCII')))
 
     def test_anon_forbidden(self):
         response = webservice_for_person(None).get(
@@ -184,14 +184,14 @@ class TestWebhook(TestCaseWithFactory):
                 self.webhook_url, 'setSecret', secret='sekrit',
                 api_version='devel').status)
         with person_logged_in(self.owner):
-            self.assertEqual(u'sekrit', self.webhook.secret)
+            self.assertEqual('sekrit', self.webhook.secret)
         self.assertEqual(
             200,
             self.webservice.named_post(
                 self.webhook_url, 'setSecret', secret='shhh',
                 api_version='devel').status)
         with person_logged_in(self.owner):
-            self.assertEqual(u'shhh', self.webhook.secret)
+            self.assertEqual('shhh', self.webhook.secret)
         self.assertEqual(
             200,
             self.webservice.named_post(
@@ -205,12 +205,12 @@ class TestWebhookDelivery(TestCaseWithFactory):
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        super(TestWebhookDelivery, self).setUp()
+        super().setUp()
         target = self.factory.makeGitRepository()
         self.owner = target.owner
         with person_logged_in(self.owner):
             self.webhook = self.factory.makeWebhook(
-                target=target, delivery_url=u'http://example.com/ep')
+                target=target, delivery_url='http://example.com/ep')
             self.webhook_url = api_url(self.webhook)
             self.delivery = self.webhook.ping()
             self.delivery_url = api_url(self.delivery)
@@ -273,7 +273,7 @@ class TestWebhookTargetBase:
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        super(TestWebhookTargetBase, self).setUp()
+        super().setUp()
         self.target = self.makeTarget()
         self.owner = self.target.owner
         self.target_url = api_url(self.target)
@@ -282,7 +282,7 @@ class TestWebhookTargetBase:
 
     def test_webhooks(self):
         with person_logged_in(self.owner):
-            for ep in (u'http://example.com/ep1', u'http://example.com/ep2'):
+            for ep in ('http://example.com/ep1', 'http://example.com/ep2'):
                 self.factory.makeWebhook(target=self.target, delivery_url=ep)
         representation = self.webservice.get(
             self.target_url + '/webhooks', api_version='devel').jsonBody()
@@ -339,10 +339,10 @@ class TestWebhookTargetBase:
 
         # The secret is set, but cannot be read back through the API.
         with person_logged_in(self.owner):
-            self.assertEqual(u'sekrit', self.target.webhooks.one().secret)
+            self.assertEqual('sekrit', self.target.webhooks.one().secret)
         representation = self.webservice.get(
             self.target_url + '/webhooks', api_version='devel').jsonBody()
-        self.assertNotIn(u'secret', representation['entries'][0])
+        self.assertNotIn('secret', representation['entries'][0])
 
     def test_newWebhook_permissions(self):
         self.useFixture(FeatureFixture({'webhooks.new.enabled': 'true'}))
diff --git a/lib/lp/services/webservice/tests/test_launchpadlib.py b/lib/lp/services/webservice/tests/test_launchpadlib.py
index 4821167..0b44231 100644
--- a/lib/lp/services/webservice/tests/test_launchpadlib.py
+++ b/lib/lp/services/webservice/tests/test_launchpadlib.py
@@ -14,7 +14,7 @@ class TestLaunchpadLib(TestCaseWithFactory):
     layer = AppServerLayer
 
     def setUp(self):
-        super(TestLaunchpadLib, self).setUp()
+        super().setUp()
         self.launchpad = salgado_with_full_permissions.login()
         self.project = self.launchpad.projects['firefox']
 
diff --git a/lib/lp/services/worlddata/interfaces/language.py b/lib/lp/services/worlddata/interfaces/language.py
index 97b3e36..a56f84e 100644
--- a/lib/lp/services/worlddata/interfaces/language.py
+++ b/lib/lp/services/worlddata/interfaces/language.py
@@ -62,55 +62,55 @@ class ILanguage(Interface):
     id = Attribute("This Language ID.")
 
     code = exported(TextLine(
-        title=u'The ISO 639 code',
+        title='The ISO 639 code',
         required=True))
 
     englishname = exported(
         TextLine(
-            title=u'The English name',
+            title='The English name',
             required=True),
         exported_as='english_name')
 
     nativename = TextLine(
-        title=u'Native name',
-        description=u'The name of this language in the language itself.',
+        title='Native name',
+        description='The name of this language in the language itself.',
         required=False)
 
     pluralforms = exported(
         Int(
-            title=u'Number of plural forms',
-            description=u'The number of plural forms this language has.',
+            title='Number of plural forms',
+            description='The number of plural forms this language has.',
             required=False),
         exported_as='plural_forms')
 
     guessed_pluralforms = Int(
-        title=u"Number of plural forms, or a reasonable guess",
+        title="Number of plural forms, or a reasonable guess",
         required=False, readonly=True)
 
     pluralexpression = exported(
         TextLine(
-            title=u'Plural form expression',
-            description=(u'The expression that relates a number of items'
-                         u' to the appropriate plural form.'),
+            title='Plural form expression',
+            description=('The expression that relates a number of items'
+                         ' to the appropriate plural form.'),
             required=False),
         exported_as='plural_expression')
 
     translators = doNotSnapshot(Field(
-        title=u'List of Person/Team that translate into this language.',
+        title='List of Person/Team that translate into this language.',
         required=True))
 
     translators_count = exported(
         Int(
-            title=u"Total number of translators for this language.",
+            title="Total number of translators for this language.",
             readonly=True))
 
     translation_teams = Field(
-        title=u'List of Teams that translate into this language.',
+        title='List of Teams that translate into this language.',
         required=True)
 
     countries = Set(
-        title=u'Spoken in',
-        description=u'List of countries this language is spoken in.',
+        title='Spoken in',
+        description='List of countries this language is spoken in.',
         required=True,
         value_type=Choice(vocabulary="CountryName"))
 
@@ -128,21 +128,21 @@ class ILanguage(Interface):
 
     visible = exported(
         Bool(
-            title=u'Visible',
+            title='Visible',
             description=(
-                u'Whether this language is visible by default.'),
+                'Whether this language is visible by default.'),
             required=True))
 
     direction = exported(
         Choice(
-            title=u'Text direction',
-            description=u'The direction of text in this language.',
+            title='Text direction',
+            description='The direction of text in this language.',
             required=True,
             vocabulary=TextDirection),
         exported_as='text_direction')
 
     displayname = TextLine(
-        title=u'The displayname of the language',
+        title='The displayname of the language',
         required=True,
         readonly=True)
 
@@ -151,14 +151,14 @@ class ILanguage(Interface):
         "language.")
 
     dashedcode = TextLine(
-        title=(u'The language code in a form suitable for use in HTML and'
-               u' XML files.'),
+        title=('The language code in a form suitable for use in HTML and'
+               ' XML files.'),
         required=True,
         readonly=True)
 
     abbreviated_text_dir = TextLine(
-        title=(u'The abbreviated form of the text direction, suitable'
-               u' for use in HTML files.'),
+        title=('The abbreviated form of the text direction, suitable'
+               ' for use in HTML files.'),
         required=True,
         readonly=True)
 
diff --git a/lib/lp/services/worlddata/model/country.py b/lib/lp/services/worlddata/model/country.py
index fa21dfb..2932858 100644
--- a/lib/lp/services/worlddata/model/country.py
+++ b/lib/lp/services/worlddata/model/country.py
@@ -3,7 +3,6 @@
 
 __all__ = ['Country', 'CountrySet', 'Continent']
 
-import six
 from zope.interface import implementer
 
 from lp.app.errors import NotFoundError
@@ -42,7 +41,7 @@ class Country(SQLBase):
     continent = ForeignKey(
         dbName='continent', foreignKey='Continent', default=None)
     languages = SQLRelatedJoin(
-        six.ensure_str('Language'), joinColumn='country',
+        'Language', joinColumn='country',
         otherColumn='language', intermediateTable='SpokenIn')
 
 
@@ -57,8 +56,7 @@ class CountrySet:
         return country
 
     def __iter__(self):
-        for row in Country.select():
-            yield row
+        yield from Country.select()
 
     def getByName(self, name):
         """See `ICountrySet`."""
diff --git a/lib/lp/services/worlddata/model/language.py b/lib/lp/services/worlddata/model/language.py
index 6359e1c..aaff902 100644
--- a/lib/lp/services/worlddata/model/language.py
+++ b/lib/lp/services/worlddata/model/language.py
@@ -65,11 +65,11 @@ class Language(SQLBase):
         default=TextDirection.LTR)
 
     translation_teams = SQLRelatedJoin(
-        six.ensure_str('Person'), joinColumn="language",
+        'Person', joinColumn="language",
         intermediateTable='Translator', otherColumn='translator')
 
     _countries = SQLRelatedJoin(
-        six.ensure_str('Country'), joinColumn='language',
+        'Country', joinColumn='language',
         otherColumn='country', intermediateTable='SpokenIn')
 
     # Define a read/write property `countries` so it can be passed
@@ -261,7 +261,7 @@ class LanguageSet:
 
     def getLanguageByCode(self, code):
         """See `ILanguageSet`."""
-        assert isinstance(code, six.string_types), (
+        assert isinstance(code, str), (
             "%s is not a valid type for 'code'" % type(code))
         return IStore(Language).find(Language, code=code).one()
 
diff --git a/lib/lp/services/worlddata/tests/test_language.py b/lib/lp/services/worlddata/tests/test_language.py
index 2861fc7..b982b35 100644
--- a/lib/lp/services/worlddata/tests/test_language.py
+++ b/lib/lp/services/worlddata/tests/test_language.py
@@ -51,7 +51,7 @@ class TestTranslatorsCounts(TestCaseWithFactory):
     layer = LaunchpadZopelessLayer
 
     def setUp(self):
-        super(TestTranslatorsCounts, self).setUp()
+        super().setUp()
         self.translated_lang = self.factory.makeLanguage(pluralforms=None)
         self.untranslated_lang = self.factory.makeLanguage(pluralforms=None)
         for i in range(3):
diff --git a/lib/lp/services/worlddata/vocabularies.py b/lib/lp/services/worlddata/vocabularies.py
index 4b5433d..bf71a92 100644
--- a/lib/lp/services/worlddata/vocabularies.py
+++ b/lib/lp/services/worlddata/vocabularies.py
@@ -64,7 +64,7 @@ class LanguageVocabulary(SQLObjectVocabularyBase):
         assert ILanguage.providedBy(language), (
             "'in LanguageVocabulary' requires ILanguage as left operand, "
             "got %s instead." % type(language))
-        return super(LanguageVocabulary, self).__contains__(language)
+        return super().__contains__(language)
 
     def toTerm(self, obj):
         """See `IVocabulary`."""