← Back to team overview

launchpad-reviewers team mailing list archive

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

 

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

Commit message:
Apply pyupgrade --py3-plus to lp.services.[a-m]*

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/413670
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:pyupgrade-py3-services-1 into launchpad:master.
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index c5316a7..5531052 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -54,6 +54,7 @@ repos:
             |oci
             |registry
             |scripts
+            |services/[a-m]
           )/
 -   repo: https://github.com/PyCQA/isort
     rev: 5.9.2
diff --git a/lib/lp/services/apachelogparser/model/parsedapachelog.py b/lib/lp/services/apachelogparser/model/parsedapachelog.py
index f5bca5d..50d8889 100644
--- a/lib/lp/services/apachelogparser/model/parsedapachelog.py
+++ b/lib/lp/services/apachelogparser/model/parsedapachelog.py
@@ -30,7 +30,7 @@ class ParsedApacheLog(Storm):
     date_last_parsed = UtcDateTimeCol(notNull=True, default=UTC_NOW)
 
     def __init__(self, first_line, bytes_read):
-        super(ParsedApacheLog, self).__init__()
+        super().__init__()
         self.first_line = six.ensure_text(first_line)
         self.bytes_read = bytes_read
         IStore(self.__class__).add(self)
diff --git a/lib/lp/services/apachelogparser/tests/test_apachelogparser.py b/lib/lp/services/apachelogparser/tests/test_apachelogparser.py
index 513ce5d..48af8bd 100644
--- a/lib/lp/services/apachelogparser/tests/test_apachelogparser.py
+++ b/lib/lp/services/apachelogparser/tests/test_apachelogparser.py
@@ -408,7 +408,7 @@ class TestParsedFilesDetection(TestCase):
     file_path = os.path.join(root, 'launchpadlibrarian.net.access-log')
 
     def setUp(self):
-        super(TestParsedFilesDetection, self).setUp()
+        super().setUp()
         switch_dbuser(DBUSER)
 
     def test_sorts_by_mtime(self):
@@ -515,14 +515,14 @@ class Test_create_or_update_parsedlog_entry(TestCase):
     layer = LaunchpadZopelessLayer
 
     def setUp(self):
-        super(Test_create_or_update_parsedlog_entry, self).setUp()
+        super().setUp()
         switch_dbuser(DBUSER)
 
     def test_creation_of_new_entries(self):
         # When given a first_line that doesn't exist in the ParsedApacheLog
         # table, create_or_update_parsedlog_entry() will create a new entry
         # with the given number of bytes read.
-        first_line = u'First line'
+        first_line = 'First line'
         create_or_update_parsedlog_entry(
             first_line, parsed_bytes=len(first_line))
 
@@ -535,7 +535,7 @@ class Test_create_or_update_parsedlog_entry(TestCase):
         # When given a first_line that already exists in the ParsedApacheLog
         # table, create_or_update_parsedlog_entry() will update that entry
         # with the given number of bytes read.
-        first_line = u'First line'
+        first_line = 'First line'
         create_or_update_parsedlog_entry(first_line, parsed_bytes=2)
         store = IStore(ParsedApacheLog)
         entry = store.find(ParsedApacheLog, first_line=first_line).one()
diff --git a/lib/lp/services/auth/enums.py b/lib/lp/services/auth/enums.py
index 351f18e..c590856 100644
--- a/lib/lp/services/auth/enums.py
+++ b/lib/lp/services/auth/enums.py
@@ -3,7 +3,6 @@
 
 """Enumerations used in lp.services.auth."""
 
-__metaclass__ = type
 __all__ = [
     "AccessTokenScope",
     ]
diff --git a/lib/lp/services/auth/interfaces.py b/lib/lp/services/auth/interfaces.py
index d18d51c..7a9927a 100644
--- a/lib/lp/services/auth/interfaces.py
+++ b/lib/lp/services/auth/interfaces.py
@@ -3,7 +3,6 @@
 
 """Personal access token interfaces."""
 
-__metaclass__ = type
 __all__ = [
     "IAccessToken",
     "IAccessTokenSet",
diff --git a/lib/lp/services/auth/model.py b/lib/lp/services/auth/model.py
index b1cc7fc..aa1ff11 100644
--- a/lib/lp/services/auth/model.py
+++ b/lib/lp/services/auth/model.py
@@ -3,7 +3,6 @@
 
 """Personal access tokens."""
 
-__metaclass__ = type
 __all__ = [
     "AccessToken",
     "AccessTokenTargetMixin",
diff --git a/lib/lp/services/auth/tests/test_model.py b/lib/lp/services/auth/tests/test_model.py
index 805b02c..82d44b0 100644
--- a/lib/lp/services/auth/tests/test_model.py
+++ b/lib/lp/services/auth/tests/test_model.py
@@ -3,8 +3,6 @@
 
 """Test personal access tokens."""
 
-__metaclass__ = type
-
 from datetime import (
     datetime,
     timedelta,
diff --git a/lib/lp/services/auth/utils.py b/lib/lp/services/auth/utils.py
index 99dd2ba..a3b956d 100644
--- a/lib/lp/services/auth/utils.py
+++ b/lib/lp/services/auth/utils.py
@@ -3,7 +3,6 @@
 
 """Personal access token utilities."""
 
-__metaclass__ = type
 __all__ = [
     "create_access_token_secret",
     ]
diff --git a/lib/lp/services/authserver/tests/test_authserver.py b/lib/lp/services/authserver/tests/test_authserver.py
index 98bdabc..272f02b 100644
--- a/lib/lp/services/authserver/tests/test_authserver.py
+++ b/lib/lp/services/authserver/tests/test_authserver.py
@@ -165,7 +165,7 @@ class MacaroonTests(TestCaseWithFactory):
     layer = ZopelessDatabaseLayer
 
     def setUp(self):
-        super(MacaroonTests, self).setUp()
+        super().setUp()
         self.issuer = DummyMacaroonIssuer()
         self.useFixture(ZopeUtilityFixture(
             self.issuer, IMacaroonIssuer, name='test'))
diff --git a/lib/lp/services/beautifulsoup.py b/lib/lp/services/beautifulsoup.py
index b61e126..6a39229 100644
--- a/lib/lp/services/beautifulsoup.py
+++ b/lib/lp/services/beautifulsoup.py
@@ -11,14 +11,12 @@ __all__ = [
 
 from bs4 import BeautifulSoup as _BeautifulSoup
 from bs4.element import SoupStrainer
-import six
 
 
 class BeautifulSoup(_BeautifulSoup):
 
     def __init__(self, markup="", features="html.parser", **kwargs):
-        if (not isinstance(markup, six.text_type) and
+        if (not isinstance(markup, str) and
                 "from_encoding" not in kwargs):
             kwargs["from_encoding"] = "UTF-8"
-        super(BeautifulSoup, self).__init__(
-            markup=markup, features=features, **kwargs)
+        super().__init__(markup=markup, features=features, **kwargs)
diff --git a/lib/lp/services/command_spawner.py b/lib/lp/services/command_spawner.py
index f2b34c8..31b8940 100644
--- a/lib/lp/services/command_spawner.py
+++ b/lib/lp/services/command_spawner.py
@@ -170,7 +170,7 @@ class CommandSpawner:
         """Read output from `pipe_file`."""
         try:
             output = pipe_file.read()
-        except IOError as e:
+        except OSError as e:
             # "Resource temporarily unavailable"--not an error really,
             # just means there's nothing to read.
             if e.errno != errno.EAGAIN:
diff --git a/lib/lp/services/comments/interfaces/conversation.py b/lib/lp/services/comments/interfaces/conversation.py
index a8bc146..aa5ff8b 100644
--- a/lib/lp/services/comments/interfaces/conversation.py
+++ b/lib/lp/services/comments/interfaces/conversation.py
@@ -28,7 +28,7 @@ from lp import _
 class IComment(Interface):
     """A comment which may have a body or footer."""
 
-    index = Int(title=u'The comment number', required=True, readonly=True)
+    index = Int(title='The comment number', required=True, readonly=True)
 
     extra_css_class = TextLine(
         description=_("A css class to apply to the comment's outer div."))
@@ -42,15 +42,15 @@ class IComment(Interface):
         readonly=True)
 
     too_long = Bool(
-        title=u'Whether the comment body is too long to display in full.',
+        title='Whether the comment body is too long to display in full.',
         readonly=True)
 
     too_long_to_render = Bool(
-        title=(u'Whether the comment body is so long that rendering is'
+        title=('Whether the comment body is so long that rendering is'
         ' inappropriate.'), readonly=True)
 
     text_for_display = Text(
-        title=u'The comment text to be displayed in the UI.', readonly=True)
+        title='The comment text to be displayed in the UI.', readonly=True)
 
     body_text = Text(
         description=_("The body text of the comment."),
diff --git a/lib/lp/services/config/__init__.py b/lib/lp/services/config/__init__.py
index d96005e..87b9b62 100644
--- a/lib/lp/services/config/__init__.py
+++ b/lib/lp/services/config/__init__.py
@@ -15,7 +15,6 @@ import sys
 
 from lazr.config import ImplicitTypeSchema
 from lazr.config.interfaces import ConfigErrors
-import six
 
 from lp.services.osutils import open_for_writing
 from lp.services.propertycache import (
@@ -288,7 +287,7 @@ class LaunchpadConfig:
 config = LaunchpadConfig()
 
 
-class DatabaseConfigOverrides(object):
+class DatabaseConfigOverrides:
     pass
 
 
@@ -330,7 +329,7 @@ class DatabaseConfig:
 
         Overriding a value to None removes the override.
         """
-        for attr, value in six.iteritems(kwargs):
+        for attr, value in kwargs.items():
             assert attr in self._db_config_attrs, (
                 "%s cannot be overridden" % attr)
             if value is None:
diff --git a/lib/lp/services/config/tests/test_config_lookup.py b/lib/lp/services/config/tests/test_config_lookup.py
index 9e32bcd..d0b4bcf 100644
--- a/lib/lp/services/config/tests/test_config_lookup.py
+++ b/lib/lp/services/config/tests/test_config_lookup.py
@@ -19,7 +19,7 @@ from lp.testing import TestCase
 class TestConfigLookup(TestCase):
 
     def setUp(self):
-        super(TestConfigLookup, self).setUp()
+        super().setUp()
         self.temp_lookup_file = None
         self.original_CONFIG_LOOKUP_FILES = config.CONFIG_LOOKUP_FILES
         self.original_LPCONFIG = os.environ['LPCONFIG']
@@ -28,7 +28,7 @@ class TestConfigLookup(TestCase):
         del self.temp_lookup_file
         config.CONFIG_LOOKUP_FILES = self.original_CONFIG_LOOKUP_FILES
         os.environ['LPCONFIG'] = self.original_LPCONFIG
-        super(TestConfigLookup, self).tearDown()
+        super().tearDown()
 
     def makeLookupFile(self):
         self.temp_lookup_file = NamedTemporaryFile()
@@ -100,7 +100,7 @@ class TestInstanceConfigDirLookup(ConfigTestCase):
     """Test where instance config directories are looked up."""
 
     def setUp(self):
-        super(TestInstanceConfigDirLookup, self).setUp()
+        super().setUp()
         self.setUpConfigRoots()
 
     def test_find_config_dir_raises_ValueError(self):
diff --git a/lib/lp/services/crypto/model.py b/lib/lp/services/crypto/model.py
index 3202656..e2467c2 100644
--- a/lib/lp/services/crypto/model.py
+++ b/lib/lp/services/crypto/model.py
@@ -15,7 +15,6 @@ from nacl.public import (
     PublicKey,
     SealedBox,
     )
-import six
 from zope.interface import implementer
 
 from lp.services.crypto.interfaces import (
@@ -46,7 +45,7 @@ class NaClEncryptedContainerBase:
             try:
                 return PublicKey(self.public_key_bytes)
             except NaClCryptoError as e:
-                six.raise_from(CryptoError(str(e)), e)
+                raise CryptoError(str(e)) from e
         else:
             return None
 
@@ -64,7 +63,7 @@ class NaClEncryptedContainerBase:
         try:
             data_encrypted = SealedBox(self.public_key).encrypt(data)
         except NaClCryptoError as e:
-            six.raise_from(CryptoError(str(e)), e)
+            raise CryptoError(str(e)) from e
         return (
             base64.b64encode(self.public_key_bytes).decode("UTF-8"),
             base64.b64encode(data_encrypted).decode("UTF-8"))
@@ -84,7 +83,7 @@ class NaClEncryptedContainerBase:
             try:
                 return PrivateKey(self.private_key_bytes)
             except NaClCryptoError as e:
-                six.raise_from(CryptoError(str(e)), e)
+                raise CryptoError(str(e)) from e
         else:
             return None
 
@@ -102,7 +101,7 @@ class NaClEncryptedContainerBase:
             public_key_bytes = base64.b64decode(public_key.encode("UTF-8"))
             encrypted_bytes = base64.b64decode(encrypted.encode("UTF-8"))
         except TypeError as e:
-            six.raise_from(CryptoError(str(e)), e)
+            raise CryptoError(str(e)) from e
         if public_key_bytes != self.public_key_bytes:
             raise ValueError(
                 "Public key %r does not match configured public key %r" %
@@ -112,4 +111,4 @@ class NaClEncryptedContainerBase:
         try:
             return SealedBox(self.private_key).decrypt(encrypted_bytes)
         except NaClCryptoError as e:
-            six.raise_from(CryptoError(str(e)), e)
+            raise CryptoError(str(e)) from e
diff --git a/lib/lp/services/daemons/tests/test_tachandler.py b/lib/lp/services/daemons/tests/test_tachandler.py
index d9cfb23..0eabfb8 100644
--- a/lib/lp/services/daemons/tests/test_tachandler.py
+++ b/lib/lp/services/daemons/tests/test_tachandler.py
@@ -34,7 +34,7 @@ from lp.testing.layers import DatabaseLayer
 class SimpleTac(TacTestSetup):
 
     def __init__(self, name, tempdir):
-        super(SimpleTac, self).__init__()
+        super().__init__()
         self.name, self.tempdir = name, tempdir
 
     @property
diff --git a/lib/lp/services/database/bulk.py b/lib/lp/services/database/bulk.py
index 517e522..9f94220 100644
--- a/lib/lp/services/database/bulk.py
+++ b/lib/lp/services/database/bulk.py
@@ -24,7 +24,6 @@ from operator import (
     itemgetter,
     )
 
-import six
 from storm.databases.postgres import Returning
 from storm.expr import (
     And,
@@ -52,7 +51,7 @@ def collate(things, key):
     collection = defaultdict(list)
     for thing in things:
         collection[key(thing)].append(thing)
-    return six.iteritems(collection)
+    return collection.items()
 
 
 def get_type(thing):
diff --git a/lib/lp/services/database/collection.py b/lib/lp/services/database/collection.py
index 94a1b5b..f580f0d 100644
--- a/lib/lp/services/database/collection.py
+++ b/lib/lp/services/database/collection.py
@@ -15,7 +15,7 @@ from storm.expr import (
 from lp.services.database.interfaces import IStore
 
 
-class Collection(object):
+class Collection:
     """An arbitrary collection of database objects.
 
     Works as a Storm wrapper: create a collection based on another
diff --git a/lib/lp/services/database/debug.py b/lib/lp/services/database/debug.py
index 2c0e5ea..5fa2da1 100644
--- a/lib/lp/services/database/debug.py
+++ b/lib/lp/services/database/debug.py
@@ -49,7 +49,7 @@ def LN(*args, **kwargs):
     return text
 
 
-class ConnectionWrapper(object):
+class ConnectionWrapper:
     _log = None
     _real_con = None
 
diff --git a/lib/lp/services/database/decoratedresultset.py b/lib/lp/services/database/decoratedresultset.py
index b31e1f8..c524b69 100644
--- a/lib/lp/services/database/decoratedresultset.py
+++ b/lib/lp/services/database/decoratedresultset.py
@@ -16,7 +16,7 @@ from zope.security.proxy import (
 
 
 @delegate_to(IResultSet, context='result_set')
-class DecoratedResultSet(object):
+class DecoratedResultSet:
     """A decorated Storm ResultSet for 'Magic' (presenter) classes.
 
     Because `DistroSeriesBinaryPackage` doesn't actually exist in the
diff --git a/lib/lp/services/database/interfaces.py b/lib/lp/services/database/interfaces.py
index 14407be..5e89515 100644
--- a/lib/lp/services/database/interfaces.py
+++ b/lib/lp/services/database/interfaces.py
@@ -35,7 +35,7 @@ class IRequestExpired(IRuntimeError):
 # think it is ever used though ...
 class ISQLBase(Interface):
     """An extension of ISQLObject that provides an ID."""
-    id = Int(title=u"The integer ID for the instance")
+    id = Int(title="The integer ID for the instance")
 
 
 #
diff --git a/lib/lp/services/database/policy.py b/lib/lp/services/database/policy.py
index 838df09..a50fe3d 100644
--- a/lib/lp/services/database/policy.py
+++ b/lib/lp/services/database/policy.py
@@ -235,7 +235,7 @@ def LaunchpadDatabasePolicyFactory(request):
     # to sniff the request 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') in [u'/+opstats', u'/+haproxy']:
+    if request.get('PATH_INFO') in ['/+opstats', '/+haproxy']:
         return DatabaseBlockedPolicy(request)
     else:
         return LaunchpadDatabasePolicy(request)
diff --git a/lib/lp/services/database/postgresql.py b/lib/lp/services/database/postgresql.py
index 6dba9d0..cec01cf 100644
--- a/lib/lp/services/database/postgresql.py
+++ b/lib/lp/services/database/postgresql.py
@@ -8,8 +8,6 @@ and table manipulation
 
 import re
 
-import six
-
 from lp.services.database.sqlbase import (
     quote,
     quoteIdentifier,
@@ -458,7 +456,7 @@ def drop_tables(cur, tables):
     """
     if tables is None or len(tables) == 0:
         return
-    if isinstance(tables, six.string_types):
+    if isinstance(tables, str):
         tables = [tables]
 
     # This syntax requires postgres 8.2 or better
diff --git a/lib/lp/services/database/sqlbase.py b/lib/lp/services/database/sqlbase.py
index 43cd316..83434cc 100644
--- a/lib/lp/services/database/sqlbase.py
+++ b/lib/lp/services/database/sqlbase.py
@@ -222,7 +222,7 @@ class SQLBase(storm.sqlobject.SQLObjectBase):
     def destroySelf(self):
         my_master = IMasterObject(self)
         if self is my_master:
-            super(SQLBase, self).destroySelf()
+            super().destroySelf()
         else:
             my_master.destroySelf()
 
diff --git a/lib/lp/services/database/sqlobject/__init__.py b/lib/lp/services/database/sqlobject/__init__.py
index a30b27d..381c7d2 100644
--- a/lib/lp/services/database/sqlobject/__init__.py
+++ b/lib/lp/services/database/sqlobject/__init__.py
@@ -6,7 +6,6 @@
 # SKIP this file when reformatting, due to the sys mangling.
 import datetime
 
-import six
 from storm.expr import SQL
 from storm.sqlobject import *  # noqa: F401,F403
 
@@ -32,7 +31,7 @@ def sqlrepr(value, dbname=None):
         return value.getquoted()
     elif isinstance(value, SQL):
         return value.expr
-    elif isinstance(value, six.string_types):
+    elif isinstance(value, str):
         for orig, repl in _sqlStringReplace:
             value = value.replace(orig, repl)
         return "E'%s'" % value
diff --git a/lib/lp/services/database/tests/test_bulk.py b/lib/lp/services/database/tests/test_bulk.py
index 03cb64d..a90d11e 100644
--- a/lib/lp/services/database/tests/test_bulk.py
+++ b/lib/lp/services/database/tests/test_bulk.py
@@ -187,9 +187,9 @@ class TestLoaders(TestCaseWithFactory):
             # test.
             (FeatureFlag.scope, FeatureFlag.priority, FeatureFlag.flag),
             sorted(
-                [(u'foo', 0, u'bar'), (u'foo', 0, u'baz'),
-                 (u'foo', 1, u'bar'), (u'foo', 1, u'quux'),
-                 (u'bar', 0, u'foo')]))
+                [('foo', 0, 'bar'), ('foo', 0, 'baz'),
+                 ('foo', 1, 'bar'), ('foo', 1, 'quux'),
+                 ('bar', 0, 'foo')]))
         self.assertEqual(
             "FeatureFlag.scope = E'bar' AND ("
             "FeatureFlag.priority = 0 AND FeatureFlag.flag IN (E'foo')) OR "
@@ -219,10 +219,10 @@ class TestLoaders(TestCaseWithFactory):
     def test_load_with_compound_primary_keys(self):
         # load() can load objects with compound primary keys.
         flags = [
-            FeatureFlag(u'foo', 0, u'bar', u'true'),
-            FeatureFlag(u'foo', 0, u'baz', u'false'),
+            FeatureFlag('foo', 0, 'bar', 'true'),
+            FeatureFlag('foo', 0, 'baz', 'false'),
             ]
-        other_flag = FeatureFlag(u'notfoo', 0, u'notbar', u'true')
+        other_flag = FeatureFlag('notfoo', 0, 'notbar', 'true')
         for flag in flags + [other_flag]:
             getFeatureStore().add(flag)
 
diff --git a/lib/lp/services/database/tests/test_isolation.py b/lib/lp/services/database/tests/test_isolation.py
index 552a13f..add0786 100644
--- a/lib/lp/services/database/tests/test_isolation.py
+++ b/lib/lp/services/database/tests/test_isolation.py
@@ -4,7 +4,6 @@
 """Tests of the isolation module."""
 
 from psycopg2.extensions import TRANSACTION_STATUS_IDLE
-import six
 from storm.zope.interfaces import IZStorm
 import transaction
 from zope.component import getUtility
@@ -33,7 +32,7 @@ class TestIsolation(TestCase):
         # transactions have been aborted.
         transaction.abort()
         for name, status in isolation.gen_store_statuses():
-            self.assertIsInstance(name, six.string_types)
+            self.assertIsInstance(name, str)
             self.assertIn(status, (None, TRANSACTION_STATUS_IDLE))
         # At least one store will not be idle when a transaction has
         # begun.
diff --git a/lib/lp/services/database/tests/test_stormbase.py b/lib/lp/services/database/tests/test_stormbase.py
index 6328abd..86b106d 100644
--- a/lib/lp/services/database/tests/test_stormbase.py
+++ b/lib/lp/services/database/tests/test_stormbase.py
@@ -36,7 +36,7 @@ class TestStormBase(TestCase):
     layer = ZopelessDatabaseLayer
 
     def setUp(self):
-        super(TestStormBase, self).setUp()
+        super().setUp()
         self.store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
         self.store.execute("CREATE TABLE StormExample (id serial PRIMARY KEY)")
 
diff --git a/lib/lp/services/database/tests/test_transaction_policy.py b/lib/lp/services/database/tests/test_transaction_policy.py
index 7122ccf..db19a4f 100644
--- a/lib/lp/services/database/tests/test_transaction_policy.py
+++ b/lib/lp/services/database/tests/test_transaction_policy.py
@@ -21,7 +21,7 @@ class TestTransactionPolicy(TestCaseWithFactory):
     layer = ZopelessDatabaseLayer
 
     def setUp(self):
-        super(TestTransactionPolicy, self).setUp()
+        super().setUp()
         # Start each test with a clean slate: no ongoing transaction
         # unless the test says so.
         transaction.abort()
diff --git a/lib/lp/services/encoding.py b/lib/lp/services/encoding.py
index f3796bd..c8e8f15 100644
--- a/lib/lp/services/encoding.py
+++ b/lib/lp/services/encoding.py
@@ -111,7 +111,7 @@ def guess(s):
 
     # Calling this method with a Unicode argument indicates a hidden bug
     # that will bite you eventually -- StuartBishop 20050709
-    if isinstance(s, six.text_type):
+    if isinstance(s, str):
         raise TypeError(
                 'encoding.guess called with Unicode string %r' % (s,)
                 )
@@ -120,7 +120,7 @@ def guess(s):
     # that can encode themselves as ASCII.
     if not isinstance(s, bytes):
         try:
-            return six.text_type(s)
+            return str(s)
         except UnicodeDecodeError:
             pass
 
@@ -128,32 +128,32 @@ def guess(s):
     try:
         for bom, encoding in _boms:
             if s.startswith(bom):
-                return six.text_type(s[len(bom):], encoding)
+                return str(s[len(bom):], encoding)
     except UnicodeDecodeError:
         pass
 
     # Try preferred encoding
     try:
-        return six.text_type(s, 'UTF-8')
+        return str(s, 'UTF-8')
     except UnicodeDecodeError:
         pass
 
     # If we have characters in this range, it is probably CP1252
     if re.search(br"[\x80-\x9f]", s) is not None:
         try:
-            return six.text_type(s, 'CP1252')
+            return str(s, 'CP1252')
         except UnicodeDecodeError:
             pass
 
     # If we have characters in this range, it is probably ISO-8859-15
     if re.search(br"[\xa4\xa6\xa8\xb4\xb8\xbc-\xbe]", s) is not None:
         try:
-            return six.text_type(s, 'ISO-8859-15')
+            return str(s, 'ISO-8859-15')
         except UnicodeDecodeError:
             pass
 
     # Otherwise we default to ISO-8859-1
-    return six.text_type(s, 'ISO-8859-1', 'replace')
+    return str(s, 'ISO-8859-1', 'replace')
 
 
 def escape_nonascii_uniquely(bogus_string):
@@ -223,7 +223,7 @@ def wsgi_native_string(s):
     Python 2, we enforce this here.
     """
     result = six.ensure_str(s, encoding='ISO-8859-1')
-    if isinstance(s, six.text_type):
+    if isinstance(s, str):
         # Ensure we're limited to ISO-8859-1.
         result.encode('ISO-8859-1')
     return result
diff --git a/lib/lp/services/features/browser/edit.py b/lib/lp/services/features/browser/edit.py
index 29e8ae0..25ebdf4 100644
--- a/lib/lp/services/features/browser/edit.py
+++ b/lib/lp/services/features/browser/edit.py
@@ -34,16 +34,16 @@ class IFeatureControlForm(Interface):
         self.context = context
 
     feature_rules = Text(
-        title=u"Feature rules",
+        title="Feature rules",
         description=(
-            u"Rules to control feature flags on Launchpad.  "
-            u"On each line: (flag, scope, priority, value), "
-            u"whitespace-separated.  Numerically higher "
-            u"priorities match first."),
+            "Rules to control feature flags on Launchpad.  "
+            "On each line: (flag, scope, priority, value), "
+            "whitespace-separated.  Numerically higher "
+            "priorities match first."),
         required=False)
     comment = Text(
-        title=u"Comment",
-        description=(u"Who requested this change and why."),
+        title="Comment",
+        description=("Who requested this change and why."),
         required=True)
 
 
@@ -71,7 +71,7 @@ class FeatureControlView(LaunchpadFormView):
         """Is the user authorized to change the rules?"""
         return check_permission('launchpad.Admin', self.context)
 
-    @action(u"Change", name="change", condition=canSubmit)
+    @action("Change", name="change", condition=canSubmit)
     def change_action(self, action, data):
         original_rules = self.request.features.rule_source.getAllRulesAsText()
         rules_text = data.get('feature_rules') or ''
@@ -83,7 +83,7 @@ class FeatureControlView(LaunchpadFormView):
         # (whitespace normalized) and ordered consistently so the diff is
         # minimal.
         new_rules = self.request.features.rule_source.getAllRulesAsText()
-        diff = u'\n'.join(self.diff_rules(original_rules, new_rules))
+        diff = '\n'.join(self.diff_rules(original_rules, new_rules))
         comment = data['comment']
         ChangeLog.append(diff, comment, self.user)
         self.diff = FormattersAPI(diff).format_diff()
diff --git a/lib/lp/services/features/browser/tests/test_changelog.py b/lib/lp/services/features/browser/tests/test_changelog.py
index ec76e38..0070832 100644
--- a/lib/lp/services/features/browser/tests/test_changelog.py
+++ b/lib/lp/services/features/browser/tests/test_changelog.py
@@ -29,7 +29,7 @@ class TestChangeLogView(TestCaseWithFactory):
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        super(TestChangeLogView, self).setUp()
+        super().setUp()
         self.root = getUtility(ILaunchpadRoot)
         self.person = self.factory.makePerson()
 
diff --git a/lib/lp/services/features/browser/tests/test_feature_editor.py b/lib/lp/services/features/browser/tests/test_feature_editor.py
index 0f4a15a..80172fe 100644
--- a/lib/lp/services/features/browser/tests/test_feature_editor.py
+++ b/lib/lp/services/features/browser/tests/test_feature_editor.py
@@ -34,7 +34,7 @@ class TestFeatureControlPage(BrowserTestCase):
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        super(TestFeatureControlPage, self).setUp()
+        super().setUp()
         self.useFixture(FakeLogger())
 
     def getUserBrowserAsTeamMember(self, teams):
@@ -72,8 +72,8 @@ class TestFeatureControlPage(BrowserTestCase):
 
     def test_feature_page_from_database(self):
         StormFeatureRuleSource().setAllRules([
-            ('ui.icing', 'default', 100, u'3.0'),
-            ('ui.icing', 'beta_user', 300, u'4.0'),
+            ('ui.icing', 'default', 100, '3.0'),
+            ('ui.icing', 'beta_user', 300, '4.0'),
             ])
         browser = self.getUserBrowserAsAdmin()
         browser.open(self.getFeatureRulesViewURL())
diff --git a/lib/lp/services/features/browser/tests/test_feature_info.py b/lib/lp/services/features/browser/tests/test_feature_info.py
index 4c8e879..3a3cf2c 100644
--- a/lib/lp/services/features/browser/tests/test_feature_info.py
+++ b/lib/lp/services/features/browser/tests/test_feature_info.py
@@ -124,7 +124,7 @@ class TestUndocumentedFeatureFlags(TestCase):
     """Test the code that records accessing of undocumented feature flags."""
 
     def setUp(self):
-        super(TestUndocumentedFeatureFlags, self).setUp()
+        super().setUp()
         # Stash away any already encountered undocumented flags.
         saved_undocumented = undocumented_flags.copy()
         saved_documented = documented_flags.copy()
diff --git a/lib/lp/services/features/model.py b/lib/lp/services/features/model.py
index 3ca37d5..1dd3875 100644
--- a/lib/lp/services/features/model.py
+++ b/lib/lp/services/features/model.py
@@ -36,7 +36,7 @@ class FeatureFlag(Storm):
     date_modified = DateTime()
 
     def __init__(self, scope, priority, flag, value):
-        super(FeatureFlag, self).__init__()
+        super().__init__()
         self.scope = scope
         self.priority = priority
         self.flag = flag
@@ -56,7 +56,7 @@ class FeatureFlagChangelogEntry(Storm):
     person = Reference(person_id, 'Person.id')
 
     def __init__(self, diff, comment, person):
-        super(FeatureFlagChangelogEntry, self).__init__()
+        super().__init__()
         self.diff = six.ensure_text(diff)
         self.date_changed = datetime.now(pytz.timezone('UTC'))
         self.comment = six.ensure_text(comment)
diff --git a/lib/lp/services/features/rulesource.py b/lib/lp/services/features/rulesource.py
index 00a43cf..44db1c1 100644
--- a/lib/lp/services/features/rulesource.py
+++ b/lib/lp/services/features/rulesource.py
@@ -42,7 +42,7 @@ class DuplicatePriorityError(Exception):
             self.flag, self.priority)
 
 
-class FeatureRuleSource(object):
+class FeatureRuleSource:
     """Access feature rule sources from the database or elsewhere."""
 
     def getAllRulesAsDict(self):
diff --git a/lib/lp/services/features/scopes.py b/lib/lp/services/features/scopes.py
index 3d7f3ca..7845b92 100644
--- a/lib/lp/services/features/scopes.py
+++ b/lib/lp/services/features/scopes.py
@@ -294,7 +294,7 @@ class ScopesFromRequest(MultiScopeHandler):
             TeamScope(person_from_request),
             UserSliceScope(person_from_request),
             ])
-        super(ScopesFromRequest, self).__init__(scopes)
+        super().__init__(scopes)
 
 
 class ScopesForScript(MultiScopeHandler):
@@ -303,4 +303,4 @@ class ScopesForScript(MultiScopeHandler):
     def __init__(self, script_name):
         scopes = list(default_scopes)
         scopes.append(ScriptScope(script_name))
-        super(ScopesForScript, self).__init__(scopes)
+        super().__init__(scopes)
diff --git a/lib/lp/services/features/testing.py b/lib/lp/services/features/testing.py
index 4ff828f..f715cc9 100644
--- a/lib/lp/services/features/testing.py
+++ b/lib/lp/services/features/testing.py
@@ -12,7 +12,6 @@ __all__ = [
 from fixtures import Fixture
 from lazr.restful.utils import get_current_browser_request
 import psycopg2
-import six
 
 from lp.services.features import (
     get_relevant_feature_controller,
@@ -91,8 +90,8 @@ class FeatureFixtureMixin:
                 flag=flag_name,
                 scope='default',
                 priority=999,
-                value=six.text_type(value))
-            for flag_name, value in six.iteritems(self.desired_features)
+                value=str(value))
+            for flag_name, value in self.desired_features.items()
                 if value is not None]
 
         if self.full_feature_rules is not None:
diff --git a/lib/lp/services/features/tests/test_changelog.py b/lib/lp/services/features/tests/test_changelog.py
index a624e12..7a4f348 100644
--- a/lib/lp/services/features/tests/test_changelog.py
+++ b/lib/lp/services/features/tests/test_changelog.py
@@ -28,12 +28,12 @@ class TestFeatureFlagChangelogEntry(TestCaseWithFactory):
         person = self.factory.makePerson()
         before = datetime.now(pytz.timezone('UTC'))
         feature_flag_change = FeatureFlagChangelogEntry(
-            diff, u'comment', person)
+            diff, 'comment', person)
         after = datetime.now(pytz.timezone('UTC'))
         self.assertEqual(
             diff, feature_flag_change.diff)
         self.assertEqual(
-            u'comment', feature_flag_change.comment)
+            'comment', feature_flag_change.comment)
         self.assertEqual(
             person, feature_flag_change.person)
         self.assertBetween(
@@ -46,7 +46,7 @@ class TestChangeLog(TestCaseWithFactory):
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        super(TestChangeLog, self).setUp()
+        super().setUp()
         self.person = self.factory.makePerson()
 
     def test_ChangeLog_append(self):
diff --git a/lib/lp/services/features/tests/test_flags.py b/lib/lp/services/features/tests/test_flags.py
index d8e687e..d22790d 100644
--- a/lib/lp/services/features/tests/test_flags.py
+++ b/lib/lp/services/features/tests/test_flags.py
@@ -21,13 +21,13 @@ from lp.testing import (
 
 
 notification_name = 'notification.global.text'
-notification_value = u'\N{SNOWMAN} stormy Launchpad weather ahead'
+notification_value = '\N{SNOWMAN} stormy Launchpad weather ahead'
 
 
 testdata = [
     (notification_name, 'beta_user', 100, notification_value),
-    ('ui.icing', 'default', 100, u'3.0'),
-    ('ui.icing', 'beta_user', 300, u'4.0'),
+    ('ui.icing', 'default', 100, '3.0'),
+    ('ui.icing', 'beta_user', 300, '4.0'),
     ]
 
 
@@ -36,7 +36,7 @@ class TestFeatureFlags(TestCase):
     layer = layers.DatabaseFunctionalLayer
 
     def setUp(self):
-        super(TestFeatureFlags, self).setUp()
+        super().setUp()
         if os.environ.get("STORM_TRACE", None):
             from storm.tracer import debug
             debug(True)
@@ -57,7 +57,7 @@ class TestFeatureFlags(TestCase):
     def test_getFlag(self):
         self.populateStore()
         control, call_log = self.makeControllerInScopes(['default'])
-        self.assertEqual(u'3.0',
+        self.assertEqual('3.0',
             control.getFlag('ui.icing'))
         self.assertEqual(['beta_user', 'default'], call_log)
 
@@ -65,12 +65,12 @@ class TestFeatureFlags(TestCase):
         # for use in page templates, the flags can be treated as a dict
         self.populateStore()
         control, call_log = self.makeControllerInScopes(['default'])
-        self.assertEqual(u'3.0',
+        self.assertEqual('3.0',
             control['ui.icing'])
         self.assertEqual(['beta_user', 'default'], call_log)
         # after looking this up the value is known and the scopes are
         # positively and negatively cached
-        self.assertEqual({'ui.icing': u'3.0'}, control.usedFlags())
+        self.assertEqual({'ui.icing': '3.0'}, control.usedFlags())
         self.assertEqual(dict(beta_user=False, default=True),
             control.usedScopes())
 
@@ -129,12 +129,12 @@ class TestFeatureFlags(TestCase):
         self.populateStore()
         default_control, call_log = self.makeControllerInScopes(['default'])
         self.assertEqual(
-            u'3.0',
+            '3.0',
             default_control.getFlag('ui.icing'))
         beta_control, call_log = self.makeControllerInScopes(
             ['beta_user', 'default'])
         self.assertEqual(
-            u'4.0',
+            '4.0',
             beta_control.getFlag('ui.icing'))
 
     def test_undefinedFlag(self):
@@ -157,7 +157,7 @@ class TestFeatureFlags(TestCase):
         try:
             # then application code can simply ask without needing a context
             # object
-            self.assertEqual(u'4.0', getFeatureFlag('ui.icing'))
+            self.assertEqual('4.0', getFeatureFlag('ui.icing'))
         finally:
             install_feature_controller(None)
 
@@ -172,7 +172,7 @@ class TestFeatureFlags(TestCase):
         # when it will make a difference to the result.
         self.populateStore()
         f, call_log = self.makeControllerInScopes(['beta_user'])
-        self.assertEqual(u'4.0', f.getFlag('ui.icing'))
+        self.assertEqual('4.0', f.getFlag('ui.icing'))
         # to calculate this it should only have had to check we're in the
         # beta_users scope; nothing else makes a difference
         self.assertEqual(dict(beta_user=True), f._known_scopes._known)
@@ -201,9 +201,9 @@ class TestFeatureFlags(TestCase):
 
 test_rules_list = [
     (notification_name, 'beta_user', 100, notification_value),
-    ('ui.icing', 'normal_user', 500, u'5.0'),
-    ('ui.icing', 'beta_user', 300, u'4.0'),
-    ('ui.icing', 'default', 100, u'3.0'),
+    ('ui.icing', 'normal_user', 500, '5.0'),
+    ('ui.icing', 'beta_user', 300, '4.0'),
+    ('ui.icing', 'default', 100, '3.0'),
     ]
 
 
diff --git a/lib/lp/services/features/tests/test_helpers.py b/lib/lp/services/features/tests/test_helpers.py
index a07b444..89b0f20 100644
--- a/lib/lp/services/features/tests/test_helpers.py
+++ b/lib/lp/services/features/tests/test_helpers.py
@@ -45,13 +45,13 @@ class FeatureFixturesTestsMixin:
         self.useFixture(self.fixture_cls({'two': '2'}))
 
         self.assertEqual(getFeatureFlag('one'), None)
-        self.assertEqual(getFeatureFlag('two'), u'2')
+        self.assertEqual(getFeatureFlag('two'), '2')
 
     def test_fixture_overrides_previously_set_flags(self):
         self.useFixture(self.fixture_cls({'one': '1'}))
         self.useFixture(self.fixture_cls({'one': '5'}))
 
-        self.assertEqual(getFeatureFlag('one'), u'5')
+        self.assertEqual(getFeatureFlag('one'), '5')
 
     def test_fixture_does_not_set_value_for_flags_that_are_None(self):
         self.useFixture(self.fixture_cls({'nothing': None}))
@@ -62,10 +62,10 @@ class FeatureFixturesTestsMixin:
         value_outside_manager = getFeatureFlag(flag)
         value_in_manager = None
 
-        with self.fixture_cls({flag: u'on'}):
+        with self.fixture_cls({flag: 'on'}):
             value_in_manager = getFeatureFlag(flag)
 
-        self.assertEqual(value_in_manager, u'on')
+        self.assertEqual(value_in_manager, 'on')
         self.assertEqual(value_outside_manager, getFeatureFlag(flag))
         self.assertNotEqual(value_outside_manager, value_in_manager)
 
diff --git a/lib/lp/services/features/tests/test_scopes.py b/lib/lp/services/features/tests/test_scopes.py
index 4934b81..843f177 100644
--- a/lib/lp/services/features/tests/test_scopes.py
+++ b/lib/lp/services/features/tests/test_scopes.py
@@ -77,7 +77,7 @@ class TestScopes(TestCaseWithFactory):
         self.assertFalse(scopes.lookup("script:other"))
 
 
-class FakePerson(object):
+class FakePerson:
 
     id = 7
 
@@ -113,12 +113,12 @@ class TestUserSliceScopeIntegration(TestCaseWithFactory):
                 flag='test_feature',
                 scope='userslice:0,1',
                 priority=999,
-                value=u'on'),
+                value='on'),
             dict(
                 flag='test_not',
                 scope='userslice:1,1',
                 priority=999,
-                value=u'not_value'),
+                value='not_value'),
             ]):
             with person_logged_in(person):
                 self.assertEqual(getFeatureFlag('test_feature'), 'on')
diff --git a/lib/lp/services/features/tests/test_webapp.py b/lib/lp/services/features/tests/test_webapp.py
index 5bf6e54..13b6e27 100644
--- a/lib/lp/services/features/tests/test_webapp.py
+++ b/lib/lp/services/features/tests/test_webapp.py
@@ -122,4 +122,4 @@ class TestFeaturesIntoOops(TestCaseWithFactory):
                 self.assertTrue('features.usedFlags' in oops)
                 self.assertEqual(
                     oops['features.usedFlags'],
-                    u'%r' % {'feature_name': u'value'})
+                    '%r' % {'feature_name': 'value'})
diff --git a/lib/lp/services/features/tests/test_xmlrpc.py b/lib/lp/services/features/tests/test_xmlrpc.py
index 10cf61c..ba1960c 100644
--- a/lib/lp/services/features/tests/test_xmlrpc.py
+++ b/lib/lp/services/features/tests/test_xmlrpc.py
@@ -39,45 +39,45 @@ class TestGetFeatureFlag(TestCaseWithFactory):
             features.install_feature_controller, old_features)
 
     def test_getFeatureFlag_returns_None_by_default(self):
-        self.assertIs(None, self.endpoint.getFeatureFlag(u'unknown'))
+        self.assertIs(None, self.endpoint.getFeatureFlag('unknown'))
 
     def test_getFeatureFlag_returns_true_for_set_flag(self):
-        flag_name = u'flag'
+        flag_name = 'flag'
         with feature_flags():
-            set_feature_flag(flag_name, u'1')
-            self.assertEqual(u'1', self.endpoint.getFeatureFlag(flag_name))
+            set_feature_flag(flag_name, '1')
+            self.assertEqual('1', self.endpoint.getFeatureFlag(flag_name))
 
     def test_getFeatureFlag_ignores_relevant_feature_controller(self):
         # getFeatureFlag should only consider the scopes it is asked to
         # consider, not any that happen to be active due to the XML-RPC
         # request itself.
-        flag_name = u'flag'
-        scope_name = u'scope'
+        flag_name = 'flag'
+        scope_name = 'scope'
         self.installFeatureController(
             FeatureController(
                 MultiScopeHandler(
                     [DefaultScope(), FixedScope(scope_name)]).lookup,
                 StormFeatureRuleSource()))
-        set_feature_flag(flag_name, u'1', scope_name)
+        set_feature_flag(flag_name, '1', scope_name)
         self.assertEqual(None, self.endpoint.getFeatureFlag(flag_name))
 
     def test_getFeatureFlag_considers_supplied_scope(self):
-        flag_name = u'flag'
-        scope_name = u'scope'
+        flag_name = 'flag'
+        scope_name = 'scope'
         with feature_flags():
-            set_feature_flag(flag_name, u'value', scope_name)
+            set_feature_flag(flag_name, 'value', scope_name)
             self.assertEqual(
-                u'value',
+                'value',
                 self.endpoint.getFeatureFlag(flag_name, [scope_name]))
 
     def test_getFeatureFlag_turns_user_into_team_scope(self):
-        flag_name = u'flag'
+        flag_name = 'flag'
         person = self.factory.makePerson()
         team = self.factory.makeTeam(members=[person])
         with feature_flags():
-            set_feature_flag(flag_name, u'value', u'team:' + team.name)
+            set_feature_flag(flag_name, 'value', 'team:' + team.name)
             self.assertEqual(
-                u'value',
+                'value',
                 self.endpoint.getFeatureFlag(
                     flag_name, ['user:' + person.name]))
 
@@ -85,13 +85,13 @@ class TestGetFeatureFlag(TestCaseWithFactory):
         sp = xmlrpc.client.ServerProxy(
             config.launchpad.feature_flags_endpoint,
             transport=XMLRPCTestTransport(), allow_none=True)
-        self.assertEqual(None, sp.getFeatureFlag(u'flag'))
+        self.assertEqual(None, sp.getFeatureFlag('flag'))
 
     def test_xmlrpc_interface_set(self):
         sp = xmlrpc.client.ServerProxy(
             config.launchpad.feature_flags_endpoint,
             transport=XMLRPCTestTransport(), allow_none=True)
-        flag_name = u'flag'
+        flag_name = 'flag'
         with feature_flags():
-            set_feature_flag(flag_name, u'1')
-            self.assertEqual(u'1', sp.getFeatureFlag(flag_name))
+            set_feature_flag(flag_name, '1')
+            self.assertEqual('1', sp.getFeatureFlag(flag_name))
diff --git a/lib/lp/services/feeds/feed.py b/lib/lp/services/feeds/feed.py
index 35d5939..9798bc0 100644
--- a/lib/lp/services/feeds/feed.py
+++ b/lib/lp/services/feeds/feed.py
@@ -19,7 +19,6 @@ import operator
 import os
 import time
 
-import six
 from six.moves.urllib.parse import urljoin
 from zope.browserpage import ViewPageTemplateFile
 from zope.component import getUtility
@@ -68,7 +67,7 @@ class FeedBase(LaunchpadView):
                       'html': 'templates/feed-html.pt'}
 
     def __init__(self, context, request):
-        super(FeedBase, self).__init__(context, request)
+        super().__init__(context, request)
         self.format = self.feed_format
         self.root_url = canonical_url(getUtility(ILaunchpadRoot),
                                       rootsite=self.rootsite)
@@ -294,7 +293,7 @@ class FeedTypedData:
             for a_tag in a_tags:
                 if a_tag['href'].startswith('/'):
                     a_tag['href'] = urljoin(self.root_url, a_tag['href'])
-            altered_content = six.text_type(soup)
+            altered_content = str(soup)
         else:
             altered_content = self._content
 
@@ -302,7 +301,7 @@ class FeedTypedData:
             altered_content = html_escape(altered_content)
         elif self.content_type == 'xhtml':
             soup = BeautifulSoup(altered_content)
-            altered_content = six.text_type(soup)
+            altered_content = str(soup)
         return altered_content
 
 
diff --git a/lib/lp/services/feeds/interfaces/feed.py b/lib/lp/services/feeds/interfaces/feed.py
index 6cdc31c..7a92ff8 100644
--- a/lib/lp/services/feeds/interfaces/feed.py
+++ b/lib/lp/services/feeds/interfaces/feed.py
@@ -54,34 +54,34 @@ class IFeed(Interface):
     # duration in seconds the feed should be cached before being considered
     # stale.
     max_age = Int(
-        title=u"Maximum age",
-        description=u"Maximum age in seconds for a feed to be cached.")
+        title="Maximum age",
+        description="Maximum age in seconds for a feed to be cached.")
 
     # A feed could contain an arbitrary large number of entries, so a quantity
     # may be specified to limit the number of entries returned.
     quantity = Int(
-        title=u"Quantity",
-        description=u"Number of items to be returned in a feed.")
+        title="Quantity",
+        description="Number of items to be returned in a feed.")
 
     # The title of the feed is prominently displayed in readers and should
     # succinctly identify the feed, e.g. "Latest bugs in Kubuntu".
     title = TextLine(
-        title=u"Title of the feed.")
+        title="Title of the feed.")
 
     # The URL for a feed identifies it uniquely and it should never change.
     # The latest bugs in Kubuntu is:
     # http://feeds.launchpad.net/kubuntu/latest-bugs.atom
     link_self = TextLine(
-        title=u"URL for the feed.",
-        description=u"The link_self URL for the feed should be "
-                     "unique and permanent.")
+        title="URL for the feed.",
+        description="The link_self URL for the feed should be "
+                    "unique and permanent.")
 
     # The site URL refers to the top-level page for the site serving the
     # feed.  For Launchpad the site_url should be the mainsite URL,
     # i.e. http://launchpad.net.
     site_url = TextLine(
-        title=u"Site URL",
-        description=u"The URL for the main site of Launchpad.")
+        title="Site URL",
+        description="The URL for the main site of Launchpad.")
 
     # Feeds are intended to be machine-readable -- XML to be processed by a
     # feed reader and then, possibly, displayed.  The alternate URL is the
@@ -89,12 +89,12 @@ class IFeed(Interface):
     # announcements the alternate location is
     # http://launchpad.net/ubuntu/+announcements.
     link_alternate = TextLine(
-        title=u"Alternate URL for the feed.",
-        description=u"The URL to a resource that is the human-readable "
-                     "equivalent of the feed.  So for: "
-                     "http://feeds.launchpad.net/ubuntu/announcements.atom "
-                     "the link_alternate would be: "
-                     "http://launchpad.net/ubuntu/+announcements";)
+        title="Alternate URL for the feed.",
+        description="The URL to a resource that is the human-readable "
+                    "equivalent of the feed.  So for: "
+                    "http://feeds.launchpad.net/ubuntu/announcements.atom "
+                    "the link_alternate would be: "
+                    "http://launchpad.net/ubuntu/+announcements";)
 
     # The feed ID is a permanent ID for the feed and it must be unique across
     # all time and domains.  That sounds harder than it really is.  To make
@@ -103,29 +103,29 @@ class IFeed(Interface):
     # So an ID for a Jokosher announcment feed would look like:
     # tag:launchpad.net,2006-5-26:/jokosher/+announcements.
     feed_id = TextLine(
-        title=u"ID for the feed.",
-        description=u"The <id> for a feed is permanent and globally unique. "
-                     "It is constructed following RFC 4151.")
+        title="ID for the feed.",
+        description="The <id> for a feed is permanent and globally unique. "
+                    "It is constructed following RFC 4151.")
 
     # The feed format is either 'atom' or 'html'.
     feed_format = TextLine(
-        title=u"Feed format",
-        description=u"Requested feed format.  "
-                     "Raises UnsupportedFeed if not supported.")
+        title="Feed format",
+        description="Requested feed format.  "
+                    "Raises UnsupportedFeed if not supported.")
 
     # The logo URL points to an image identifying the feed and will likely
     # vary from one Launchpad application to another.  For example the logo
     # for bugs is:
     # http://launchpad.net/@@/bug.
     logo = TextLine(
-        title=u"Logo URL",
-        description=u"The URL for the feed logo.")
+        title="Logo URL",
+        description="The URL for the feed logo.")
 
     # The icon URL points to an image identifying the feed.  For Launchpad
     # feeds the icon is http://launchpad.net/@@/launchpad.
     icon = TextLine(
-        title=u"Icon URL",
-        description=u"The URL for the feed icon.")
+        title="Icon URL",
+        description="The URL for the feed icon.")
 
     # The date updated represents the last date any information in the feed
     # changed.  For instance for feed for Launchpad announcements the date
@@ -133,8 +133,8 @@ class IFeed(Interface):
     # the feed changed.  Feed readers use the date updated one criteria as to
     # whether to fetch the feed information anew.
     date_updated = Datetime(
-        title=u"Date update",
-        description=u"Date of last update for the feed.")
+        title="Date update",
+        description="Date of last update for the feed.")
 
     def getItems():
         """Get the individual items for the feed.
@@ -164,24 +164,24 @@ class IFeedEntry(Interface):
     # succinctly identify the entry, e.g. "Microsoft has a majority market
     # share."
     title = TextLine(
-        title=u"Title",
-        description=u"The title of the entry")
+        title="Title",
+        description="The title of the entry")
 
     # The link alternate is an URL specifying the location of the
     # human-readable equivalent for the entry.  For a Ubuntu announcements, an
     # example alternate location is
     # http://launchpad.net/ubuntu/+announcement/4.
     link_alternate = TextLine(
-        title=u"Alternate URL for the entry.",
-        description=u"The URL to a resource that is the human-readable "
-                     "equivalent of the entry, e.g. "
-                     "http://launchpad.net/ubuntu/+announcement/1";)
+        title="Alternate URL for the entry.",
+        description="The URL to a resource that is the human-readable "
+                    "equivalent of the entry, e.g. "
+                    "http://launchpad.net/ubuntu/+announcement/1";)
 
     # The actual content for the entry that is to be displayed in the feed
     # reader.  It may be text or marked up HTML.  It should be an
     # IFeedTypedData.
     content = Attribute(
-        u"Content for the entry.  Descriptive content for the entry.  "
+        "Content for the entry.  Descriptive content for the entry.  "
         "For an announcement, for example, the content "
         "is the text of the announcement.  It may be "
         "plain text or formatted html, as is done for "
@@ -189,22 +189,22 @@ class IFeedEntry(Interface):
 
     # Date the entry was created in the system, without respect to the feed.
     date_created = Datetime(
-        title=u"Date Created",
-        description=u"Date the entry was originally created in Launchpad.")
+        title="Date Created",
+        description="Date the entry was originally created in Launchpad.")
 
     # Date any aspect of the entry was changed.
     date_updated = Datetime(
-        title=u"Date Updated",
-        description=u"Date the entry was last updated.")
+        title="Date Updated",
+        description="Date the entry was last updated.")
 
     # Date the entry became published.
     date_published = Datetime(
-        title=u"Date Published",
-        description=u"Date the entry was published.  "
-                     "For some content this date will be the same "
-                     "as the creation date.  For others, like an "
-                     "announcement, it will be the date the announcement "
-                     "became public.")
+        title="Date Published",
+        description="Date the entry was published.  "
+                    "For some content this date will be the same "
+                    "as the creation date.  For others, like an "
+                    "announcement, it will be the date the announcement "
+                    "became public.")
 
     # The primary authors for the entry.
     authors = Attribute(
@@ -221,50 +221,50 @@ class IFeedEntry(Interface):
     # The logo representing the entry.
     # Not used and ignored.
     logo = TextLine(
-        title=u"Logo URL",
-        description=u"The URL for the entry logo."
-                     "Currently not used.")
+        title="Logo URL",
+        description="The URL for the entry logo."
+                    "Currently not used.")
 
     # The icon representing the entry.
     # Not used and ignored.
     icon = TextLine(
-        title=u"Icon URL",
-        description=u"The URL for the entry icon."
-                     "Currently not used.")
+        title="Icon URL",
+        description="The URL for the entry icon."
+                    "Currently not used.")
 
     # The description of the program that generated the feed.  May include
     # versioning information.  Useful for debugging purposes only.
     # Not used and ignored.
     generator = TextLine(
-        title=u"The generator of the feed.",
-        description=u"A description of the program generating the feed.  "
-                     "Analogous to a browser USER-AGENT string.  "
-                     "Currently not used.")
+        title="The generator of the feed.",
+        description="A description of the program generating the feed.  "
+                    "Analogous to a browser USER-AGENT string.  "
+                    "Currently not used.")
 
 
 class IFeedTypedData(Interface):
     """Interface for typed data in a feed."""
 
     content_types = List(
-        title=u"Content types",
-        description=u"List of supported content types",
+        title="Content types",
+        description="List of supported content types",
         required=True)
 
     content = Text(
-        title=u"Content",
-        description=u"Data contents",
+        title="Content",
+        description="Data contents",
         required=True)
 
     content_type = Text(
-        title=u"Content type",
-        description=u"The actual content type for this object.  Must be"
-                     "one of those listed in content_types.",
+        title="Content type",
+        description="The actual content type for this object.  Must be"
+                    "one of those listed in content_types.",
         required=False)
 
     root_url = Text(
-        title=u"Root URL",
-        description=u"URL for the root of the site that produced the content, "
-                     "i.e. 'http://code.launchpad.net'",
+        title="Root URL",
+        description="URL for the root of the site that produced the content, "
+                    "i.e. 'http://code.launchpad.net'",
         required=False)
 
 
@@ -272,16 +272,16 @@ class IFeedPerson(Interface):
     """Interface for a person in a feed."""
 
     name = TextLine(
-        title=u"Name",
-        description=u"The person's name.",
+        title="Name",
+        description="The person's name.",
         required=True)
 
     email = TextLine(
-        title=u"Email",
-        description=u"The person's email address.",
+        title="Email",
+        description="The person's email address.",
         required=False)
 
     uri = URI(
-        title=u"URI",
-        description=u"The URI for the person.",
+        title="URI",
+        description="The URI for the person.",
         required=True)
diff --git a/lib/lp/services/feeds/tests/helper.py b/lib/lp/services/feeds/tests/helper.py
index 4170399..7f7f8e8 100644
--- a/lib/lp/services/feeds/tests/helper.py
+++ b/lib/lp/services/feeds/tests/helper.py
@@ -30,7 +30,7 @@ class IThing(Interface):
 
 
 @implementer(IThing)
-class Thing(object):
+class Thing:
 
     def __init__(self, value):
         self.value = value
diff --git a/lib/lp/services/fields/__init__.py b/lib/lp/services/fields/__init__.py
index 7d5ff4e..684ec36 100644
--- a/lib/lp/services/fields/__init__.py
+++ b/lib/lp/services/fields/__init__.py
@@ -63,7 +63,6 @@ from lazr.uri import (
     InvalidURIError,
     URI,
     )
-import six
 from zope.component import getUtility
 from zope.interface import (
     implementer,
@@ -225,7 +224,7 @@ class StrippedTextLine(TextLine):
         """Strip the value and pass up."""
         if value is not None:
             value = value.strip()
-        super(StrippedTextLine, self).set(object, value)
+        super().set(object, value)
 
 
 @implementer(INoneableTextLine)
@@ -245,7 +244,7 @@ class StrippableText(Text):
     """A text that can be configured to strip when setting."""
 
     def __init__(self, strip_text=False, trailing_only=False, **kwargs):
-        super(StrippableText, self).__init__(**kwargs)
+        super().__init__(**kwargs)
         self.strip_text = strip_text
         self.trailing_only = trailing_only
 
@@ -261,12 +260,12 @@ class StrippableText(Text):
     def set(self, object, value):
         """Strip the value and pass up."""
         value = self.normalize(value)
-        super(StrippableText, self).set(object, value)
+        super().set(object, value)
 
     def validate(self, value):
         """See `IField`."""
         value = self.normalize(value)
-        return super(StrippableText, self).validate(value)
+        return super().validate(value)
 
 
 # Summary
@@ -310,7 +309,7 @@ class FormattableDate(Date):
         error_msg = ("Date could not be formatted. Provide a date formatted "
             "like YYYY-MM-DD format. The year must be after 1900.")
 
-        super(FormattableDate, self)._validate(value)
+        super()._validate(value)
         # The only thing of interest here is whether or the input can be
         # formatted properly, not whether it makes sense otherwise.
         # As a minimal sanity check, just raise an error if it fails.
@@ -338,7 +337,7 @@ class BugField(Reference):
 
     def __init__(self, *args, **kwargs):
         """The schema will always be `IBug`."""
-        super(BugField, self).__init__(Interface, *args, **kwargs)
+        super().__init__(Interface, *args, **kwargs)
 
     def _get_schema(self):
         """Get the schema here to avoid circular imports."""
@@ -401,9 +400,9 @@ class SearchTag(Tag):
         if value in ('*', '-*'):
             return True
         elif value.startswith('-'):
-            return super(SearchTag, self).constraint(value[1:])
+            return super().constraint(value[1:])
         else:
-            return super(SearchTag, self).constraint(value)
+            return super().constraint(value)
 
 
 class UniqueField(TextLine):
@@ -447,7 +446,7 @@ class UniqueField(TextLine):
         object of this same context. The 'input' should be valid as per
         TextLine.
         """
-        super(UniqueField, self)._validate(input)
+        super()._validate(input)
         assert self._content_iface is not None
 
         if self.unchanged(input):
@@ -493,7 +492,7 @@ class BlacklistableContentNameField(ContentNameField):
 
     def _validate(self, input):
         """Check that the given name is valid, unique and not blacklisted."""
-        super(BlacklistableContentNameField, self)._validate(input)
+        super()._validate(input)
 
         # Although this check is performed in UniqueField._validate(), we need
         # to do it here again to avoid checking whether or not the name is
@@ -594,7 +593,7 @@ class URIField(TextLine):
     def __init__(self, allowed_schemes=(), allow_userinfo=True,
                  allow_port=True, allow_query=True, allow_fragment=True,
                  trailing_slash=None, **kwargs):
-        super(URIField, self).__init__(**kwargs)
+        super().__init__(**kwargs)
         self.allowed_schemes = set(allowed_schemes)
         self.allow_userinfo = allow_userinfo
         self.allow_port = allow_port
@@ -605,7 +604,7 @@ class URIField(TextLine):
     def set(self, object, value):
         """Canonicalize a URL and set it as a field value."""
         value = self.normalize(value)
-        super(URIField, self).set(object, value)
+        super().set(object, value)
 
     def normalize(self, input):
         """See `IURIField`."""
@@ -624,7 +623,7 @@ class URIField(TextLine):
                 uri = uri.ensureSlash()
             else:
                 uri = uri.ensureNoSlash()
-        input = six.text_type(uri)
+        input = str(uri)
         return input
 
     def _validate(self, value):
@@ -654,7 +653,7 @@ class URIField(TextLine):
             raise LaunchpadValidationError(
                 'URIs with fragment identifiers are not allowed.')
 
-        super(URIField, self)._validate(value)
+        super()._validate(value)
 
 
 class FieldNotBoundError(Exception):
@@ -709,7 +708,7 @@ class BaseImageUpload(Bytes):
                 This image exceeds the maximum allowed size in bytes.""")))
         try:
             pil_image = PIL.Image.open(io.BytesIO(image))
-        except (IOError, ValueError):
+        except (OSError, ValueError):
             raise LaunchpadValidationError(_(dedent("""
                 The file uploaded was not recognized as an image; please
                 check it and retry.""")))
@@ -737,7 +736,7 @@ class BaseImageUpload(Bytes):
             content = value.read()
         else:
             content = value
-        super(BaseImageUpload, self)._validate(content)
+        super()._validate(content)
         self._valid_image(content)
 
     def set(self, object, value):
diff --git a/lib/lp/services/fields/tests/test_fields.py b/lib/lp/services/fields/tests/test_fields.py
index 99fd864..6dd3aba 100644
--- a/lib/lp/services/fields/tests/test_fields.py
+++ b/lib/lp/services/fields/tests/test_fields.py
@@ -99,13 +99,13 @@ class TestStrippableText(TestCase):
         # The minimum length constraint tests the stripped string.
         field = StrippableText(
             __name__='test', strip_text=True, min_length=1)
-        self.assertRaises(TooShort, field.validate, u'  ')
+        self.assertRaises(TooShort, field.validate, '  ')
 
     def test_validate_max_contraints(self):
         # The minimum length constraint tests the stripped string.
         field = StrippableText(
             __name__='test', strip_text=True, max_length=2)
-        self.assertEqual(None, field.validate(u'  a  '))
+        self.assertEqual(None, field.validate('  a  '))
 
 
 class TestWorkItemsTextValidation(TestCaseWithFactory):
@@ -113,7 +113,7 @@ class TestWorkItemsTextValidation(TestCaseWithFactory):
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        super(TestWorkItemsTextValidation, self).setUp()
+        super().setUp()
         self.field = WorkItemsText(__name__='test')
 
     def test_parseandvalidate(self):
@@ -198,7 +198,7 @@ class TestWorkItemsTextValidation(TestCaseWithFactory):
 class TestWorkItemsText(TestCase):
 
     def setUp(self):
-        super(TestWorkItemsText, self).setUp()
+        super().setUp()
         self.field = WorkItemsText(__name__='test')
 
     def test_validate_raises_LaunchpadValidationError(self):
@@ -431,10 +431,10 @@ class TestBlacklistableContentNameField(TestCaseWithFactory):
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        super(TestBlacklistableContentNameField, self).setUp()
+        super().setUp()
         name_blacklist_set = getUtility(INameBlacklistSet)
         self.team = self.factory.makeTeam()
-        admin_exp = name_blacklist_set.create(u'fnord', admin=self.team)
+        admin_exp = name_blacklist_set.create('fnord', admin=self.team)
         IStore(admin_exp).flush()
 
     def makeTestField(self):
@@ -455,7 +455,7 @@ class TestBlacklistableContentNameField(TestCaseWithFactory):
         # Anonymous users, processes, cannot create a name that matches
         # a blacklisted name.
         field = self.makeTestField()
-        date_value = u'fnord'
+        date_value = 'fnord'
         self.assertRaises(
             LaunchpadValidationError, field.validate, date_value)
 
@@ -463,7 +463,7 @@ class TestBlacklistableContentNameField(TestCaseWithFactory):
         # Users who do not adminster a blacklisted name cannot create
         # a matching name.
         field = self.makeTestField()
-        date_value = u'fnord'
+        date_value = 'fnord'
         login_person(self.factory.makePerson())
         self.assertRaises(
             LaunchpadValidationError, field.validate, date_value)
@@ -472,7 +472,7 @@ class TestBlacklistableContentNameField(TestCaseWithFactory):
         # Users in the team that adminsters a blacklisted name may create
         # matching names.
         field = self.makeTestField()
-        date_value = u'fnord'
+        date_value = 'fnord'
         login_person(self.team.teamowner)
         self.assertEqual(None, field.validate(date_value))
 
diff --git a/lib/lp/services/fields/tests/test_tag_fields.py b/lib/lp/services/fields/tests/test_tag_fields.py
index d3bdf8e..2ca5a6d 100644
--- a/lib/lp/services/fields/tests/test_tag_fields.py
+++ b/lib/lp/services/fields/tests/test_tag_fields.py
@@ -22,19 +22,19 @@ class TestTag(TestCase):
         # Tag allows names all in lowercase, starting with any letter
         # of the English alphabet, followed by 1 or more letters,
         # numbers or minuses.
-        self.assertIs(None, self.field.validate(u'fred'))
-        self.assertIs(None, self.field.validate(u'one-two'))
-        self.assertIs(None, self.field.validate(u'one-2'))
-        self.assertIs(None, self.field.validate(u'one-2-3---5-'))
+        self.assertIs(None, self.field.validate('fred'))
+        self.assertIs(None, self.field.validate('one-two'))
+        self.assertIs(None, self.field.validate('one-2'))
+        self.assertIs(None, self.field.validate('one-2-3---5-'))
 
     def test_too_short(self):
         # Tag rejects tags that are less than 2 characters long.
         self.assertRaises(
             ConstraintNotSatisfied,
-            self.field.validate, u'')
+            self.field.validate, '')
         self.assertRaises(
             ConstraintNotSatisfied,
-            self.field.validate, u'x')
+            self.field.validate, 'x')
 
     def test_invalid_characters(self):
         # Tag rejects characters outside of the range [a-z0-9-].
@@ -42,20 +42,20 @@ class TestTag(TestCase):
         # test_negated_search_form.
         self.assertRaises(
             ConstraintNotSatisfied,
-            self.field.validate, u'char^not^allowed')
+            self.field.validate, 'char^not^allowed')
         self.assertRaises(
             ConstraintNotSatisfied,
-            self.field.validate, u'no whitespace')
+            self.field.validate, 'no whitespace')
         self.assertRaises(
             ConstraintNotSatisfied,
-            self.field.validate, u'really\no-whitespace')
+            self.field.validate, 'really\no-whitespace')
 
     def test_negated_search_form(self):
         # Tag rejects tags beginning with minuses. This form is
         # reserved to mean "not <tag>".
         self.assertRaises(
             ConstraintNotSatisfied,
-            self.field.validate, u'-fred')
+            self.field.validate, '-fred')
 
     def test_wildcard(self):
         # Tag rejects a solitary asterisk, or an asterisk preceded by
@@ -65,10 +65,10 @@ class TestTag(TestCase):
         # any tag".
         self.assertRaises(
             ConstraintNotSatisfied,
-            self.field.validate, u'*')
+            self.field.validate, '*')
         self.assertRaises(
             ConstraintNotSatisfied,
-            self.field.validate, u'-*')
+            self.field.validate, '-*')
 
 
 class TestSearchTag(TestTag):
@@ -78,17 +78,17 @@ class TestSearchTag(TestTag):
     def test_negated_search_form(self):
         # SearchTag allows tags beginning with minuses. This form is
         # reserved to mean "not <tag>".
-        self.assertIs(None, self.field.validate(u'-fred'))
+        self.assertIs(None, self.field.validate('-fred'))
 
     def test_wildcard(self):
         # SearchTag allows a solitary asterisk, or an asterisk
         # preceded by a minus. This means "any tag" or "not any
         # tag".
-        self.assertIs(None, self.field.validate(u'*'))
-        self.assertIs(None, self.field.validate(u'-*'))
+        self.assertIs(None, self.field.validate('*'))
+        self.assertIs(None, self.field.validate('-*'))
 
     def test_wildcard_elsewhere(self):
         # Asterisks are not allowed to appear anywhere else in a tag.
         self.assertRaises(
             ConstraintNotSatisfied,
-            self.field.validate, u'sn*t-allowed')
+            self.field.validate, 'sn*t-allowed')
diff --git a/lib/lp/services/geoip/model.py b/lib/lp/services/geoip/model.py
index 53260aa..ab3f3c0 100644
--- a/lib/lp/services/geoip/model.py
+++ b/lib/lp/services/geoip/model.py
@@ -77,7 +77,7 @@ class GeoIP:
 
 
 @implementer(IRequestLocalLanguages)
-class RequestLocalLanguages(object):
+class RequestLocalLanguages:
 
     def __init__(self, request):
         self.request = request
@@ -100,7 +100,7 @@ class RequestLocalLanguages(object):
 
 
 @implementer(IRequestPreferredLanguages)
-class RequestPreferredLanguages(object):
+class RequestPreferredLanguages:
 
     def __init__(self, request):
         self.request = request
diff --git a/lib/lp/services/geoip/tests/test_request_country.py b/lib/lp/services/geoip/tests/test_request_country.py
index 409f861..a72367b 100644
--- a/lib/lp/services/geoip/tests/test_request_country.py
+++ b/lib/lp/services/geoip/tests/test_request_country.py
@@ -29,21 +29,21 @@ class RequestCountryTestCase(unittest.TestCase):
 
     def testRemoteAddr(self):
         country = request_country({'REMOTE_ADDR': self.lp})
-        self.assertEqual(country.name, u'United Kingdom')
+        self.assertEqual(country.name, 'United Kingdom')
 
     def testXForwardedFor(self):
         country = request_country({
                 'HTTP_X_FORWARDED_FOR': self.lp,
                 'REMOTE_ADDR': '1.2.3.4',
                 })
-        self.assertEqual(country.name, u'United Kingdom')
+        self.assertEqual(country.name, 'United Kingdom')
 
     def testNestedProxies(self):
         country = request_country({
                 'HTTP_X_FORWARDED_FOR':
                     'localhost, 127.0.0.1, %s, 1,1,1,1' % self.lp,
                 })
-        self.assertEqual(country.name, u'United Kingdom')
+        self.assertEqual(country.name, 'United Kingdom')
 
     def testMissingHeaders(self):
         country = request_country({})
diff --git a/lib/lp/services/gpg/handler.py b/lib/lp/services/gpg/handler.py
index 1f04851..a56cc51 100644
--- a/lib/lp/services/gpg/handler.py
+++ b/lib/lp/services/gpg/handler.py
@@ -15,7 +15,6 @@ import http.client
 from io import BytesIO
 import os
 import shutil
-import socket
 import subprocess
 import sys
 import tempfile
@@ -449,7 +448,7 @@ class GPGHandler:
         # XXX michaeln 2010-05-07 bug=576405
         # Currently gpgme.Context().keylist fails if passed a unicode
         # string even though that's what is returned for fingerprints.
-        if isinstance(filter, six.text_type):
+        if isinstance(filter, str):
             filter = filter.encode('utf-8')
 
         with gpgme_timeline(
@@ -499,7 +498,7 @@ class GPGHandler:
 
         try:
             conn.request("POST", "/pks/add", params, headers)
-        except socket.error as err:
+        except OSError as err:
             raise GPGUploadFailure(
                 'Could not reach keyserver at http://%s %s' % (
                     keyserver_http_url, str(err)))
@@ -581,7 +580,7 @@ class GPGHandler:
 
 
 @implementer(IPymeSignature)
-class PymeSignature(object):
+class PymeSignature:
     """See IPymeSignature."""
 
     def __init__(self, fingerprint=None, plain_data=None, timestamp=None):
diff --git a/lib/lp/services/gpg/interfaces.py b/lib/lp/services/gpg/interfaces.py
index 90f780e..ee105ca 100644
--- a/lib/lp/services/gpg/interfaces.py
+++ b/lib/lp/services/gpg/interfaces.py
@@ -140,7 +140,7 @@ class GPGKeyNotFoundError(Exception):
         if message is None:
             message = (
             "No GPG key found with the given content: %s" % (fingerprint, ))
-        super(GPGKeyNotFoundError, self).__init__(message)
+        super().__init__(message)
 
 
 @error_status(http.client.INTERNAL_SERVER_ERROR)
@@ -154,8 +154,7 @@ class GPGKeyTemporarilyNotFoundError(GPGKeyNotFoundError):
         message = (
             "GPG key %s not found due to a server or network failure."
             % fingerprint)
-        super(GPGKeyTemporarilyNotFoundError, self).__init__(
-            fingerprint, message)
+        super().__init__(fingerprint, message)
 
 
 @error_status(http.client.NOT_FOUND)
@@ -167,8 +166,7 @@ class GPGKeyDoesNotExistOnServer(GPGKeyNotFoundError):
     def __init__(self, fingerprint):
         message = (
             "GPG key %s does not exist on the keyserver." % fingerprint)
-        super(GPGKeyDoesNotExistOnServer, self).__init__(
-            fingerprint, message)
+        super().__init__(fingerprint, message)
 
 
 class GPGKeyRevoked(Exception):
@@ -176,8 +174,7 @@ class GPGKeyRevoked(Exception):
 
     def __init__(self, key):
         self.key = key
-        super(GPGKeyRevoked, self).__init__(
-            "%s has been publicly revoked" % (key.fingerprint, ))
+        super().__init__("%s has been publicly revoked" % (key.fingerprint, ))
 
 
 class GPGKeyExpired(Exception):
@@ -185,8 +182,7 @@ class GPGKeyExpired(Exception):
 
     def __init__(self, key):
         self.key = key
-        super(GPGKeyExpired, self).__init__(
-            "%s has expired" % (key.fingerprint, ))
+        super().__init__("%s has expired" % (key.fingerprint, ))
 
 
 class GPGKeyMismatchOnServer(Exception):
@@ -200,7 +196,7 @@ class GPGKeyMismatchOnServer(Exception):
         message = (
             "The keyserver returned the wrong key: expected %s, got %s." %
             (expected_fingerprint, keyserver_fingerprint))
-        super(GPGKeyMismatchOnServer, self).__init__(message)
+        super().__init__(message)
 
 
 class SecretGPGKeyImportDetected(Exception):
diff --git a/lib/lp/services/gpg/tests/test_gpghandler.py b/lib/lp/services/gpg/tests/test_gpghandler.py
index 5b023ba..6dba52c 100644
--- a/lib/lp/services/gpg/tests/test_gpghandler.py
+++ b/lib/lp/services/gpg/tests/test_gpghandler.py
@@ -97,7 +97,7 @@ class TestGPGHandler(TestCase):
 
     def setUp(self):
         """Get a gpghandler and login"""
-        super(TestGPGHandler, self).setUp()
+        super().setUp()
         login(ANONYMOUS)
         self.gpg_handler = getUtility(IGPGHandler)
         self.gpg_handler.resetLocalState()
@@ -108,7 +108,7 @@ class TestGPGHandler(TestCase):
         # This should be a zope test cleanup thing per SteveA.
         self.gpg_handler.resetLocalState()
         logout()
-        super(TestGPGHandler, self).tearDown()
+        super().tearDown()
 
     def populateKeyring(self):
         for email in iter_test_key_emails():
@@ -188,7 +188,7 @@ class TestGPGHandler(TestCase):
         """
         self.populateKeyring()
 
-        target_fpr = u'340CA3BB270E2716C9EE0B768E7EB7086C64A8C5'
+        target_fpr = '340CA3BB270E2716C9EE0B768E7EB7086C64A8C5'
 
         # Finding a key by its unicode fingerprint.
         filtered_keys = self.gpg_handler.localKeys(target_fpr)
@@ -197,7 +197,7 @@ class TestGPGHandler(TestCase):
 
     def test_non_ascii_filter(self):
         """localKeys should not error if passed non-ascii unicode strings."""
-        filtered_keys = self.gpg_handler.localKeys(u'non-ascii \u8463')
+        filtered_keys = self.gpg_handler.localKeys('non-ascii \u8463')
         self.assertRaises(StopIteration, next, filtered_keys)
 
     def testTestkeyrings(self):
@@ -377,7 +377,7 @@ class TestGPGHandler(TestCase):
         #     export_file.write(new_key.export())
         self.useFixture(FakeGenerateKey("ppa-sample@xxxxxxxxxxxxxxxxx"))
         new_key = self.gpg_handler.generateKey(
-            u"Launchpad PPA for Celso \xe1\xe9\xed\xf3\xfa Providelo",
+            "Launchpad PPA for Celso \xe1\xe9\xed\xf3\xfa Providelo",
             logger=logger)
         # generateKey currently only generates passwordless sign-only keys,
         # i.e. they can sign content but cannot encrypt.  The generated key
@@ -393,10 +393,10 @@ class TestGPGHandler(TestCase):
             uids=MatchesListwise([
                 MatchesStructure.byEquality(
                     name=(
-                        u"Launchpad PPA for Celso "
-                        u"\xe1\xe9\xed\xf3\xfa Providelo"),
-                    comment=u"",
-                    email=u""),
+                        "Launchpad PPA for Celso "
+                        "\xe1\xe9\xed\xf3\xfa Providelo"),
+                    comment="",
+                    email=""),
                 ])))
         # The public key is also available.
         pub_key = self.gpg_handler.retrieveKey(new_key.fingerprint)
@@ -411,10 +411,10 @@ class TestGPGHandler(TestCase):
             uids=MatchesListwise([
                 MatchesStructure.byEquality(
                     name=(
-                        u"Launchpad PPA for Celso "
-                        u"\xe1\xe9\xed\xf3\xfa Providelo"),
-                    comment=u"",
-                    email=u""),
+                        "Launchpad PPA for Celso "
+                        "\xe1\xe9\xed\xf3\xfa Providelo"),
+                    comment="",
+                    email=""),
                 ])))
         return new_key
 
@@ -441,8 +441,8 @@ class TestGPGHandler(TestCase):
         new_key = self.assertGeneratesKey(logger=logger)
         new_public_key = self.gpg_handler.retrieveKey(new_key.fingerprint)
         self.assertEqual(
-            u"INFO Injecting key_type OpenPGP 'Launchpad PPA for Celso "
-            u"\xe1\xe9\xed\xf3\xfa Providelo' into signing service\n",
+            "INFO Injecting key_type OpenPGP 'Launchpad PPA for Celso "
+            "\xe1\xe9\xed\xf3\xfa Providelo' into signing service\n",
             logger.getLogBuffer())
         self.assertEqual(1, signing_service_client.inject.call_count)
         self.assertThat(
@@ -452,8 +452,8 @@ class TestGPGHandler(TestCase):
                 Equals(new_key.export()),
                 Equals(new_public_key.export()),
                 Equals(
-                    u"Launchpad PPA for Celso "
-                    u"\xe1\xe9\xed\xf3\xfa Providelo"),
+                    "Launchpad PPA for Celso "
+                    "\xe1\xe9\xed\xf3\xfa Providelo"),
                 Equals(now.replace(tzinfo=pytz.UTC)),
                 ]))
 
@@ -470,7 +470,7 @@ class TestGPGHandler(TestCase):
         self.assertRaisesWithContent(
             ValueError, "boom",
             self.gpg_handler.generateKey,
-            u"Launchpad PPA for Celso \xe1\xe9\xed\xf3\xfa Providelo")
+            "Launchpad PPA for Celso \xe1\xe9\xed\xf3\xfa Providelo")
         self.assertEqual(1, signing_service_client.inject.call_count)
         self.assertEqual([], list(self.gpg_handler.localKeys()))
 
diff --git a/lib/lp/services/httpproxy/connect_tunneling.py b/lib/lp/services/httpproxy/connect_tunneling.py
index 39a7786..f42468e 100644
--- a/lib/lp/services/httpproxy/connect_tunneling.py
+++ b/lib/lp/services/httpproxy/connect_tunneling.py
@@ -32,8 +32,7 @@ class TunnelingTCP4ClientEndpoint(TCP4ClientEndpoint):
     def __init__(self, reactor, host, port, proxyConf, contextFactory,
                  timeout=30, bindAddress=None):
         proxyHost, proxyPort, self._proxyAuthHeader = proxyConf
-        super(TunnelingTCP4ClientEndpoint, self).__init__(reactor, proxyHost,
-            proxyPort, timeout, bindAddress)
+        super().__init__(reactor, proxyHost, proxyPort, timeout, bindAddress)
         self._tunneledHost = host
         self._tunneledPort = port
         self._contextFactory = contextFactory
@@ -74,8 +73,7 @@ class TunnelingTCP4ClientEndpoint(TCP4ClientEndpoint):
 
     def connect(self, protocolFactory):
         self._protocolFactory = protocolFactory
-        self._connectDeferred = super(
-            TunnelingTCP4ClientEndpoint, self).connect(protocolFactory)
+        self._connectDeferred = super().connect(protocolFactory)
         self._connectDeferred.addCallback(self.requestTunnel)
         self._connectDeferred.addErrback(self.connectFailed)
         return self._tunnelReadyDeferred
@@ -88,7 +86,7 @@ class TunnelingAgent(Agent):
 
     def __init__(self, reactor, proxyConf, contextFactory=None,
                  connectTimeout=None, bindAddress=None, pool=None):
-        super(TunnelingAgent, self).__init__(reactor, contextFactory,
+        super().__init__(reactor, contextFactory,
             connectTimeout, bindAddress, pool)
         self._contextFactory = contextFactory
         self._connectTimeout = connectTimeout
diff --git a/lib/lp/services/identity/interfaces/account.py b/lib/lp/services/identity/interfaces/account.py
index 63034c5..d7ccb53 100644
--- a/lib/lp/services/identity/interfaces/account.py
+++ b/lib/lp/services/identity/interfaces/account.py
@@ -295,7 +295,7 @@ class AccountStatusChoice(Choice):
             raise AccountStatusError(
                 "The status cannot change from %s to %s" %
                 (removeSecurityProxy(self.context).status, value))
-        super(AccountStatusChoice, self)._validate(value)
+        super()._validate(value)
 
 
 class IAccountPublic(Interface):
diff --git a/lib/lp/services/identity/model/account.py b/lib/lp/services/identity/model/account.py
index beb0921..e35e976 100644
--- a/lib/lp/services/identity/model/account.py
+++ b/lib/lp/services/identity/model/account.py
@@ -10,7 +10,6 @@ __all__ = [
 
 import datetime
 
-import six
 from storm.locals import ReferenceSet
 from zope.interface import implementer
 
@@ -103,7 +102,7 @@ class AccountSet:
 
         # Create an OpenIdIdentifier record if requested.
         if openid_identifier is not None:
-            assert isinstance(openid_identifier, six.text_type)
+            assert isinstance(openid_identifier, str)
             identifier = OpenIdIdentifier()
             identifier.account = account
             identifier.identifier = openid_identifier
diff --git a/lib/lp/services/identity/model/emailaddress.py b/lib/lp/services/identity/model/emailaddress.py
index 887dce7..e84ca9c 100644
--- a/lib/lp/services/identity/model/emailaddress.py
+++ b/lib/lp/services/identity/model/emailaddress.py
@@ -87,7 +87,7 @@ class EmailAddress(SQLBase, HasOwnerMixin):
         for subscription in store.find(
                 MailingListSubscription, email_address=self):
             store.remove(subscription)
-        super(EmailAddress, self).destroySelf()
+        super().destroySelf()
 
     @property
     def rdf_sha1(self):
diff --git a/lib/lp/services/identity/tests/test_account.py b/lib/lp/services/identity/tests/test_account.py
index 36cac06..5825e53 100644
--- a/lib/lp/services/identity/tests/test_account.py
+++ b/lib/lp/services/identity/tests/test_account.py
@@ -23,14 +23,14 @@ class TestAccount(TestCaseWithFactory):
 
     def test_account_repr_ansii(self):
         # Verify that ANSI displayname is ascii safe.
-        distro = self.factory.makeAccount(u'\xdc-account')
+        distro = self.factory.makeAccount('\xdc-account')
         ignore, displayname, status = repr(distro).rsplit(' ', 2)
         self.assertEqual("'\\xdc-account'", displayname)
         self.assertEqual('(Active)>', status)
 
     def test_account_repr_unicode(self):
         # Verify that Unicode displayname is ascii safe.
-        distro = self.factory.makeAccount(u'\u0170-account')
+        distro = self.factory.makeAccount('\u0170-account')
         ignore, displayname, status = repr(distro).rsplit(' ', 2)
         self.assertEqual("'\\u0170-account'", displayname)
 
diff --git a/lib/lp/services/inlinehelp/zcml.py b/lib/lp/services/inlinehelp/zcml.py
index 7642aa5..3f8a852 100644
--- a/lib/lp/services/inlinehelp/zcml.py
+++ b/lib/lp/services/inlinehelp/zcml.py
@@ -25,10 +25,10 @@ from lp.services.webapp.interfaces import ILaunchpadApplication
 class IHelpFolderDirective(Interface):
     """Directive to register an help folder."""
     folder = Path(
-        title=u'The path to the help folder.',
+        title='The path to the help folder.',
         required=True)
     name = TextLine(
-        title=u'The name to register the help folder under.',
+        title='The name to register the help folder under.',
         required=True)
 
 
diff --git a/lib/lp/services/job/celeryjob.py b/lib/lp/services/job/celeryjob.py
index c872bf8..fcc1c5f 100644
--- a/lib/lp/services/job/celeryjob.py
+++ b/lib/lp/services/job/celeryjob.py
@@ -67,7 +67,7 @@ class CeleryRunJob(RunJob):
         """
         self.dbuser = dbuser
         task_init(dbuser)
-        super(CeleryRunJob, self).run(job_id)
+        super().run(job_id)
 
     def reQueue(self, job_id, fallback_queue):
         self.apply_async(args=(job_id, self.dbuser), queue=fallback_queue)
@@ -118,7 +118,7 @@ class PrefixedTask(Task):
         """
         if task_id is None and self.task_id_prefix is not None:
             task_id = '%s_%s' % (self.task_id_prefix, uuid4())
-        return super(PrefixedTask, self).apply_async(
+        return super().apply_async(
             args=args, kwargs=kwargs, task_id=task_id, producer=producer,
             link=link, link_error=link_error, shadow=shadow, **options)
 
diff --git a/lib/lp/services/job/runner.py b/lib/lp/services/job/runner.py
index 2823909..9410069 100644
--- a/lib/lp/services/job/runner.py
+++ b/lib/lp/services/job/runner.py
@@ -359,7 +359,7 @@ class BaseJobRunner(LazrJobRunner):
             self.error_utility = errorlog.globalErrorUtility
         else:
             self.error_utility = error_utility
-        super(BaseJobRunner, self).__init__(
+        super().__init__(
             logger, oops_config=self.error_utility._oops_config,
             oopsMessage=self.error_utility.oopsMessage)
 
@@ -381,7 +381,7 @@ class BaseJobRunner(LazrJobRunner):
         if job.lease_expires is not None:
             set_default_timeout_function(lambda: job.getTimeout())
         try:
-            super(BaseJobRunner, self).runJob(IRunnableJob(job), fallback)
+            super().runJob(IRunnableJob(job), fallback)
         finally:
             set_default_timeout_function(original_timeout_function)
 
@@ -389,8 +389,7 @@ class BaseJobRunner(LazrJobRunner):
         set_request_started(
             enable_timeout=False, detail_filter=job.timeline_detail_filter)
         try:
-            return super(BaseJobRunner, self).runJobHandleError(
-                job, fallback=fallback)
+            return super().runJobHandleError(job, fallback=fallback)
         finally:
             clear_request_started()
 
@@ -575,7 +574,7 @@ class QuietAMPConnector(main.AMPConnector):
             # than ERROR.  Launchpad generates OOPSes for anything at
             # WARNING or above; we still want to do that if a child process
             # exits fatally, but not if it just writes something to stderr.
-            main.log.info(u'FROM {n}: {l}', n=self.name, l=line)
+            main.log.info('FROM {n}: {l}', n=self.name, l=line)
 
 
 class TwistedJobRunner(BaseJobRunner):
@@ -589,7 +588,7 @@ class TwistedJobRunner(BaseJobRunner):
             env['LPCONFIG'] = os.environ['LPCONFIG']
         starter = VirtualEnvProcessStarter(env=env)
         starter.connectorFactory = QuietAMPConnector
-        super(TwistedJobRunner, self).__init__(logger, error_utility)
+        super().__init__(logger, error_utility)
         self.job_source = job_source
         self.import_name = '%s.%s' % (
             removeSecurityProxy(job_source).__module__, job_source.__name__)
diff --git a/lib/lp/services/job/scripts/process_job_source.py b/lib/lp/services/job/scripts/process_job_source.py
index a7282bf..79c3686 100644
--- a/lib/lp/services/job/scripts/process_job_source.py
+++ b/lib/lp/services/job/scripts/process_job_source.py
@@ -29,7 +29,7 @@ class ProcessSingleJobSource(LaunchpadCronScript):
     """
 
     def __init__(self, *args, **kwargs):
-        super(ProcessSingleJobSource, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         # The fromlist argument is necessary so that __import__()
         # returns the bottom submodule instead of the top one.
         module = __import__(self.config_section.module,
@@ -76,7 +76,7 @@ class ProcessSingleJobSource(LaunchpadCronScript):
             self.parser.print_help()
             sys.exit(1)
         self.job_source_name = self.args[0]
-        super(ProcessSingleJobSource, self).handle_options()
+        super().handle_options()
 
     def job_counts(self, jobs):
         """Return a list of tuples containing the job name and counts."""
@@ -99,7 +99,7 @@ class ProcessSingleJobSource(LaunchpadCronScript):
         references across calls to this method.
         """
         disconnect_stores()
-        super(ProcessSingleJobSource, self)._init_db(isolation)
+        super()._init_db(isolation)
 
     def main(self):
         errorlog.globalErrorUtility.configure(self.config_name)
@@ -138,7 +138,7 @@ class ProcessJobSource(LaunchpadScript):
             self.parser.print_help()
             sys.exit(1)
         self.job_source_names = self.args
-        super(ProcessJobSource, self).handle_options()
+        super().handle_options()
 
     def main(self):
         if self.options.verbose:
diff --git a/lib/lp/services/job/scripts/tests/test_process_job_source.py b/lib/lp/services/job/scripts/tests/test_process_job_source.py
index f9fd3fc..19fcb40 100644
--- a/lib/lp/services/job/scripts/tests/test_process_job_source.py
+++ b/lib/lp/services/job/scripts/tests/test_process_job_source.py
@@ -49,7 +49,7 @@ class ProcessJobSourceTest(TestCaseWithFactory):
     script = 'cronscripts/process-job-source.py'
 
     def tearDown(self):
-        super(ProcessJobSourceTest, self).tearDown()
+        super().tearDown()
         self.layer.force_dirty_database()
 
     def test_missing_argument(self):
@@ -128,7 +128,7 @@ class ProcessJobSourceGroupsTest(TestCaseWithFactory):
         return sorted(set(sources))
 
     def tearDown(self):
-        super(ProcessJobSourceGroupsTest, self).tearDown()
+        super().tearDown()
         self.layer.force_dirty_database()
 
     def test_missing_argument(self):
diff --git a/lib/lp/services/job/tests/test_celeryjob.py b/lib/lp/services/job/tests/test_celeryjob.py
index 9757d36..9d76ef5 100644
--- a/lib/lp/services/job/tests/test_celeryjob.py
+++ b/lib/lp/services/job/tests/test_celeryjob.py
@@ -27,7 +27,7 @@ class TestRunMissingJobs(TestCaseWithFactory):
     layer = ZopelessAppServerLayer
 
     def setUp(self):
-        super(TestRunMissingJobs, self).setUp()
+        super().setUp()
         from lp.services.job.celeryjob import (
             find_missing_ready,
             run_missing_ready,
diff --git a/lib/lp/services/job/tests/test_runner.py b/lib/lp/services/job/tests/test_runner.py
index d14194c..34cee43 100644
--- a/lib/lp/services/job/tests/test_runner.py
+++ b/lib/lp/services/job/tests/test_runner.py
@@ -165,7 +165,7 @@ class TestJobRunner(StatsMixin, TestCaseWithFactory):
     layer = LaunchpadZopelessLayer
 
     def setUp(self):
-        super(TestJobRunner, self).setUp()
+        super().setUp()
         self.setUpStats()
 
     def makeTwoJobs(self):
@@ -259,7 +259,7 @@ class TestJobRunner(StatsMixin, TestCaseWithFactory):
         class DBAlterJob(NullJob):
 
             def __init__(self):
-                super(DBAlterJob, self).__init__('')
+                super().__init__('')
 
             def run(self):
                 self.job.log = 'hello'
@@ -437,7 +437,7 @@ class TestJobRunner(StatsMixin, TestCaseWithFactory):
         """runJob sets a default timeout function for urlfetch."""
         class RecordDefaultTimeoutJob(NullJob):
             def __init__(self):
-                super(RecordDefaultTimeoutJob, self).__init__("")
+                super().__init__("")
 
             def run(self):
                 self.default_timeout = get_default_timeout_function()()
@@ -641,7 +641,7 @@ class TestTwistedJobRunner(TestCaseWithFactory):
     run_tests_with = RunIsolatedTest
 
     def setUp(self):
-        super(TestTwistedJobRunner, self).setUp()
+        super().setUp()
         # The test relies on _pythonpath being importable. Thus we need to add
         # a directory that contains _pythonpath to the sys.path. We can rely
         # on the root directory of the checkout containing _pythonpath.
diff --git a/lib/lp/services/librarian/client.py b/lib/lp/services/librarian/client.py
index 17d40d9..a011bc3 100644
--- a/lib/lp/services/librarian/client.py
+++ b/lib/lp/services/librarian/client.py
@@ -103,7 +103,7 @@ class FileUploadClient:
             # Register epoll for the socket.
             self.state.s_poll = select.epoll()
             self.state.s_poll.register(self.state.s.fileno(), select.EPOLLIN)
-        except socket.error as x:
+        except OSError as x:
             raise UploadFailed(
                 '[%s:%s]: %s' % (self.upload_host, self.upload_port, x))
 
@@ -247,7 +247,7 @@ class FileUploadClient:
 
             Store.of(content).flush()
 
-            assert isinstance(aliasID, six.integer_types), \
+            assert isinstance(aliasID, int), \
                     "aliasID %r not an integer" % (aliasID, )
             return aliasID
         finally:
diff --git a/lib/lp/services/librarian/model.py b/lib/lp/services/librarian/model.py
index 2c513d3..10a387e 100644
--- a/lib/lp/services/librarian/model.py
+++ b/lib/lp/services/librarian/model.py
@@ -227,7 +227,7 @@ class LibraryFileAlias(SQLBase):
 
     def __storm_invalidated__(self):
         """Make sure that the file is closed across transaction boundary."""
-        super(LibraryFileAlias, self).__storm_invalidated__()
+        super().__storm_invalidated__()
         self.close()
 
 
@@ -247,7 +247,7 @@ class LibraryFileAliasWithParent:
 
 
 @implementer(ILibraryFileAliasSet)
-class LibraryFileAliasSet(object):
+class LibraryFileAliasSet:
     """Create and find LibraryFileAliases."""
 
     def create(self, name, size, file, contentType, expires=None,
diff --git a/lib/lp/services/librarian/tests/test_client.py b/lib/lp/services/librarian/tests/test_client.py
index 4c2035a..cb054a9 100644
--- a/lib/lp/services/librarian/tests/test_client.py
+++ b/lib/lp/services/librarian/tests/test_client.py
@@ -63,7 +63,7 @@ class PropagatingThread(threading.Thread):
             self.exc = e
 
     def join(self):
-        super(PropagatingThread, self).join()
+        super().join()
         if self.exc:
             raise self.exc
 
@@ -71,7 +71,7 @@ class PropagatingThread(threading.Thread):
 class InstrumentedLibrarianClient(LibrarianClient):
 
     def __init__(self, *args, **kwargs):
-        super(InstrumentedLibrarianClient, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         self.check_error_calls = 0
 
     sentDatabaseName = False
@@ -89,7 +89,7 @@ class InstrumentedLibrarianClient(LibrarianClient):
 
     def _checkError(self):
         self.check_error_calls += 1
-        super(InstrumentedLibrarianClient, self)._checkError()
+        super()._checkError()
 
 
 def make_mock_file(error, max_raise):
@@ -118,7 +118,7 @@ class FakeServerTestSetup(TacTestSetup):
 
     def setUp(self):
         self.port = None
-        super(FakeServerTestSetup, self).setUp()
+        super().setUp()
 
     def setUpRoot(self):
         pass
@@ -143,7 +143,7 @@ class FakeServerTestSetup(TacTestSetup):
         pass
 
     def _hasDaemonStarted(self):
-        if super(FakeServerTestSetup, self)._hasDaemonStarted():
+        if super()._hasDaemonStarted():
             with open(self.logfile) as logfile:
                 self.port = int(re.search(
                     r"Site starting on (\d+)", logfile.read()).group(1))
@@ -198,7 +198,7 @@ class EchoServer(threading.Thread):
     during the upload process.
     """
     def __init__(self):
-        super(EchoServer, self).__init__()
+        super().__init__()
         self.should_stop = False
         self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         self.socket.settimeout(1)
@@ -208,7 +208,7 @@ class EchoServer(threading.Thread):
 
     def join(self, *args, **kwargs):
         self.should_stop = True
-        super(EchoServer, self).join(*args, **kwargs)
+        super().join(*args, **kwargs)
 
     def run(self):
         while not self.should_stop:
diff --git a/lib/lp/services/librarian/tests/test_libraryfilealias_with_parent.py b/lib/lp/services/librarian/tests/test_libraryfilealias_with_parent.py
index a3fa991..d8c69e3 100644
--- a/lib/lp/services/librarian/tests/test_libraryfilealias_with_parent.py
+++ b/lib/lp/services/librarian/tests/test_libraryfilealias_with_parent.py
@@ -21,7 +21,7 @@ class TestLibraryFileAliasForBugAttachment(TestCaseWithFactory):
     layer = LaunchpadFunctionalLayer
 
     def setUp(self):
-        super(TestLibraryFileAliasForBugAttachment, self).setUp()
+        super().setUp()
         self.bug_owner = self.factory.makePerson()
         login_person(self.bug_owner)
         self.bug = self.factory.makeBug(
diff --git a/lib/lp/services/librarian/tests/test_smoketest.py b/lib/lp/services/librarian/tests/test_smoketest.py
index 185989a..4b1a5e0 100644
--- a/lib/lp/services/librarian/tests/test_smoketest.py
+++ b/lib/lp/services/librarian/tests/test_smoketest.py
@@ -31,7 +31,7 @@ def bad_urlopen(url):
 
 def error_urlopen(url):
     """A urllib replacement for testing that raises an exception."""
-    raise IOError('network error')
+    raise OSError('network error')
 
 
 def explosive_urlopen(exception, url):
@@ -44,7 +44,7 @@ class SmokeTestTestCase(TestCaseWithFactory):
     layer = ZopelessDatabaseLayer
 
     def setUp(self):
-        super(SmokeTestTestCase, self).setUp()
+        super().setUp()
         self.fake_librarian = self.useFixture(FakeLibrarian())
 
     def test_store_file(self):
diff --git a/lib/lp/services/librarianserver/librariangc.py b/lib/lp/services/librarianserver/librariangc.py
index 3779dca..aa3429f 100644
--- a/lib/lp/services/librarianserver/librariangc.py
+++ b/lib/lp/services/librarianserver/librariangc.py
@@ -18,7 +18,6 @@ from time import time
 
 import iso8601
 import pytz
-import six
 from swiftclient import client as swiftclient
 from zope.interface import implementer
 
@@ -904,7 +903,7 @@ def delete_unwanted_swift_files(con):
 def get_file_path(content_id):
     """Return the physical file path to the matching LibraryFileContent id.
     """
-    assert isinstance(content_id, six.integer_types), (
+    assert isinstance(content_id, int), (
         'Invalid content_id %s' % repr(content_id))
     return os.path.join(get_storage_root(), relative_file_path(content_id))
 
diff --git a/lib/lp/services/librarianserver/storage.py b/lib/lp/services/librarianserver/storage.py
index e8611b2..668815f 100644
--- a/lib/lp/services/librarianserver/storage.py
+++ b/lib/lp/services/librarianserver/storage.py
@@ -192,7 +192,7 @@ class TxSwiftStream(swift.SwiftStream):
         return return_chunk
 
 
-class LibraryFileUpload(object):
+class LibraryFileUpload:
     """A file upload from a client."""
     srcDigest = None
     mimetype = 'unknown/unknown'
diff --git a/lib/lp/services/librarianserver/swift.py b/lib/lp/services/librarianserver/swift.py
index 4a1139d..637110f 100644
--- a/lib/lp/services/librarianserver/swift.py
+++ b/lib/lp/services/librarianserver/swift.py
@@ -20,7 +20,6 @@ import os.path
 import re
 import time
 
-import six
 from six.moves.urllib.parse import quote
 from swiftclient import client as swiftclient
 
@@ -267,7 +266,7 @@ def swift_location(lfc_id):
     Per https://answers.launchpad.net/swift/+question/181977, we can't
     simply stuff everything into one container.
     '''
-    assert isinstance(lfc_id, six.integer_types), 'Not a LibraryFileContent.id'
+    assert isinstance(lfc_id, int), 'Not a LibraryFileContent.id'
 
     # Don't change this unless you are also going to rebuild the Swift
     # storage, as objects will no longer be found in the expected
diff --git a/lib/lp/services/librarianserver/testing/fake.py b/lib/lp/services/librarianserver/testing/fake.py
index 0dfc37a..b18f9a9 100644
--- a/lib/lp/services/librarianserver/testing/fake.py
+++ b/lib/lp/services/librarianserver/testing/fake.py
@@ -18,7 +18,6 @@ import hashlib
 import io
 
 from fixtures import Fixture
-import six
 from six.moves.urllib.parse import urljoin
 import transaction
 from transaction.interfaces import ISynchronizer
@@ -138,7 +137,7 @@ class FakeLibrarian(Fixture):
         database transaction.
         """
         # Note that all files have been committed to storage.
-        for alias in six.itervalues(self.aliases):
+        for alias in self.aliases.values():
             alias.file_committed = True
 
     def _makeAlias(self, file_id, name, content, content_type):
@@ -175,7 +174,7 @@ class FakeLibrarian(Fixture):
 
     def findBySHA256(self, sha256):
         "See `ILibraryFileAliasSet`."""
-        for alias in six.itervalues(self.aliases):
+        for alias in self.aliases.values():
             if alias.content.sha256 == sha256:
                 return alias
 
diff --git a/lib/lp/services/librarianserver/testing/server.py b/lib/lp/services/librarianserver/testing/server.py
index 67ae838..cc8375e 100644
--- a/lib/lp/services/librarianserver/testing/server.py
+++ b/lib/lp/services/librarianserver/testing/server.py
@@ -146,7 +146,7 @@ class LibrarianServerFixture(TacTestSetup):
             os.makedirs(self.root, 0o700)
 
     def _waitForDaemonStartup(self):
-        super(LibrarianServerFixture, self)._waitForDaemonStartup()
+        super()._waitForDaemonStartup()
         # Expose the dynamically allocated ports, if we used them.
         if not self._dynamic_config():
             self.download_port = config.librarian.download_port
diff --git a/lib/lp/services/librarianserver/testing/tests/test_fakelibrarian.py b/lib/lp/services/librarianserver/testing/tests/test_fakelibrarian.py
index fdd9a58..0c98684 100644
--- a/lib/lp/services/librarianserver/testing/tests/test_fakelibrarian.py
+++ b/lib/lp/services/librarianserver/testing/tests/test_fakelibrarian.py
@@ -5,7 +5,6 @@
 
 import io
 
-import six
 import transaction
 from transaction.interfaces import ISynchronizer
 from zope.component import getUtility
@@ -58,7 +57,7 @@ class LibraryAccessScenarioMixin:
 
     def test_insert_retrieve(self):
         name, content, alias = self._storeFile()
-        self.assertIsInstance(alias.id, six.integer_types)
+        self.assertIsInstance(alias.id, int)
 
         transaction.commit()
 
@@ -98,7 +97,7 @@ class LibraryAccessScenarioMixin:
     def test_addFile_returns_alias_id(self):
         alias_id = getUtility(ILibrarianClient).addFile(
             'bar.txt', 3, io.BytesIO(b'bar'), 'text/plain')
-        self.assertIsInstance(alias_id, six.integer_types)
+        self.assertIsInstance(alias_id, int)
         self.assertIsInstance(
             getUtility(ILibraryFileAliasSet)[alias_id],
             LibraryFileAlias)
@@ -142,7 +141,7 @@ class TestFakeLibrarian(LibraryAccessScenarioMixin, TestCaseWithFactory):
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        super(TestFakeLibrarian, self).setUp()
+        super().setUp()
         self.fake_librarian = self.useFixture(FakeLibrarian())
 
     def test_fake(self):
diff --git a/lib/lp/services/librarianserver/tests/test_db.py b/lib/lp/services/librarianserver/tests/test_db.py
index 7b12917..55b87b8 100644
--- a/lib/lp/services/librarianserver/tests/test_db.py
+++ b/lib/lp/services/librarianserver/tests/test_db.py
@@ -31,7 +31,7 @@ class DBTestCase(TestCase):
     layer = LaunchpadZopelessLayer
 
     def setUp(self):
-        super(DBTestCase, self).setUp()
+        super().setUp()
         switch_dbuser('librarian')
 
     def test_lookupByDigest(self):
@@ -93,7 +93,7 @@ class TestLibrarianStuff(TestCase):
     run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=30)
 
     def setUp(self):
-        super(TestLibrarianStuff, self).setUp()
+        super().setUp()
         switch_dbuser('librarian')
         self.store = IStore(LibraryFileContent)
         self.content_id = db.Library().add('deadbeef', 1234, 'abababab', 'ba')
diff --git a/lib/lp/services/librarianserver/tests/test_db_outage.py b/lib/lp/services/librarianserver/tests/test_db_outage.py
index d87fda6..5709cd9 100644
--- a/lib/lp/services/librarianserver/tests/test_db_outage.py
+++ b/lib/lp/services/librarianserver/tests/test_db_outage.py
@@ -64,7 +64,7 @@ class TestLibrarianDBOutage(TestCase):
     layer = PGBouncerLibrarianLayer
 
     def setUp(self):
-        super(TestLibrarianDBOutage, self).setUp()
+        super().setUp()
         self.pgbouncer = PGBouncerLibrarianLayer.pgbouncer_fixture
         self.client = LibrarianClient()
 
diff --git a/lib/lp/services/librarianserver/tests/test_gc.py b/lib/lp/services/librarianserver/tests/test_gc.py
index 30dbf3a..716bfa3 100644
--- a/lib/lp/services/librarianserver/tests/test_gc.py
+++ b/lib/lp/services/librarianserver/tests/test_gc.py
@@ -68,7 +68,7 @@ class TestLibrarianGarbageCollectionBase:
     layer = LaunchpadZopelessLayer
 
     def setUp(self):
-        super(TestLibrarianGarbageCollectionBase, self).setUp()
+        super().setUp()
         self.client = LibrarianClient()
         self.patch(librariangc, 'log', BufferLogger())
 
@@ -118,7 +118,7 @@ class TestLibrarianGarbageCollectionBase:
         self.con.rollback()
         self.con.close()
         del self.con
-        super(TestLibrarianGarbageCollectionBase, self).tearDown()
+        super().tearDown()
 
     def _makeDupes(self):
         """Create two duplicate LibraryFileContent entries with one
@@ -722,7 +722,7 @@ class TestSwiftLibrarianGarbageCollection(
 
         self.useFixture(FeatureFixture({'librarian.swift.enabled': True}))
 
-        super(TestSwiftLibrarianGarbageCollection, self).setUp()
+        super().setUp()
 
         # Move files into Swift.
         path = librariangc.get_file_path(self.f1_id)
@@ -935,7 +935,7 @@ class TestTwoSwiftsLibrarianGarbageCollection(
     def setUp(self):
         self.old_swift_fixture = self.useFixture(
             SwiftFixture(old_instance=True))
-        super(TestTwoSwiftsLibrarianGarbageCollection, self).setUp()
+        super().setUp()
 
     def test_swift_files_multiple_instances(self):
         # swift_files yields files in the correct order across multiple
@@ -1068,7 +1068,7 @@ class TestBlobCollection(TestCase):
     layer = LaunchpadZopelessLayer
 
     def setUp(self):
-        super(TestBlobCollection, self).setUp()
+        super().setUp()
         # Add in some sample data
         cur = cursor()
 
@@ -1210,7 +1210,7 @@ class TestBlobCollection(TestCase):
     def tearDown(self):
         self.con.rollback()
         self.con.close()
-        super(TestBlobCollection, self).tearDown()
+        super().tearDown()
 
     def test_DeleteExpiredBlobs(self):
         # Delete expired blobs from the TemporaryBlobStorage table
diff --git a/lib/lp/services/librarianserver/tests/test_storage_db.py b/lib/lp/services/librarianserver/tests/test_storage_db.py
index dd78f09..6d97bbb 100644
--- a/lib/lp/services/librarianserver/tests/test_storage_db.py
+++ b/lib/lp/services/librarianserver/tests/test_storage_db.py
@@ -34,7 +34,7 @@ class LibrarianStorageDBTests(TestCase):
     layer = LaunchpadZopelessLayer
 
     def setUp(self):
-        super(LibrarianStorageDBTests, self).setUp()
+        super().setUp()
         switch_dbuser('librarian')
         self.directory = self.useFixture(TempDir()).path
         self.storage = LibrarianStorage(self.directory, db.Library())
@@ -146,7 +146,7 @@ class LibrarianStorageSwiftTests(TestCase):
     run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=30)
 
     def setUp(self):
-        super(LibrarianStorageSwiftTests, self).setUp()
+        super().setUp()
         switch_dbuser('librarian')
         self.swift_fixture = self.useFixture(SwiftFixture())
         self.useFixture(FeatureFixture({'librarian.swift.enabled': True}))
diff --git a/lib/lp/services/librarianserver/tests/test_swift.py b/lib/lp/services/librarianserver/tests/test_swift.py
index ecdb6d9..02ab7c5 100644
--- a/lib/lp/services/librarianserver/tests/test_swift.py
+++ b/lib/lp/services/librarianserver/tests/test_swift.py
@@ -9,7 +9,6 @@ import os.path
 import time
 from unittest.mock import patch
 
-import six
 from swiftclient import client as swiftclient
 import transaction
 
@@ -34,7 +33,7 @@ class TestFeedSwift(TestCase):
     layer = LaunchpadZopelessLayer
 
     def setUp(self):
-        super(TestFeedSwift, self).setUp()
+        super().setUp()
         self.swift_fixture = self.useFixture(SwiftFixture())
         self.useFixture(FeatureFixture({'librarian.swift.enabled': True}))
         transaction.commit()
@@ -59,7 +58,7 @@ class TestFeedSwift(TestCase):
         self.lfcs = [lfa.content for lfa in self.lfas]
 
     def tearDown(self):
-        super(TestFeedSwift, self).tearDown()
+        super().tearDown()
         # Restart the Librarian so it picks up the feature flag change.
         self.attachLibrarianLog(LibrarianLayer.librarian_fixture)
         LibrarianLayer.librarian_fixture.cleanUp()
@@ -227,7 +226,7 @@ class TestFeedSwift(TestCase):
         # stored in Swift as a single object.
         size = 512 * 1024  # 512KB
         expected_content = b''.join(
-            six.int2byte(i % 256) for i in range(0, size))
+            bytes((i % 256,)) for i in range(0, size))
         lfa_id = self.add_file('hello_bigboy.xls', expected_content)
 
         # Data round trips when served from disk.
@@ -243,7 +242,7 @@ class TestFeedSwift(TestCase):
         size = LibrarianStorage.CHUNK_SIZE * 50
         self.assertTrue(size > 1024 * 1024)
         expected_content = b''.join(
-            six.int2byte(i % 256) for i in range(0, size))
+            bytes((i % 256,)) for i in range(0, size))
         lfa_id = self.add_file('hello_bigboy.xls', expected_content)
         lfc = IStore(LibraryFileAlias).get(LibraryFileAlias, lfa_id).content
 
@@ -266,7 +265,7 @@ class TestFeedSwift(TestCase):
         size = LibrarianStorage.CHUNK_SIZE * 50 + 1
         self.assertTrue(size > 1024 * 1024)
         expected_content = b''.join(
-            six.int2byte(i % 256) for i in range(0, size))
+            bytes((i % 256,)) for i in range(0, size))
         lfa_id = self.add_file('hello_bigboy.xls', expected_content)
         lfc = IStore(LibraryFileAlias).get(LibraryFileAlias, lfa_id).content
 
@@ -286,7 +285,7 @@ class TestFeedSwift(TestCase):
         size = LibrarianStorage.CHUNK_SIZE * 50
         self.assertTrue(size > 1024 * 1024)
         expected_content = b''.join(
-            six.int2byte(i % 256) for i in range(0, size))
+            bytes((i % 256,)) for i in range(0, size))
         lfa_id = self.add_file('hello_bigboy.xls', expected_content)
         lfa = IStore(LibraryFileAlias).get(LibraryFileAlias, lfa_id)
         lfc = lfa.content
diff --git a/lib/lp/services/librarianserver/tests/test_web.py b/lib/lp/services/librarianserver/tests/test_web.py
index 6325163..5800724 100644
--- a/lib/lp/services/librarianserver/tests/test_web.py
+++ b/lib/lp/services/librarianserver/tests/test_web.py
@@ -101,7 +101,7 @@ class LibrarianZopelessWebTestMixin(LibrarianWebTestMixin):
     layer = LaunchpadZopelessLayer
 
     def setUp(self):
-        super(LibrarianZopelessWebTestMixin, self).setUp()
+        super().setUp()
         switch_dbuser(config.librarian.dbuser)
 
     def commit(self):
@@ -157,7 +157,7 @@ class LibrarianWebTestCase(LibrarianWebTestMixin, TestCaseWithFactory):
         # displaying Ubuntu build logs in the browser.  The mimetype should be
         # "text/plain" for these files.
         client = LibrarianClient()
-        contents = 'Build log \N{SNOWMAN}...'.encode('UTF-8')
+        contents = 'Build log \N{SNOWMAN}...'.encode()
         build_log = BytesIO()
         with GzipFile(mode='wb', fileobj=build_log) as f:
             f.write(contents)
@@ -491,7 +491,7 @@ class LibrarianWebMacaroonTestCase(LibrarianWebTestMixin, TestCaseWithFactory):
     layer = AppServerLayer
 
     def setUp(self):
-        super(LibrarianWebMacaroonTestCase, self).setUp()
+        super().setUp()
         # Copy launchpad.internal_macaroon_secret_key from the appserver
         # config so that we can issue macaroons using it.
         with ConfigUseFixture(self.layer.appserver_config_name):
@@ -553,7 +553,7 @@ class DeletedContentTestCase(unittest.TestCase):
     layer = LaunchpadZopelessLayer
 
     def setUp(self):
-        super(DeletedContentTestCase, self).setUp()
+        super().setUp()
         switch_dbuser(config.librarian.dbuser)
 
     def test_deletedContentNotFound(self):
diff --git a/lib/lp/services/librarianserver/web.py b/lib/lp/services/librarianserver/web.py
index 55ad829..51b997b 100644
--- a/lib/lp/services/librarianserver/web.py
+++ b/lib/lp/services/librarianserver/web.py
@@ -48,7 +48,7 @@ defaultResource = static.Data(b"""
         <p><small>Copyright 2004-2021 Canonical Ltd.</small></p>
         <!-- kthxbye. -->
         </body></html>
-        """, type=six.ensure_str('text/html'))
+        """, type='text/html')
 fourOhFour = resource.NoResource('No such resource')
 
 
@@ -258,7 +258,7 @@ class File(resource.Resource):
 
 
 @implementer(IPushProducer)
-class FileProducer(object):
+class FileProducer:
 
     buffer_size = abstract.FileDescriptor.bufferSize
 
@@ -314,7 +314,7 @@ class DigestSearchResource(resource.Resource):
             digest = six.ensure_text(request.args[b'digest'][0])
         except (LookupError, UnicodeDecodeError):
             return static.Data(
-                b'Bad search', six.ensure_str('text/plain')).render(request)
+                b'Bad search', 'text/plain').render(request)
 
         deferred = deferToThread(self._matchingAliases, digest)
         deferred.addCallback(self._cb_matchingAliases, request)
@@ -333,7 +333,7 @@ class DigestSearchResource(resource.Resource):
         text = '\n'.join([str(len(matches))] + matches)
         response = static.Data(
             text.encode('utf-8'),
-            six.ensure_str('text/plain; charset=utf-8')).render(request)
+            'text/plain; charset=utf-8').render(request)
         request.write(response)
         request.finish()
 
@@ -342,7 +342,7 @@ class DigestSearchResource(resource.Resource):
 robotsTxt = static.Data(b"""
 User-agent: *
 Disallow: /
-""", type=six.ensure_str('text/plain'))
+""", type='text/plain')
 
 
 def _eb(failure, request):
diff --git a/lib/lp/services/limitedlist.py b/lib/lp/services/limitedlist.py
index b9833b0..0084010 100644
--- a/lib/lp/services/limitedlist.py
+++ b/lib/lp/services/limitedlist.py
@@ -10,20 +10,20 @@ class LimitedList(list):
     """A mutable sequence that takes a limited number of elements."""
 
     def __new__(cls, max_length, value=None):
-        return super(LimitedList, cls).__new__(cls)
+        return super().__new__(cls)
 
     def __init__(self, max_length, value=None):
         if value is None:
             value = []
         elif len(value) > max_length:
             value = value[-max_length:]
-        super(LimitedList, self).__init__(value)
+        super().__init__(value)
         self.max_length = max_length
 
     def __repr__(self):
         return (
             '<LimitedList(%s, %s)>'
-            % (self.max_length, super(LimitedList, self).__repr__()))
+            % (self.max_length, super().__repr__()))
 
     def _ensureLength(self):
         """Ensure that the maximum length is not exceeded."""
@@ -32,46 +32,44 @@ class LimitedList(list):
             del self[0:elements_to_drop]
 
     def __add__(self, other):
-        return LimitedList(
-            self.max_length, super(LimitedList, self).__add__(other))
+        return LimitedList(self.max_length, super().__add__(other))
 
     def __radd__(self, other):
         return LimitedList(self.max_length, other.__add__(self))
 
     def __iadd__(self, other):
-        result = super(LimitedList, self).__iadd__(other)
+        result = super().__iadd__(other)
         self._ensureLength()
         return result
 
     def __mul__(self, other):
-        return LimitedList(
-            self.max_length, super(LimitedList, self).__mul__(other))
+        return LimitedList(self.max_length, super().__mul__(other))
 
     def __rmul__(self, other):
         return self.__mul__(other)
 
     def __imul__(self, other):
-        result = super(LimitedList, self).__imul__(other)
+        result = super().__imul__(other)
         self._ensureLength()
         return result
 
     def __setitem__(self, key, value):
-        result = super(LimitedList, self).__setitem__(key, value)
+        result = super().__setitem__(key, value)
         if isinstance(key, slice):
             self._ensureLength()
         return result
 
     def append(self, value):
-        result = super(LimitedList, self).append(value)
+        result = super().append(value)
         self._ensureLength()
         return result
 
     def extend(self, value):
-        result = super(LimitedList, self).extend(value)
+        result = super().extend(value)
         self._ensureLength()
         return result
 
     def insert(self, position, value):
-        result = super(LimitedList, self).insert(position, value)
+        result = super().insert(position, value)
         self._ensureLength()
         return result
diff --git a/lib/lp/services/log/logger.py b/lib/lp/services/log/logger.py
index 848ff0c..0a15759 100644
--- a/lib/lp/services/log/logger.py
+++ b/lib/lp/services/log/logger.py
@@ -107,7 +107,7 @@ class FakeLogger:
         return self.loglevel
 
     def _format_message(self, msg, *args):
-        if not isinstance(msg, six.string_types):
+        if not isinstance(msg, str):
             msg = str(msg)
         # To avoid type errors when the msg has % values and args is empty,
         # don't expand the string with empty args.
@@ -196,7 +196,7 @@ class BufferLogger(FakeLogger):
     # service.
 
     def __init__(self):
-        super(BufferLogger, self).__init__(six.StringIO())
+        super().__init__(six.StringIO())
 
     def getLogBuffer(self):
         """Return the existing log messages."""
diff --git a/lib/lp/services/macaroons/interfaces.py b/lib/lp/services/macaroons/interfaces.py
index 3a39356..758a9de 100644
--- a/lib/lp/services/macaroons/interfaces.py
+++ b/lib/lp/services/macaroons/interfaces.py
@@ -23,7 +23,7 @@ class BadMacaroonContext(Exception):
     def __init__(self, context, message=None):
         if message is None:
             message = "Cannot handle context %r." % context
-        super(BadMacaroonContext, self).__init__(message)
+        super().__init__(message)
         self.context = context
 
 
diff --git a/lib/lp/services/macaroons/testing.py b/lib/lp/services/macaroons/testing.py
index b570a54..2b3ed5b 100644
--- a/lib/lp/services/macaroons/testing.py
+++ b/lib/lp/services/macaroons/testing.py
@@ -31,7 +31,7 @@ class MacaroonVerifies(Matcher):
     """Matches if a macaroon can be verified."""
 
     def __init__(self, issuer_name, context, matcher=None, **verify_kwargs):
-        super(MacaroonVerifies, self).__init__()
+        super().__init__()
         self.issuer_name = issuer_name
         self.context = context
         self.matcher = matcher
diff --git a/lib/lp/services/mail/basemailer.py b/lib/lp/services/mail/basemailer.py
index 3fcd2ee..04d907e 100644
--- a/lib/lp/services/mail/basemailer.py
+++ b/lib/lp/services/mail/basemailer.py
@@ -10,7 +10,6 @@ import logging
 from smtplib import SMTPException
 import sys
 
-import six
 from zope.component import getUtility
 from zope.error.interfaces import IErrorReportingUtility
 from zope.security.management import getSecurityPolicy
@@ -78,7 +77,7 @@ class BaseMailer:
         self._subject_template = subject
         self._template_name = template_name
         self._recipients = NotificationRecipientSet()
-        for recipient, reason in six.iteritems(recipients):
+        for recipient, reason in recipients.items():
             self._recipients.add(recipient, reason, reason.mail_header)
         self.from_address = from_address
         self.delta = delta
diff --git a/lib/lp/services/mail/interfaces.py b/lib/lp/services/mail/interfaces.py
index c383c12..bd8387f 100644
--- a/lib/lp/services/mail/interfaces.py
+++ b/lib/lp/services/mail/interfaces.py
@@ -72,8 +72,8 @@ class IMailHandler(Interface):
     """
 
     allow_unknown_users = Bool(
-        title=u"Allow unknown users",
-        description=u"The handler can handle emails from persons not"
+        title="Allow unknown users",
+        description="The handler can handle emails from persons not"
                     " registered in Launchpad (which will result in an"
                     " anonymous interaction being set up.")
 
diff --git a/lib/lp/services/mail/mailbox.py b/lib/lp/services/mail/mailbox.py
index d1698c6..8dd54a3 100644
--- a/lib/lp/services/mail/mailbox.py
+++ b/lib/lp/services/mail/mailbox.py
@@ -11,7 +11,6 @@ __all__ = [
 
 import os
 import poplib
-import socket
 import threading
 
 from zope.interface import (
@@ -119,7 +118,7 @@ class POP3MailBox:
                 popbox = poplib.POP3_SSL(self._host)
             else:
                 popbox = poplib.POP3(self._host)
-        except socket.error as e:
+        except OSError as e:
             raise MailBoxError(str(e))
         try:
             popbox.user(self._user)
diff --git a/lib/lp/services/mail/meta.py b/lib/lp/services/mail/meta.py
index 24e3495..efd413d 100644
--- a/lib/lp/services/mail/meta.py
+++ b/lib/lp/services/mail/meta.py
@@ -37,26 +37,26 @@ def testMailBoxHandler(_context):
 class IPOP3MailBoxDirective(Interface):
     """Configure a mail box which interfaces to a POP3 server."""
     host = ASCII(
-            title=u"Host",
-            description=u"Host name of the POP3 server.",
+            title="Host",
+            description="Host name of the POP3 server.",
             required=True,
             )
 
     user = ASCII(
-            title=u"User",
-            description=u"User name to connect to the POP3 server with.",
+            title="User",
+            description="User name to connect to the POP3 server with.",
             required=True,
             )
 
     password = ASCII(
-            title=u"Password",
-            description=u"Password to connect to the POP3 server with.",
+            title="Password",
+            description="Password to connect to the POP3 server with.",
             required=True,
             )
 
     ssl = Bool(
-            title=u"SSL",
-            description=u"Use SSL.",
+            title="SSL",
+            description="Use SSL.",
             required=False,
             default=False)
 
@@ -69,8 +69,8 @@ def pop3MailBoxHandler(_context, host, user, password, ssl=False):
 class IDirectoryMailBoxDirective(Interface):
     """Configure a mail box which interfaces to a directory of raw files."""
     directory = ASCII(
-            title=u"Directory",
-            description=u"The directory containing the raw mail files.",
+            title="Directory",
+            description="The directory containing the raw mail files.",
             required=True,
             )
 
@@ -82,28 +82,28 @@ def directorymailBoxHandler(_context, directory):
 
 class IStubMailerDirective(IMailerDirective):
     from_addr = ASCII(
-            title=u"From Address",
-            description=u"All outgoing emails will use this email address",
+            title="From Address",
+            description="All outgoing emails will use this email address",
             required=True,
             )
     to_addr = ASCII(
-            title=u"To Address",
+            title="To Address",
             description=(
-                u"All outgoing emails will be redirected to this email "
-                u"address"),
+                "All outgoing emails will be redirected to this email "
+                "address"),
             required=True,
             )
     mailer = ASCII(
-            title=u"Mailer to use",
-            description=u"""\
+            title="Mailer to use",
+            description="""\
                 Which registered mailer to use, such as configured with
                 the smtpMailer or sendmailMailer directives""",
                 required=False,
                 default='smtp',
                 )
     rewrite = Bool(
-            title=u"Rewrite headers",
-            description=u"""\
+            title="Rewrite headers",
+            description="""\
                     If true, headers are rewritten in addition to the
                     destination address in the envelope. May me required
                     to bypass spam filters.""",
@@ -137,19 +137,19 @@ def testMailerHandler(_context, name):
 
 class IMboxMailerDirective(IMailerDirective):
     filename = ASCII(
-        title=u'File name',
-        description=u'Unix mbox file to store outgoing emails in',
+        title='File name',
+        description='Unix mbox file to store outgoing emails in',
         required=True,
         )
     overwrite = Bool(
-        title=u'Overwrite',
-        description=u'Whether to overwrite the existing mbox file or not',
+        title='Overwrite',
+        description='Whether to overwrite the existing mbox file or not',
         required=False,
         default=False,
         )
     mailer = ASCII(
-        title=u"Chained mailer to which messages are forwarded",
-        description=u"""\
+        title="Chained mailer to which messages are forwarded",
+        description="""\
             Optional mailer to forward messages to, such as those configured
             with smtpMailer, sendmailMailer, or testMailer directives.  When
             not given, the message is not forwarded but only stored in the
diff --git a/lib/lp/services/mail/notificationrecipientset.py b/lib/lp/services/mail/notificationrecipientset.py
index ee324ad..ca48e7c 100644
--- a/lib/lp/services/mail/notificationrecipientset.py
+++ b/lib/lp/services/mail/notificationrecipientset.py
@@ -11,7 +11,6 @@ __all__ = [
 
 from operator import attrgetter
 
-import six
 from zope.interface import implementer
 from zope.security.proxy import (
     isinstance as zope_isinstance,
@@ -76,7 +75,7 @@ class NotificationRecipientSet:
 
     def __contains__(self, person_or_email):
         """See `INotificationRecipientSet`."""
-        if zope_isinstance(person_or_email, six.string_types):
+        if zope_isinstance(person_or_email, str):
             return person_or_email in self._emailToPerson
         elif IPerson.providedBy(person_or_email):
             return person_or_email in self._personToRationale
@@ -89,7 +88,7 @@ class NotificationRecipientSet:
 
     def getReason(self, person_or_email):
         """See `INotificationRecipientSet`."""
-        if zope_isinstance(person_or_email, six.string_types):
+        if zope_isinstance(person_or_email, str):
             try:
                 person = self._emailToPerson[person_or_email]
             except KeyError:
diff --git a/lib/lp/services/mail/sendmail.py b/lib/lp/services/mail/sendmail.py
index 133b423..3e24695 100644
--- a/lib/lp/services/mail/sendmail.py
+++ b/lib/lp/services/mail/sendmail.py
@@ -84,11 +84,9 @@ def do_paranoid_email_content_validation(from_addr, to_addrs, subject, body):
     # XXX StuartBishop 2005-03-19:
     # These checks need to be migrated upstream if this bug
     # still exists in modern Z3.
-    assert zisinstance(from_addr, six.string_types), \
-        'Invalid From: %r' % from_addr
-    assert zisinstance(subject, six.string_types), \
-        'Invalid Subject: %r' % subject
-    assert zisinstance(body, six.string_types), 'Invalid body: %r' % body
+    assert zisinstance(from_addr, str), 'Invalid From: %r' % from_addr
+    assert zisinstance(subject, str), 'Invalid Subject: %r' % subject
+    assert zisinstance(body, str), 'Invalid body: %r' % body
 
 
 def do_paranoid_envelope_to_validation(to_addrs):
@@ -102,7 +100,7 @@ def do_paranoid_envelope_to_validation(to_addrs):
     assert (zisinstance(to_addrs, (list, tuple, set))
             and len(to_addrs) > 0), 'Invalid To: %r' % (to_addrs,)
     for addr in to_addrs:
-        assert zisinstance(addr, six.string_types) and bool(addr), \
+        assert zisinstance(addr, str) and bool(addr), \
                 'Invalid recipient: %r in %r' % (addr, to_addrs)
         assert '\n' not in addr, (
             "Address contains carriage returns: %r" % (addr,))
@@ -202,13 +200,13 @@ def simple_sendmail(from_addr, to_addrs, subject, body, headers=None,
     return ctrl.send()
 
 
-class MailController(object):
+class MailController:
     """Message generation interface closer to peoples' mental model."""
 
     def __init__(self, from_addr, to_addrs, subject, body, headers=None,
                  envelope_to=None, bulk=True):
         self.from_addr = from_addr
-        if zisinstance(to_addrs, (bytes, six.text_type)):
+        if zisinstance(to_addrs, (bytes, str)):
             to_addrs = [to_addrs]
         self.to_addrs = to_addrs
         self.envelope_to = envelope_to
@@ -223,7 +221,7 @@ class MailController(object):
     def addAttachment(self, content, content_type='application/octet-stream',
                       inline=False, filename=None, charset=None):
         attachment = Message()
-        if charset and isinstance(content, six.text_type):
+        if charset and isinstance(content, str):
             content = content.encode(charset)
         attachment.add_header('Content-Type', content_type)
         if inline:
@@ -453,9 +451,9 @@ def sendmail(message, to_addrs=None, bulk=True):
 
     raw_message = message_as_bytes(message)
     message_detail = message['Subject']
-    if not isinstance(message_detail, six.string_types):
+    if not isinstance(message_detail, str):
         # Might be a Header object; can be squashed.
-        message_detail = six.text_type(message_detail)
+        message_detail = str(message_detail)
     if _immediate_mail_delivery:
         # Immediate email delivery is not unit tested, and won't be.
         # The immediate-specific stuff is pretty simple though so this
@@ -514,7 +512,7 @@ def raw_sendmail(from_addr, to_addrs, raw_message, message_detail):
     :param message_detail: String of detail about the message
         to be recorded to help with debugging, eg the message subject.
     """
-    assert not isinstance(to_addrs, six.string_types), \
+    assert not isinstance(to_addrs, str), \
         'to_addrs must be a sequence'
     assert isinstance(raw_message, bytes), 'Not a byte string'
     assert raw_message.decode('ascii'), 'Not ASCII - badly encoded message'
diff --git a/lib/lp/services/mail/tests/test_basemailer.py b/lib/lp/services/mail/tests/test_basemailer.py
index c19ee2a..182d063 100644
--- a/lib/lp/services/mail/tests/test_basemailer.py
+++ b/lib/lp/services/mail/tests/test_basemailer.py
@@ -65,7 +65,7 @@ class RaisingMailController(MailController):
         if getattr(self, 'raise_on_send', False):
             raise SMTPException('boom')
         else:
-            super(RaisingMailController, self).send(bulk)
+            super().send(bulk)
 
 
 class RaisingMailControllerFactory:
diff --git a/lib/lp/services/mail/tests/test_dkim.py b/lib/lp/services/mail/tests/test_dkim.py
index 09adeb9..abc9fa4 100644
--- a/lib/lp/services/mail/tests/test_dkim.py
+++ b/lib/lp/services/mail/tests/test_dkim.py
@@ -120,7 +120,7 @@ class TestDKIM(TestCaseWithFactory):
             key = b'abcdefg'
         else:
             raise ValueError(response_type)
-        self._dns_responses[u'example._domainkey.canonical.com.'] = key
+        self._dns_responses['example._domainkey.canonical.com.'] = key
 
     def get_dkim_log(self):
         return self._log_output.getvalue()
diff --git a/lib/lp/services/mail/tests/test_incoming.py b/lib/lp/services/mail/tests/test_incoming.py
index b442bb0..97f2348 100644
--- a/lib/lp/services/mail/tests/test_incoming.py
+++ b/lib/lp/services/mail/tests/test_incoming.py
@@ -125,7 +125,7 @@ class IncomingTestCase(TestCaseWithFactory):
             "An error occurred while processing a mail you sent to "
             "Launchpad's email\ninterface.\n\n\n"
             "Error message:\n\nSignature couldn't be verified: "
-            "(7, 58, %r)" % u"No data",
+            "(7, 58, %r)" % "No data",
             body)
 
     def test_expired_key(self):
@@ -270,13 +270,13 @@ class IncomingTestCase(TestCaseWithFactory):
     def test_invalid_from_address_unicode(self):
         # Invalid From: header such as no "@" is handled.
         message, test_handler = self.makeSentMessage(
-            u'm\xeda@xxxxxx', 'test@xxxxxx')
+            'm\xeda@xxxxxx', 'test@xxxxxx')
         handleMail()
         self.assertEqual([], self.oopses)
         self.assertEqual(1, len(test_handler.handledMails))
         self.assertEqual(
-            u'm\xeda@xxxxxx',
-            six.text_type(make_header(decode_header(
+            'm\xeda@xxxxxx',
+            str(make_header(decode_header(
                 test_handler.handledMails[0]['From']))))
 
     def test_invalid_from_address_no_mime_encoding(self):
@@ -284,7 +284,7 @@ class IncomingTestCase(TestCaseWithFactory):
         # is handled.
         test_handler = FakeHandler()
         mail_handlers.add('lp.dev', test_handler)
-        raw_message = dedent(u"""\
+        raw_message = dedent("""\
             Content-Type: text/plain; charset="UTF-8"
             MIME-Version: 1.0
             Message-Id: <message-id>
@@ -295,7 +295,7 @@ class IncomingTestCase(TestCaseWithFactory):
             body
             """).encode('UTF-8')
         TestMailer().send(
-            u'\u05D0 <alef@xxxxxx>'.encode('UTF-8'), 'test@xxxxxx',
+            '\u05D0 <alef@xxxxxx>'.encode(), 'test@xxxxxx',
             raw_message)
         handleMail()
         self.assertEqual([], self.oopses)
@@ -307,13 +307,13 @@ class IncomingTestCase(TestCaseWithFactory):
     def test_invalid_cc_address_unicode(self):
         # Invalid Cc: header such as no "@" is handled.
         message, test_handler = self.makeSentMessage(
-            'me@xxxxxx', 'test@xxxxxx', cc=u'm\xeda@xxxxxx')
+            'me@xxxxxx', 'test@xxxxxx', cc='m\xeda@xxxxxx')
         handleMail()
         self.assertEqual([], self.oopses)
         self.assertEqual(1, len(test_handler.handledMails))
         self.assertEqual(
-            u'm\xeda@xxxxxx',
-            six.text_type(make_header(decode_header(
+            'm\xeda@xxxxxx',
+            str(make_header(decode_header(
                 test_handler.handledMails[0]['Cc']))))
 
 
diff --git a/lib/lp/services/mail/tests/test_mailbox.py b/lib/lp/services/mail/tests/test_mailbox.py
index 3ce73c2..d0429ce 100644
--- a/lib/lp/services/mail/tests/test_mailbox.py
+++ b/lib/lp/services/mail/tests/test_mailbox.py
@@ -20,7 +20,7 @@ from lp.testing import (
 class TestDirectoryMailBox(TestCase):
 
     def setUp(self):
-        super(TestDirectoryMailBox, self).setUp()
+        super().setUp()
         # Create a temp directory.
         self.email_dir = tempfile.mkdtemp()
         self.addCleanup(rmtree, self.email_dir)
diff --git a/lib/lp/services/mail/tests/test_sendmail.py b/lib/lp/services/mail/tests/test_sendmail.py
index 55e00bc..1965ee5 100644
--- a/lib/lp/services/mail/tests/test_sendmail.py
+++ b/lib/lp/services/mail/tests/test_sendmail.py
@@ -6,7 +6,6 @@ import email.header
 from email.message import Message
 import unittest
 
-import six
 from zope.interface import implementer
 from zope.sendmail.interfaces import IMailDelivery
 
@@ -88,8 +87,8 @@ class TestMailController(TestCase):
 
     def test_MakeMessageSpecialChars(self):
         """A message should have its to and from addrs converted to ascii."""
-        to_addr = u'\u1100to@xxxxxxxxxxx'
-        from_addr = u'\u1100from@xxxxxxxxxxx'
+        to_addr = '\u1100to@xxxxxxxxxxx'
+        from_addr = '\u1100from@xxxxxxxxxxx'
         ctrl = MailController(from_addr, to_addr, 'subject', 'body')
         message = ctrl.makeMessage()
         self.assertEqual('=?utf-8?b?4YSAZnJvbUBleGFtcGxlLmNvbQ==?=',
@@ -129,7 +128,7 @@ class TestMailController(TestCase):
         # UTF-8 encoded MIME text, and the message as a whole can be flattened
         # to a string with Unicode errors.
         ctrl = MailController(
-            'from@xxxxxxxxxxx', 'to@xxxxxxxxxxx', 'subject', u'Bj\xf6rn')
+            'from@xxxxxxxxxxx', 'to@xxxxxxxxxxx', 'subject', 'Bj\xf6rn')
         message = ctrl.makeMessage()
         # Make sure that the message can be flattened to a string as sendmail
         # does without raising a UnicodeEncodeError.
@@ -141,7 +140,7 @@ class TestMailController(TestCase):
         # UTF-8 encoded MIME text, and the message as a whole can be flattened
         # to a string with Unicode errors.
         ctrl = MailController(
-            'from@xxxxxxxxxxx', 'to@xxxxxxxxxxx', 'subject', u'Bj\xf6rn')
+            'from@xxxxxxxxxxx', 'to@xxxxxxxxxxx', 'subject', 'Bj\xf6rn')
         ctrl.addAttachment('attach')
         message = ctrl.makeMessage()
         # Make sure that the message can be flattened to a string as sendmail
@@ -154,7 +153,7 @@ class TestMailController(TestCase):
     def test_MakeMessage_with_binary_attachment(self):
         """Message should still encode as ascii with non-ascii attachments."""
         ctrl = MailController(
-            'from@xxxxxxxxxxx', 'to@xxxxxxxxxxx', 'subject', u'Body')
+            'from@xxxxxxxxxxx', 'to@xxxxxxxxxxx', 'subject', 'Body')
         ctrl.addAttachment('\x00\xffattach')
         message = ctrl.makeMessage()
         self.assertTrue(
@@ -163,7 +162,7 @@ class TestMailController(TestCase):
     def test_MakeMessage_with_non_binary_attachment(self):
         """Simple ascii attachments should not be encoded."""
         ctrl = MailController(
-            'from@xxxxxxxxxxx', 'to@xxxxxxxxxxx', 'subject', u'Body')
+            'from@xxxxxxxxxxx', 'to@xxxxxxxxxxx', 'subject', 'Body')
         ctrl.addAttachment('Hello, I am ascii')
         message = ctrl.makeMessage()
         body, attachment = message.get_payload()
@@ -225,7 +224,7 @@ class TestMailController(TestCase):
 
     def test_encodeOptimally_with_text(self):
         """Mostly-ascii attachments should be encoded as quoted-printable."""
-        text = u'I went to the caf\u00e9 today.'.encode('utf-8')
+        text = 'I went to the caf\u00e9 today.'.encode()
         part = Message()
         part.set_payload(text)
         MailController.encodeOptimally(part)
@@ -301,11 +300,11 @@ class TestMailController(TestCase):
         a0 = actions[0]
         self.assertEqual(a0.category, 'sendmail')
         self.assertEqual(a0.detail, subject)
-        self.assertIsInstance(a0.detail, six.string_types)
+        self.assertIsInstance(a0.detail, str)
 
 
 @implementer(IMailDelivery)
-class RecordingMailer(object):
+class RecordingMailer:
 
     def send(self, from_addr, to_addr, raw_message):
         self.from_addr = from_addr
diff --git a/lib/lp/services/memcache/tests/test_memcache_client.py b/lib/lp/services/memcache/tests/test_memcache_client.py
index 5574d22..b9989d8 100644
--- a/lib/lp/services/memcache/tests/test_memcache_client.py
+++ b/lib/lp/services/memcache/tests/test_memcache_client.py
@@ -26,7 +26,7 @@ class MemcacheClientTestCase(TestCase):
     layer = LaunchpadZopelessLayer
 
     def setUp(self):
-        super(MemcacheClientTestCase, self).setUp()
+        super().setUp()
         self.client = getUtility(IMemcacheClient)
 
     def test_basics(self):
diff --git a/lib/lp/services/messages/interfaces/message.py b/lib/lp/services/messages/interfaces/message.py
index ed6a533..a699dd9 100644
--- a/lib/lp/services/messages/interfaces/message.py
+++ b/lib/lp/services/messages/interfaces/message.py
@@ -138,7 +138,7 @@ class IMessageView(IMessageCommon):
     title = TextLine(
         title=_('The message title, usually just the subject.'),
         readonly=True)
-    visible = Bool(title=u"This message is visible or not.", required=False,
+    visible = Bool(title="This message is visible or not.", required=False,
         default=True)
 
     bugattachments = exported(
diff --git a/lib/lp/services/messages/model/message.py b/lib/lp/services/messages/model/message.py
index 53f4ee2..102a813 100644
--- a/lib/lp/services/messages/model/message.py
+++ b/lib/lp/services/messages/model/message.py
@@ -161,7 +161,7 @@ class Message(SQLBase):
 
     @classmethod
     def chunks_text(cls, chunks):
-        bits = [six.text_type(chunk) for chunk in chunks if chunk.content]
+        bits = [str(chunk) for chunk in chunks if chunk.content]
         return '\n\n'.join(bits)
 
     # XXX flacoste 2006-09-08: Bogus attribute only present so that
@@ -358,7 +358,7 @@ class MessageSet:
             decoded = word if charset is None else self.decode(word, charset)
             re_encoded_bits.append((decoded.encode('utf-8'), 'utf-8'))
 
-        return six.text_type(make_header(re_encoded_bits))
+        return str(make_header(re_encoded_bits))
 
     def fromEmail(self, email_message, owner=None, filealias=None,
                   parsed_message=None, create_missing_persons=False,
@@ -573,7 +573,6 @@ class MessageSet:
         return message
 
 
-@six.python_2_unicode_compatible
 @implementer(IMessageChunk)
 class MessageChunk(SQLBase):
     """One part of a possibly multipart Message"""
@@ -634,7 +633,7 @@ class UserToUserEmail(Storm):
         :param message: the message being sent
         :type message: `email.message.Message`
         """
-        super(UserToUserEmail, self).__init__()
+        super().__init__()
         person_set = getUtility(IPersonSet)
         # Find the person who is sending this message.
         realname, address = parseaddr(message['from'])
@@ -660,7 +659,7 @@ class UserToUserEmail(Storm):
         self.sender = sender
         self.recipient = recipient
         self.message_id = six.ensure_text(message_id, 'ascii')
-        self.subject = six.text_type(make_header(decode_header(subject)))
+        self.subject = str(make_header(decode_header(subject)))
         # Add the object to the store of the sender.  Our StormMigrationGuide
         # recommends against this saying "Note that the constructor should not
         # usually add the object to a store -- leave that for a FooSet.new()
diff --git a/lib/lp/services/messages/tests/scenarios.py b/lib/lp/services/messages/tests/scenarios.py
index ab1f298..9faa16c 100644
--- a/lib/lp/services/messages/tests/scenarios.py
+++ b/lib/lp/services/messages/tests/scenarios.py
@@ -18,7 +18,7 @@ class MessageTypeScenariosMixin(WithScenarios):
         ]
 
     def setUp(self):
-        super(MessageTypeScenariosMixin, self).setUp()
+        super().setUp()
         self.person = self.factory.makePerson()
         login_person(self.person)
 
diff --git a/lib/lp/services/messages/tests/test_message.py b/lib/lp/services/messages/tests/test_message.py
index 9828dd3..397faf1 100644
--- a/lib/lp/services/messages/tests/test_message.py
+++ b/lib/lp/services/messages/tests/test_message.py
@@ -10,7 +10,6 @@ from email.utils import (
     make_msgid,
     )
 
-import six
 from testtools.matchers import (
     ContainsDict,
     EndsWith,
@@ -56,10 +55,10 @@ class TestMessageSet(TestCaseWithFactory):
     # characters according to Unicode but not according to ASCII, and this
     # would otherwise result in different test output between Python 2 and
     # 3.)
-    high_characters = b''.join(six.int2byte(c) for c in range(161, 256))
+    high_characters = b''.join(bytes((c,)) for c in range(161, 256))
 
     def setUp(self):
-        super(TestMessageSet, self).setUp()
+        super().setUp()
         # Testing behaviour, not permissions here.
         login('foo.bar@xxxxxxxxxxxxx')
 
@@ -172,8 +171,8 @@ class TestMessageSet(TestCaseWithFactory):
 
     def test_decode_utf8(self):
         """Test decode with a known encoding."""
-        result = MessageSet.decode(u'\u1234'.encode('utf-8'), 'utf-8')
-        self.assertEqual(u'\u1234', result)
+        result = MessageSet.decode('\u1234'.encode(), 'utf-8')
+        self.assertEqual('\u1234', result)
 
     def test_decode_macintosh(self):
         """Test decode with macintosh encoding."""
@@ -183,7 +182,7 @@ class TestMessageSet(TestCaseWithFactory):
     def test_decode_unknown_ascii(self):
         """Test decode with ascii characters in an unknown encoding."""
         result = MessageSet.decode(b'abcde', 'booga')
-        self.assertEqual(u'abcde', result)
+        self.assertEqual('abcde', result)
 
     def test_decode_unknown_high_characters(self):
         """Test decode with non-ascii characters in an unknown encoding."""
diff --git a/lib/lp/services/messaging/interfaces.py b/lib/lp/services/messaging/interfaces.py
index 06df94d..e7fd90c 100644
--- a/lib/lp/services/messaging/interfaces.py
+++ b/lib/lp/services/messaging/interfaces.py
@@ -37,7 +37,7 @@ class QueueEmpty(MessagingException):
 class IMessageSession(Interface):
 
     is_connected = Bool(
-        u"Whether the session is connected to the messaging system.")
+        "Whether the session is connected to the messaging system.")
 
     def connect():
         """Connect to the messaging system.
diff --git a/lib/lp/services/messaging/rabbit.py b/lib/lp/services/messaging/rabbit.py
index 47633be..a720760 100644
--- a/lib/lp/services/messaging/rabbit.py
+++ b/lib/lp/services/messaging/rabbit.py
@@ -13,7 +13,6 @@ __all__ = [
 from collections import deque
 from functools import partial
 import json
-import socket
 import sys
 import threading
 import time
@@ -86,7 +85,7 @@ class RabbitSession(threading.local):
     exchange = LAUNCHPAD_EXCHANGE
 
     def __init__(self):
-        super(RabbitSession, self).__init__()
+        super().__init__()
         self._connection = None
         self._deferred = deque()
         # Maintain sessions according to transaction boundaries. Keep a strong
@@ -115,7 +114,7 @@ class RabbitSession(threading.local):
         if self._connection is not None:
             try:
                 self._connection.close()
-            except socket.error:
+            except OSError:
                 # Socket error is fine; the connection is still closed.
                 pass
             finally:
@@ -186,7 +185,7 @@ class RabbitUnreliableSession(RabbitSession):
         other errors but files an oops report for these.
         """
         try:
-            super(RabbitUnreliableSession, self).finish()
+            super().finish()
         except self.suppressed_errors:
             pass
         except Exception:
@@ -223,7 +222,7 @@ class RabbitRoutingKey(RabbitMessageBase):
     """A RabbitMQ data origination point."""
 
     def __init__(self, session, routing_key):
-        super(RabbitRoutingKey, self).__init__(session)
+        super().__init__(session)
         self.key = routing_key
 
     def associateConsumer(self, consumer):
@@ -259,7 +258,7 @@ class RabbitQueue(RabbitMessageBase):
     """A RabbitMQ Queue."""
 
     def __init__(self, session, name):
-        super(RabbitQueue, self).__init__(session)
+        super().__init__(session)
         self.name = name
 
     def receive(self, timeout=0.0):
diff --git a/lib/lp/services/messaging/tests/test_rabbit.py b/lib/lp/services/messaging/tests/test_rabbit.py
index 8666638..c9e6449 100644
--- a/lib/lp/services/messaging/tests/test_rabbit.py
+++ b/lib/lp/services/messaging/tests/test_rabbit.py
@@ -5,9 +5,7 @@
 
 from functools import partial
 from itertools import count
-import socket
 
-import six
 from testtools.testcase import ExpectedException
 import transaction
 from transaction._transaction import Status as TransactionStatus
@@ -93,7 +91,7 @@ class RabbitTestCase(TestCase):
     layer = RabbitMQLayer
 
     def tearDown(self):
-        super(RabbitTestCase, self).tearDown()
+        super().tearDown()
         global_session.reset()
         global_unreliable_session.reset()
 
@@ -133,7 +131,7 @@ class TestRabbitSession(RabbitTestCase):
 
         def new_close(*args, **kwargs):
             old_close(*args, **kwargs)
-            raise socket.error
+            raise OSError
 
         with monkey_patch(session._connection, close=new_close):
             session.disconnect()
@@ -217,7 +215,7 @@ class TestRabbitUnreliableSession(TestRabbitSession):
     layer = RabbitMQLayer
 
     def setUp(self):
-        super(TestRabbitUnreliableSession, self).setUp()
+        super().setUp()
         self.prev_oops = self.getOops()
 
     def getOops(self):
@@ -396,7 +394,7 @@ class TestRabbit(RabbitTestCase):
             return set()
         else:
             return {
-                sync.session for sync in six.itervalues(syncs_set.data)
+                sync.session for sync in syncs_set.data.values()
                 if isinstance(sync, RabbitSessionTransactionSync)}
 
     def test_global_session(self):