← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad-buildd:pyupgrade into launchpad-buildd:master

 

Colin Watson has proposed merging ~cjwatson/launchpad-buildd:pyupgrade into launchpad-buildd:master.

Commit message:
Apply pyupgrade

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad-buildd/+git/launchpad-buildd/+merge/416604

We require Python >= 3.6 now.

(In fact, we can probably switch to `--py38-plus`, but `pyupgrade` doesn't apply any additional changes when doing that, and it's still just about possible that we might want to fall back to bionic for some reason, so I decided to be a little conservative there.)
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad-buildd:pyupgrade into launchpad-buildd:master.
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 8970c21..7f5eb00 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -11,3 +11,8 @@ repos:
     rev: 4.0.1
     hooks:
     -   id: flake8
+-   repo: https://github.com/asottile/pyupgrade
+    rev: v2.31.0
+    hooks:
+    -   id: pyupgrade
+        args: [--py36-plus]
diff --git a/bin/buildrecipe b/bin/buildrecipe
index 5dcab4a..9094490 100755
--- a/bin/buildrecipe
+++ b/bin/buildrecipe
@@ -4,11 +4,6 @@
 
 """A script that builds a package from a recipe and a chroot."""
 
-from __future__ import print_function
-
-__metaclass__ = type
-
-import io
 from optparse import OptionParser
 import os
 import pwd
@@ -157,8 +152,8 @@ class RecipeBuilder:
         retcode = call_report(cmd, env=env)
         if retcode != 0:
             return retcode
-        (source,) = [name for name in os.listdir(self.tree_path)
-                     if os.path.isdir(os.path.join(self.tree_path, name))]
+        (source,) = (name for name in os.listdir(self.tree_path)
+                     if os.path.isdir(os.path.join(self.tree_path, name)))
         self.source_dir_relative = os.path.join(
             self.work_dir_relative, 'tree', source)
         return retcode
@@ -167,8 +162,8 @@ class RecipeBuilder:
         source_dir = os.path.join(
             self.chroot_path, self.source_dir_relative.lstrip('/'))
         changelog = os.path.join(source_dir, 'debian/changelog')
-        return io.open(
-            changelog, 'r', errors='replace').readline().split(' ')[0]
+        return open(
+            changelog, errors='replace').readline().split(' ')[0]
 
     def getSourceControl(self):
         """Return the parsed source control stanza from the source tree."""
@@ -203,7 +198,7 @@ class RecipeBuilder:
                     "Build-Conflicts", "Build-Conflicts-Indep",
                     ):
                 if field in control:
-                    print("%s: %s" % (field, control[field]), file=dummy_dsc)
+                    print(f"{field}: {control[field]}", file=dummy_dsc)
             print(file=dummy_dsc)
 
     def runAptFtparchive(self):
diff --git a/bin/check-implicit-pointer-functions b/bin/check-implicit-pointer-functions
index c42d8a6..a8fcd95 100755
--- a/bin/check-implicit-pointer-functions
+++ b/bin/check-implicit-pointer-functions
@@ -5,8 +5,6 @@
 
 """Scan for compiler warnings that are likely to cause 64-bit problems."""
 
-from __future__ import print_function
-
 from argparse import ArgumentParser
 import sys
 
diff --git a/bin/in-target b/bin/in-target
index da0303d..664936d 100755
--- a/bin/in-target
+++ b/bin/in-target
@@ -5,10 +5,6 @@
 
 """Run an operation in the target."""
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 import sys
 
 from lpbuildd.target.cli import (
diff --git a/bin/lpbuildd-git-proxy b/bin/lpbuildd-git-proxy
index f1ad451..b700690 100755
--- a/bin/lpbuildd-git-proxy
+++ b/bin/lpbuildd-git-proxy
@@ -14,12 +14,12 @@ from urllib.parse import urlparse
 
 def main():
     proxy_url = urlparse(os.environ["http_proxy"])
-    proxy_arg = "PROXY:%s:%s:%s" % (
+    proxy_arg = "PROXY:{}:{}:{}".format(
         proxy_url.hostname, sys.argv[1], sys.argv[2])
     if proxy_url.port:
         proxy_arg += ",proxyport=%s" % proxy_url.port
     if proxy_url.username:
-        proxy_arg += ",proxyauth=%s:%s" % (
+        proxy_arg += ",proxyauth={}:{}".format(
             proxy_url.username, proxy_url.password)
     os.execvp("socat", ["socat", "STDIO", proxy_arg])
 
diff --git a/bin/test_buildd_generatetranslationtemplates b/bin/test_buildd_generatetranslationtemplates
index 2282f1d..e981ef0 100755
--- a/bin/test_buildd_generatetranslationtemplates
+++ b/bin/test_buildd_generatetranslationtemplates
@@ -5,8 +5,6 @@
 # Test script for manual use only.  Exercises the
 # TranslationTemplatesBuildManager through XMLRPC.
 
-from __future__ import print_function
-
 import sys
 
 from six.moves.xmlrpc_client import ServerProxy
diff --git a/bin/test_buildd_recipe b/bin/test_buildd_recipe
index d4f5ed6..89803e6 100755
--- a/bin/test_buildd_recipe
+++ b/bin/test_buildd_recipe
@@ -5,8 +5,6 @@
 # This is a script to do end-to-end testing of the buildd with a bzr-builder
 # recipe, without involving the BuilderBehaviour.
 
-from __future__ import print_function
-
 import sys
 
 from six.moves.xmlrpc_client import ServerProxy
@@ -22,7 +20,7 @@ def deb_line(host, suites):
     prefix = 'deb http://'
     if apt_cacher_ng_host is not None:
         prefix += '%s:3142/' % apt_cacher_ng_host
-    return '%s%s %s %s' % (prefix, host, distroseries_name, suites)
+    return f'{prefix}{host} {distroseries_name} {suites}'
 
 
 proxy = ServerProxy('http://localhost:8221/rpc')
@@ -35,7 +33,7 @@ if status[0] != 'BuilderStatus.IDLE':
     sys.exit(1)
 print(proxy.build(
     '1-2', 'sourcepackagerecipe', '1ef177161c3cb073e66bf1550931c6fbaa0a94b0',
-    {}, {'author_name': u'Steve\u1234',
+    {}, {'author_name': 'Steve\u1234',
          'author_email': 'stevea@xxxxxxxxxxx',
          'suite': distroseries_name,
          'distroseries_name': distroseries_name,
diff --git a/buildd-genconfig b/buildd-genconfig
index 8195ef7..be633ed 100755
--- a/buildd-genconfig
+++ b/buildd-genconfig
@@ -3,8 +3,6 @@
 # Copyright 2009 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
 from optparse import OptionParser
 import os
 
@@ -49,7 +47,7 @@ parser.add_option(
 
 (options, args) = parser.parse_args()
 
-template = open(options.TEMPLATE, "r").read()
+template = open(options.TEMPLATE).read()
 
 replacements = {
     "@NAME@": options.NAME,
diff --git a/charm/reactive/launchpad-buildd.py b/charm/reactive/launchpad-buildd.py
index 5a72e78..9d5e138 100644
--- a/charm/reactive/launchpad-buildd.py
+++ b/charm/reactive/launchpad-buildd.py
@@ -1,8 +1,6 @@
 # Copyright 2016 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
 import os.path
 import re
 import shutil
@@ -49,7 +47,7 @@ def install_packages():
         # Install from resources.
         changed = False
         for package, resource_path in zip(packages, resource_paths):
-            local_path = os.path.join(cache_dir, "{}.deb".format(package))
+            local_path = os.path.join(cache_dir, f"{package}.deb")
             to_install.append((local_path, resource_path))
             if host.file_hash(local_path) != host.file_hash(resource_path):
                 changed = True
diff --git a/debian/changelog b/debian/changelog
index 8235c76..2dc69b1 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -3,6 +3,7 @@ launchpad-buildd (210) UNRELEASED; urgency=medium
   * Stop building with dpkg-deb -Zgzip; we no longer need to install on
     lucid.
   * Make more loop device nodes available in LXD containers (LP: #1963706).
+  * Drop pre-Python-3.6 code using pyupgrade.
 
  -- Colin Watson <cjwatson@xxxxxxxxxx>  Mon, 28 Feb 2022 11:27:20 +0000
 
diff --git a/debian/control b/debian/control
index fa626c2..6bbe218 100644
--- a/debian/control
+++ b/debian/control
@@ -13,7 +13,7 @@ Build-Depends: apt-utils,
                dh-python,
                git,
                intltool,
-               python3 (>= 3.3),
+               python3 (>= 3.6),
                python3-apt,
                python3-debian,
                python3-fixtures,
diff --git a/debian/upgrade-config b/debian/upgrade-config
index db10239..9594a2e 100755
--- a/debian/upgrade-config
+++ b/debian/upgrade-config
@@ -5,8 +5,6 @@
 
 """Upgrade a launchpad-buildd configuration file."""
 
-from __future__ import print_function
-
 try:
     from configparser import (
         ConfigParser as SafeConfigParser,
@@ -37,7 +35,7 @@ bin_path = "/usr/share/launchpad-buildd/slavebin"
 def upgrade_to_12():
     print("Upgrading %s to version 12" % conf_file)
     subprocess.call(["mv", conf_file, conf_file+"-prev12~"])
-    in_file = open(conf_file+"-prev12~", "r")
+    in_file = open(conf_file+"-prev12~")
     out_file = open(conf_file, "w")
     for line in in_file:
         if line.startswith("[debianmanager]"):
@@ -52,7 +50,7 @@ def upgrade_to_12():
 def upgrade_to_34():
     print("Upgrading %s to version 34" % conf_file)
     subprocess.call(["mv", conf_file, conf_file+"-prev34~"])
-    in_file = open(conf_file+"-prev34~", "r")
+    in_file = open(conf_file+"-prev34~")
     out_file = open(conf_file, "w")
     for line in in_file:
         if line.startswith("[debianmanager]"):
@@ -65,7 +63,7 @@ def upgrade_to_34():
 def upgrade_to_39():
     print("Upgrading %s to version 39" % conf_file)
     subprocess.call(["mv", conf_file, conf_file+"-prev39~"])
-    in_file = open(conf_file+"-prev39~", "r")
+    in_file = open(conf_file+"-prev39~")
     out_file = open(conf_file, "w")
     for line in in_file:
         if line.startswith("sbuildargs"):
@@ -80,7 +78,7 @@ def upgrade_to_39():
 def upgrade_to_57():
     print("Upgrading %s to version 57" % conf_file)
     subprocess.call(["mv", conf_file, conf_file+"-prev57~"])
-    in_file = open(conf_file+"-prev57~", "r")
+    in_file = open(conf_file+"-prev57~")
     out_file = open(conf_file, "w")
     # We want to move all the sbuild lines to a new
     # 'binarypackagemanager' section at the end.
@@ -100,7 +98,7 @@ def upgrade_to_57():
 def upgrade_to_58():
     print("Upgrading %s to version 58" % conf_file)
     subprocess.call(["mv", conf_file, conf_file+"-prev58~"])
-    in_file = open(conf_file+"-prev58~", "r")
+    in_file = open(conf_file+"-prev58~")
     out_file = open(conf_file, "w")
     out_file.write(in_file.read())
     out_file.write(
@@ -111,7 +109,7 @@ def upgrade_to_58():
 def upgrade_to_59():
     print("Upgrading %s to version 59" % conf_file)
     subprocess.call(["mv", conf_file, conf_file+"-prev59~"])
-    in_file = open(conf_file+"-prev59~", "r")
+    in_file = open(conf_file+"-prev59~")
     out_file = open(conf_file, "w")
     out_file.write(in_file.read())
     out_file.write(
@@ -123,7 +121,7 @@ def upgrade_to_59():
 def upgrade_to_63():
     print("Upgrading %s to version 63" % conf_file)
     subprocess.call(["mv", conf_file, conf_file+"-prev63~"])
-    in_file = open(conf_file+"-prev63~", "r")
+    in_file = open(conf_file+"-prev63~")
     out_file = open(conf_file, "w")
     for line in in_file:
         if not line.startswith('ogrepath'):
@@ -133,7 +131,7 @@ def upgrade_to_63():
 def upgrade_to_110():
     print("Upgrading %s to version 110" % conf_file)
     subprocess.call(["mv", conf_file, conf_file+"-prev110~"])
-    in_file = open(conf_file+"-prev110~", "r")
+    in_file = open(conf_file+"-prev110~")
     out_file = open(conf_file, "w")
     for line in in_file:
         if line.startswith("[allmanagers]"):
@@ -147,7 +145,7 @@ def upgrade_to_115():
     print("Upgrading %s to version 115" % conf_file)
     subprocess.call(["mv", conf_file, conf_file+"-prev115~"])
     in_allmanagers = False
-    in_file = open(conf_file+"-prev115~", "r")
+    in_file = open(conf_file+"-prev115~")
     out_file = open(conf_file, "w")
     for line in in_file:
         if line.startswith("[allmanagers]"):
@@ -165,7 +163,7 @@ def upgrade_to_115():
 def upgrade_to_120():
     print("Upgrading %s to version 120" % conf_file)
     subprocess.call(["mv", conf_file, conf_file+"-prev120~"])
-    in_file = open(conf_file+"-prev120~", "r")
+    in_file = open(conf_file+"-prev120~")
     out_file = open(conf_file, "w")
     out_file.write(in_file.read())
     out_file.write(
@@ -178,7 +176,7 @@ def upgrade_to_120():
 def upgrade_to_126():
     print("Upgrading %s to version 126" % conf_file)
     subprocess.call(["mv", conf_file, conf_file+"-prev126~"])
-    in_file = open(conf_file+"-prev126~", "r")
+    in_file = open(conf_file+"-prev126~")
     out_file = open(conf_file, "w")
     archive_ubuntu = " --archive=ubuntu"
     for line in in_file:
@@ -194,7 +192,7 @@ def upgrade_to_127():
     print("Upgrading %s to version 127" % conf_file)
     os.rename(conf_file, conf_file + "-prev127~")
 
-    in_file = open(conf_file + "-prev127~", "r")
+    in_file = open(conf_file + "-prev127~")
     out_file = open(conf_file, "w")
     obsolete_prefixes = [
         '[allmanagers]', '[debianmanager]', '[binarypackagemanager]',
@@ -228,7 +226,7 @@ def upgrade_to_162():
     print("Upgrading %s to version 162" % conf_file)
     os.rename(conf_file, conf_file + "-prev162~")
 
-    with open(conf_file + "-prev162~", "r") as in_file:
+    with open(conf_file + "-prev162~") as in_file:
         with open(conf_file, "w") as out_file:
             out_file.write(in_file.read())
             out_file.write(
@@ -240,7 +238,7 @@ def upgrade_to_190():
     print("Upgrading %s to version 190" % conf_file)
     os.rename(conf_file, conf_file + "-prev190~")
 
-    with open(conf_file + "-prev190~", "r") as in_file:
+    with open(conf_file + "-prev190~") as in_file:
         with open(conf_file, "w") as out_file:
             for line in in_file:
                 if line.strip() == "[slave]":
@@ -261,7 +259,7 @@ def upgrade_to_200():
         proxyport = None
 
     os.rename(conf_file, conf_file + "-prev200~")
-    with open(conf_file + "-prev200~", "r") as in_file:
+    with open(conf_file + "-prev200~") as in_file:
         with open(conf_file, "w") as out_file:
             in_builder = False
             in_snapmanager = False
diff --git a/docs/how-to/developing.rst b/docs/how-to/developing.rst
index 895d0a7..4a9ed52 100644
--- a/docs/how-to/developing.rst
+++ b/docs/how-to/developing.rst
@@ -23,8 +23,7 @@ Then, inside the container, install the necessary dependencies:
         sudo apt-get update
         cat system-dependencies.txt | sudo xargs apt-get install -y
 
-This should be enough for you to be able to run `make check`, which runs the
-test suite both in python2 and python3.
+This should be enough for you to be able to run `make check`.
 
 More information on how to integrate it with Launchpad can be found here:
 https://dev.launchpad.net/Soyuz/HowToDevelopWithBuildd
diff --git a/lpbuildd/binarypackage.py b/lpbuildd/binarypackage.py
index 1bbf831..d0429da 100644
--- a/lpbuildd/binarypackage.py
+++ b/lpbuildd/binarypackage.py
@@ -1,8 +1,6 @@
 # Copyright 2009-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import absolute_import, print_function
-
 from collections import defaultdict
 import os
 import re
@@ -126,8 +124,7 @@ class BinaryPackageBuildManager(DebianBuildManager):
         self.arch_indep = extra_args.get('arch_indep', False)
         self.build_debug_symbols = extra_args.get('build_debug_symbols', False)
 
-        super(BinaryPackageBuildManager, self).initiate(
-            files, chroot, extra_args)
+        super().initiate(files, chroot, extra_args)
 
     def doRunBuild(self):
         """Run the sbuild process to build the package."""
@@ -396,7 +393,7 @@ class BinaryPackageBuildManager(DebianBuildManager):
             with open(os.path.join(self._cachepath, "buildlog"), "rb") as log:
                 try:
                     log.seek(-4096, os.SEEK_END)
-                except IOError:
+                except OSError:
                     pass
                 tail = log.read(4096).decode("UTF-8", "replace")
             if re.search(r"^Fail-Stage: install-deps$", tail, re.M):
diff --git a/lpbuildd/builder.py b/lpbuildd/builder.py
index 1257fce..e0c93a7 100644
--- a/lpbuildd/builder.py
+++ b/lpbuildd/builder.py
@@ -6,10 +6,6 @@
 
 # The basic builder implementation.
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 from functools import partial
 import hashlib
 import json
@@ -20,7 +16,6 @@ import sys
 import tempfile
 
 import apt
-import six
 from six.moves.urllib.request import (
     build_opener,
     HTTPBasicAuthHandler,
@@ -38,7 +33,7 @@ from lpbuildd.target.backend import make_backend
 from lpbuildd.util import shell_escape
 
 
-devnull = open("/dev/null", "r")
+devnull = open("/dev/null")
 
 
 def _sanitizeURLs(bytes_seq):
@@ -125,7 +120,7 @@ def get_build_path(home, build_id, *extra):
     return os.path.join(home, "build-" + build_id, *extra)
 
 
-class BuildManager(object):
+class BuildManager:
     """Build manager abstract parent."""
 
     backend_name = "chroot"
@@ -166,7 +161,7 @@ class BuildManager(object):
         text_args = [
             arg.decode("UTF-8", "replace") if isinstance(arg, bytes) else arg
             for arg in args[1:]]
-        self._builder.log("RUN: %s %s\n" % (
+        self._builder.log("RUN: {} {}\n".format(
             command, " ".join(shell_escape(arg) for arg in text_args)))
         childfds = {
             0: devnull.fileno() if stdin is None else "w",
@@ -286,7 +281,7 @@ class BuildManager(object):
         try:
             with open(self.status_path) as status_file:
                 return json.load(status_file)
-        except IOError:
+        except OSError:
             pass
         except Exception as e:
             print(
@@ -399,7 +394,7 @@ class BuildStatus:
     ABORTED = "BuildStatus.ABORTED"
 
 
-class Builder(object):
+class Builder:
     """The core of a builder."""
 
     def __init__(self, config):
@@ -453,7 +448,7 @@ class Builder(object):
         if url is not None:
             extra_info = 'Cache'
             if not os.path.exists(cachefile):
-                self.log('Fetching %s by url %s' % (sha1sum, url))
+                self.log(f'Fetching {sha1sum} by url {url}')
                 if username:
                     opener = self.setupAuthHandler(
                         url, username, password).open
@@ -554,23 +549,11 @@ class Builder(object):
             self._log.write(data_bytes)
             self._log.flush()
         data_text = (
-            data if isinstance(data, six.text_type)
+            data if isinstance(data, str)
             else data.decode("UTF-8", "replace"))
-        if six.PY3:
-            data_str = data_text
-        else:
-            # Twisted's logger doesn't handle non-ASCII text very reliably
-            # on Python 2.  This is just for debugging, so replace non-ASCII
-            # characters with the corresponding \u escapes.  We need to go
-            # to ridiculous lengths here to avoid (e.g.) replacing newlines
-            # with "\n".
-            data_str = re.sub(
-                r"([^\x00-\x7f])",
-                lambda match: "\\u%04x" % ord(match.group(0)),
-                data_text).encode("UTF-8")
-        if data_str.endswith("\n"):
-            data_str = data_str[:-1]
-        log.msg("Build log: " + data_str)
+        if data_text.endswith("\n"):
+            data_text = data_text[:-1]
+        log.msg("Build log: " + data_text)
 
     def getLogTail(self):
         """Return the tail of the log.
@@ -595,7 +578,7 @@ class Builder(object):
         try:
             try:
                 rlog = open(self.cachePath("buildlog"), "rb")
-            except IOError:
+            except OSError:
                 ret = b""
             else:
                 # We rely on good OS practices that keep the file handler
@@ -852,26 +835,26 @@ class XMLRPCBuilder(xmlrpc.XMLRPC):
         """
         # check requested manager
         if managertag not in self._managers:
-            extra_info = "%s not in %r" % (managertag, list(self._managers))
+            extra_info = f"{managertag} not in {list(self._managers)!r}"
             return (BuilderStatus.UNKNOWNBUILDER, extra_info)
         # check requested chroot availability
         chroot_present, info = self.builder.ensurePresent(chrootsum)
         if not chroot_present:
-            extra_info = """CHROOTSUM -> %s
+            extra_info = """CHROOTSUM -> {}
             ***** INFO *****
-            %s
+            {}
             ****************
-            """ % (chrootsum, info)
+            """.format(chrootsum, info)
             return (BuilderStatus.UNKNOWNSUM, extra_info)
         # check requested files availability
         for filesum in filemap.values():
             file_present, info = self.builder.ensurePresent(filesum)
             if not file_present:
-                extra_info = """FILESUM -> %s
+                extra_info = """FILESUM -> {}
                 ***** INFO *****
-                %s
+                {}
                 ****************
-                """ % (filesum, info)
+                """.format(filesum, info)
                 return (BuilderStatus.UNKNOWNSUM, extra_info)
         # check buildid sanity
         if buildid is None or buildid == "" or buildid == 0:
diff --git a/lpbuildd/charm.py b/lpbuildd/charm.py
index 520b97c..11c7a5a 100644
--- a/lpbuildd/charm.py
+++ b/lpbuildd/charm.py
@@ -1,10 +1,6 @@
 # Copyright 2021 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 import os
 
 from lpbuildd.debian import (
@@ -45,7 +41,7 @@ class CharmBuildManager(BuildManagerProxyMixin, DebianBuildManager):
         self.revocation_endpoint = extra_args.get("revocation_endpoint")
         self.proxy_service = None
 
-        super(CharmBuildManager, self).initiate(files, chroot, extra_args)
+        super().initiate(files, chroot, extra_args)
 
     def doRunBuild(self):
         """Run the process to build the charm."""
@@ -54,7 +50,7 @@ class CharmBuildManager(BuildManagerProxyMixin, DebianBuildManager):
         if self.revocation_endpoint:
             args.extend(["--revocation-endpoint", self.revocation_endpoint])
         for snap, channel in sorted(self.channels.items()):
-            args.extend(["--channel", "%s=%s" % (snap, channel)])
+            args.extend(["--channel", f"{snap}={channel}"])
         if self.branch is not None:
             args.extend(["--branch", self.branch])
         if self.git_repository is not None:
diff --git a/lpbuildd/check_implicit_pointer_functions.py b/lpbuildd/check_implicit_pointer_functions.py
index 78df89f..67e3b45 100755
--- a/lpbuildd/check_implicit_pointer_functions.py
+++ b/lpbuildd/check_implicit_pointer_functions.py
@@ -32,8 +32,6 @@
 # interpreted as pointers.  Those are almost guaranteed to cause
 # crashes.
 
-from __future__ import print_function
-
 import re
 
 implicit_pattern = re.compile(
diff --git a/lpbuildd/ci.py b/lpbuildd/ci.py
index 3256782..b2afd4e 100644
--- a/lpbuildd/ci.py
+++ b/lpbuildd/ci.py
@@ -1,10 +1,6 @@
 # Copyright 2022 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 import os
 
 from six.moves.configparser import (
@@ -31,7 +27,7 @@ RESULT_FAILED = "FAILED"
 
 
 def _make_job_id(job_name, job_index):
-    return "%s:%s" % (job_name, job_index)
+    return f"{job_name}:{job_index}"
 
 
 class CIBuildState(DebianBuildState):
@@ -63,7 +59,7 @@ class CIBuildManager(BuildManagerProxyMixin, DebianBuildManager):
         self.proxy_service = None
         self.job_status = {}
 
-        super(CIBuildManager, self).initiate(files, chroot, extra_args)
+        super().initiate(files, chroot, extra_args)
 
     def doRunBuild(self):
         """Start running CI jobs."""
@@ -73,7 +69,7 @@ class CIBuildManager(BuildManagerProxyMixin, DebianBuildManager):
                 ["--revocation-endpoint", self.revocation_endpoint])
         args = list(self.proxy_args)
         for snap, channel in sorted(self.channels.items()):
-            args.extend(["--channel", "%s=%s" % (snap, channel)])
+            args.extend(["--channel", f"{snap}={channel}"])
         if self.branch is not None:
             args.extend(["--branch", self.branch])
         if self.git_repository is not None:
@@ -134,7 +130,7 @@ class CIBuildManager(BuildManagerProxyMixin, DebianBuildManager):
 
     @staticmethod
     def _makeJobID(job_name, job_index):
-        return "%s:%s" % (job_name, job_index)
+        return f"{job_name}:{job_index}"
 
     def runNextJob(self):
         """Run the next CI job."""
@@ -205,7 +201,7 @@ class CIBuildManager(BuildManagerProxyMixin, DebianBuildManager):
 
     def status(self):
         """See `BuildManager.status`."""
-        status = super(CIBuildManager, self).status()
+        status = super().status()
         status["jobs"] = dict(self.job_status)
         return status
 
diff --git a/lpbuildd/debian.py b/lpbuildd/debian.py
index da37eb1..5cb47e7 100644
--- a/lpbuildd/debian.py
+++ b/lpbuildd/debian.py
@@ -4,10 +4,7 @@
 # Authors: Daniel Silverstone <daniel.silverstone@xxxxxxxxxxxxx>
 #      and Adam Conrad <adam.conrad@xxxxxxxxxxxxx>
 
-__metaclass__ = type
-
 import base64
-import io
 import os
 import re
 import signal
@@ -123,7 +120,7 @@ class DebianBuildManager(BuildManager):
         path = self.getChangesFilename()
         self._builder.addWaitingFile(path)
 
-        with io.open(path, "r", errors="replace") as chfile:
+        with open(path, errors="replace") as chfile:
             for fn in self._parseChangesFile(chfile):
                 self._builder.addWaitingFile(
                     get_build_path(self.home, self._buildid, fn))
@@ -345,7 +342,7 @@ class DebianBuildManager(BuildManager):
 
     def abort(self):
         """See `BuildManager`."""
-        super(DebianBuildManager, self).abort()
+        super().abort()
         if self._iterator is not None:
             self._iterator.cancel()
             self._iterator = None
diff --git a/lpbuildd/livefs.py b/lpbuildd/livefs.py
index 6b59852..f627e76 100644
--- a/lpbuildd/livefs.py
+++ b/lpbuildd/livefs.py
@@ -1,8 +1,6 @@
 # Copyright 2013-2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 import os
 
 from six.moves.configparser import (
@@ -48,8 +46,7 @@ class LiveFilesystemBuildManager(DebianBuildManager):
         self.cohort_key = extra_args.get("cohort-key")
         self.debug = extra_args.get("debug", False)
 
-        super(LiveFilesystemBuildManager, self).initiate(
-            files, chroot, extra_args)
+        super().initiate(files, chroot, extra_args)
 
     def doRunBuild(self):
         """Run the process to build the live filesystem."""
diff --git a/lpbuildd/log.py b/lpbuildd/log.py
index c0197fc..cba0721 100644
--- a/lpbuildd/log.py
+++ b/lpbuildd/log.py
@@ -1,12 +1,6 @@
 # Copyright 2009-2015 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import (
-    absolute_import,
-    print_function,
-    unicode_literals,
-    )
-
 import signal
 import sys
 
diff --git a/lpbuildd/oci.py b/lpbuildd/oci.py
index c6e103c..fb1dc27 100644
--- a/lpbuildd/oci.py
+++ b/lpbuildd/oci.py
@@ -1,10 +1,6 @@
 # Copyright 2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 import gzip
 import hashlib
 import json
@@ -57,7 +53,7 @@ class OCIBuildManager(BuildManagerProxyMixin, DebianBuildManager):
         self.revocation_endpoint = extra_args.get("revocation_endpoint")
         self.proxy_service = None
 
-        super(OCIBuildManager, self).initiate(files, chroot, extra_args)
+        super().initiate(files, chroot, extra_args)
 
     def doRunBuild(self):
         """Run the process to build the OCI image."""
@@ -75,7 +71,7 @@ class OCIBuildManager(BuildManagerProxyMixin, DebianBuildManager):
             args.extend(["--build-file", self.build_file])
         if self.build_args:
             for k, v in self.build_args.items():
-                args.extend(["--build-arg", "%s=%s" % (k, v)])
+                args.extend(["--build-arg", f"{k}={v}"])
         if self.build_path is not None:
             args.extend(["--build-path", self.build_path])
         try:
@@ -123,21 +119,20 @@ class OCIBuildManager(BuildManagerProxyMixin, DebianBuildManager):
     def _gatherManifestSection(self, section, extract_path, sha_directory):
         config_file_path = os.path.join(extract_path, section["Config"])
         self._builder.addWaitingFile(config_file_path)
-        with open(config_file_path, 'r') as config_fp:
+        with open(config_file_path) as config_fp:
             config = json.load(config_fp)
         diff_ids = config["rootfs"]["diff_ids"]
         digest_diff_map = {}
         for diff_id, layer_id in zip(diff_ids, section['Layers']):
             layer_id = layer_id.split('/')[0]
             diff_file = os.path.join(sha_directory, diff_id.split(':')[1])
-            layer_path = os.path.join(
-                extract_path, "{}.tar.gz".format(layer_id))
+            layer_path = os.path.join(extract_path, f"{layer_id}.tar.gz")
             self._builder.addWaitingFile(layer_path)
             # If we have a mapping between diff and existing digest,
             # this means this layer has been pulled from a remote.
             # We should maintain the same digest to achieve layer reuse
             if os.path.exists(diff_file):
-                with open(diff_file, 'r') as diff_fp:
+                with open(diff_file) as diff_fp:
                     diff = json.load(diff_fp)
                     # We should be able to just take the first occurence,
                     # as that will be the 'most parent' image
@@ -165,7 +160,7 @@ class OCIBuildManager(BuildManagerProxyMixin, DebianBuildManager):
                 get_output=True, return_process=True)
             tar = tarfile.open(fileobj=proc.stdout, mode="r|")
         except Exception as e:
-            self._builder.log("Unable to save image: {}".format(e))
+            self._builder.log(f"Unable to save image: {e}")
             raise
 
         current_dir = ''
@@ -174,7 +169,7 @@ class OCIBuildManager(BuildManagerProxyMixin, DebianBuildManager):
         try:
             # The tarfile is a stream and must be processed in order
             for file in tar:
-                self._builder.log("Processing tar file: {}".format(file.name))
+                self._builder.log(f"Processing tar file: {file.name}")
                 # Directories are just nodes, you can't extract the children
                 # directly, so keep track of what dir we're in.
                 if file.isdir():
@@ -203,8 +198,7 @@ class OCIBuildManager(BuildManagerProxyMixin, DebianBuildManager):
                     # (directory_name.tar.gz/contents) otherwise we will endup
                     # with multiple gzips with the same name "layer.tar.gz".
                     fileobj = tar.extractfile(file)
-                    name = os.path.join(extract_path,
-                                        '{}.tar.gz'.format(current_dir))
+                    name = os.path.join(extract_path, f'{current_dir}.tar.gz')
                     with gzip.GzipFile(name, 'wb') as gzip_layer:
                         byte = fileobj.read(1)
                         while len(byte) > 0:
@@ -218,7 +212,7 @@ class OCIBuildManager(BuildManagerProxyMixin, DebianBuildManager):
                     # If it's not in a directory, we need that
                     tar.extract(file, extract_path)
         except Exception as e:
-            self._builder.log("Tar file processing failed: {}".format(e))
+            self._builder.log(f"Tar file processing failed: {e}")
             raise
         finally:
             if gzip_layer is not None:
@@ -261,7 +255,7 @@ class OCIBuildManager(BuildManagerProxyMixin, DebianBuildManager):
                     os.path.join(sha_directory, file)
                 )
         else:
-            self._builder.log("No metadata directory at {}".format(sha_path))
+            self._builder.log(f"No metadata directory at {sha_path}")
 
         # Parse the manifest for the other files we need
         manifest_path = os.path.join(extract_path, 'manifest.json')
@@ -280,5 +274,5 @@ class OCIBuildManager(BuildManagerProxyMixin, DebianBuildManager):
                 json.dump(digest_maps, digest_map_fp)
             self._builder.addWaitingFile(digest_map_file)
         except Exception as e:
-            self._builder.log("Failed to parse manifest: {}".format(e))
+            self._builder.log(f"Failed to parse manifest: {e}")
             raise
diff --git a/lpbuildd/pottery/intltool.py b/lpbuildd/pottery/intltool.py
index 1755cab..975afee 100644
--- a/lpbuildd/pottery/intltool.py
+++ b/lpbuildd/pottery/intltool.py
@@ -3,7 +3,6 @@
 
 """Functions to build PO templates on the builder."""
 
-__metaclass__ = type
 __all__ = [
     'check_potfiles_in',
     'generate_pot',
@@ -278,7 +277,7 @@ class ConfigFile:
                 ]
 
 
-class Substitution(object):
+class Substitution:
     """Find and replace substitutions.
 
     Variable texts may contain other variables which should be substituted
diff --git a/lpbuildd/pottery/tests/test_intltool.py b/lpbuildd/pottery/tests/test_intltool.py
index dd1d47d..b540c1d 100644
--- a/lpbuildd/pottery/tests/test_intltool.py
+++ b/lpbuildd/pottery/tests/test_intltool.py
@@ -1,8 +1,6 @@
 # Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 import errno
 import os
 import tarfile
diff --git a/lpbuildd/proxy.py b/lpbuildd/proxy.py
index de6e3c7..69381e1 100644
--- a/lpbuildd/proxy.py
+++ b/lpbuildd/proxy.py
@@ -1,10 +1,6 @@
 # Copyright 2015-2021 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 import base64
 import io
 
@@ -203,8 +199,8 @@ class BuilderProxyFactory(http.HTTPFactory):
         referrer = http._escape(request.getHeader(b"referer") or b"-")
         agent = http._escape(request.getHeader(b"user-agent") or b"-")
         line = (
-            u'%(timestamp)s "%(method)s %(uri)s %(protocol)s" '
-            u'%(code)d %(length)s "%(referrer)s" "%(agent)s"\n' % {
+            '%(timestamp)s "%(method)s %(uri)s %(protocol)s" '
+            '%(code)d %(length)s "%(referrer)s" "%(agent)s"\n' % {
                 'timestamp': self._logDateTime,
                 'method': http._escape(request.method),
                 'uri': http._escape(request.uri),
@@ -232,7 +228,7 @@ class BuildManagerProxyMixin:
             proxy_host = self.backend.ipv4_network.ip
         else:
             proxy_host = "localhost"
-        return ["--proxy-url", "http://{}:{}/".format(proxy_host, proxy_port)]
+        return ["--proxy-url", f"http://{proxy_host}:{proxy_port}/";]
 
     def stopProxy(self):
         """Stop the local builder proxy, if necessary."""
@@ -247,7 +243,7 @@ class BuildManagerProxyMixin:
             return
         self._builder.log("Revoking proxy token...\n")
         url = urlparse(self.proxy_url)
-        auth = "{}:{}".format(url.username, url.password)
+        auth = f"{url.username}:{url.password}"
         headers = {
             "Authorization": "Basic {}".format(
                 base64.b64encode(auth.encode('utf-8')).decode('utf-8'))
@@ -258,4 +254,4 @@ class BuildManagerProxyMixin:
             urlopen(req)
         except (HTTPError, URLError) as e:
             self._builder.log(
-                "Unable to revoke token for %s: %s" % (url.username, e))
+                f"Unable to revoke token for {url.username}: {e}")
diff --git a/lpbuildd/snap.py b/lpbuildd/snap.py
index 0c42f12..8d1d2c6 100644
--- a/lpbuildd/snap.py
+++ b/lpbuildd/snap.py
@@ -1,10 +1,6 @@
 # Copyright 2015-2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 import os
 
 from six.moves.configparser import (
@@ -56,13 +52,13 @@ class SnapBuildManager(BuildManagerProxyMixin, DebianBuildManager):
         self.private = extra_args.get("private", False)
         self.proxy_service = None
 
-        super(SnapBuildManager, self).initiate(files, chroot, extra_args)
+        super().initiate(files, chroot, extra_args)
 
     def doRunBuild(self):
         """Run the process to build the snap."""
         args = []
         for snap, channel in sorted(self.channels.items()):
-            args.extend(["--channel", "%s=%s" % (snap, channel)])
+            args.extend(["--channel", f"{snap}={channel}"])
         if self.build_request_id:
             args.extend(["--build-request-id", str(self.build_request_id)])
         if self.build_request_timestamp:
diff --git a/lpbuildd/sourcepackagerecipe.py b/lpbuildd/sourcepackagerecipe.py
index af818b0..15d5d65 100644
--- a/lpbuildd/sourcepackagerecipe.py
+++ b/lpbuildd/sourcepackagerecipe.py
@@ -80,8 +80,7 @@ class SourcePackageRecipeBuildManager(DebianBuildManager):
         self.distroseries_name = extra_args['distroseries_name']
         self.git = extra_args.get('git', False)
 
-        super(SourcePackageRecipeBuildManager, self).initiate(
-            files, chroot, extra_args)
+        super().initiate(files, chroot, extra_args)
 
     def doRunBuild(self):
         """Run the build process to build the source package."""
diff --git a/lpbuildd/target/apt.py b/lpbuildd/target/apt.py
index 09d51dd..59a3056 100644
--- a/lpbuildd/target/apt.py
+++ b/lpbuildd/target/apt.py
@@ -1,10 +1,6 @@
 # Copyright 2009-2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 import logging
 import os
 import subprocess
@@ -25,7 +21,7 @@ class OverrideSourcesList(Operation):
 
     @classmethod
     def add_arguments(cls, parser):
-        super(OverrideSourcesList, cls).add_arguments(parser)
+        super().add_arguments(parser)
         parser.add_argument(
             "--apt-proxy-url", metavar="URL", help="APT proxy URL")
         parser.add_argument(
@@ -77,7 +73,7 @@ class OverrideSourcesList(Operation):
                 os.fchmod(preferences.fileno(), 0o644)
                 self.backend.copy_in(
                     preferences.name,
-                    "/etc/apt/preferences.d/{}.pref".format(pocket))
+                    f"/etc/apt/preferences.d/{pocket}.pref")
         return 0
 
 
@@ -86,7 +82,7 @@ class AddTrustedKeys(Operation):
     description = "Write out new trusted keys."
 
     def __init__(self, args, parser):
-        super(AddTrustedKeys, self).__init__(args, parser)
+        super().__init__(args, parser)
         self.input_file = sys.stdin
 
     def run(self):
@@ -103,7 +99,7 @@ class Update(Operation):
 
     def run(self):
         logger.info("Updating target for build %s", self.args.build_id)
-        with open("/dev/null", "r") as devnull:
+        with open("/dev/null") as devnull:
             env = {
                 "LANG": "C",
                 "DEBIAN_FRONTEND": "noninteractive",
diff --git a/lpbuildd/target/backend.py b/lpbuildd/target/backend.py
index 244c38b..69746bb 100644
--- a/lpbuildd/target/backend.py
+++ b/lpbuildd/target/backend.py
@@ -1,10 +1,6 @@
 # Copyright 2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 import os.path
 import subprocess
 
diff --git a/lpbuildd/target/build_charm.py b/lpbuildd/target/build_charm.py
index c6c370f..347af56 100644
--- a/lpbuildd/target/build_charm.py
+++ b/lpbuildd/target/build_charm.py
@@ -1,10 +1,6 @@
 # Copyright 2021 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 import logging
 import os
 
@@ -32,7 +28,7 @@ class BuildCharm(BuilderProxyOperationMixin, VCSOperationMixin,
 
     @classmethod
     def add_arguments(cls, parser):
-        super(BuildCharm, cls).add_arguments(parser)
+        super().add_arguments(parser)
         parser.add_argument(
             "--channel", action=SnapChannelsAction, metavar="SNAP=CHANNEL",
             dest="channels", default={}, help=(
@@ -45,7 +41,7 @@ class BuildCharm(BuilderProxyOperationMixin, VCSOperationMixin,
         parser.add_argument("name", help="name of charm to build")
 
     def __init__(self, args, parser):
-        super(BuildCharm, self).__init__(args, parser)
+        super().__init__(args, parser)
         self.buildd_path = os.path.join("/home/buildd", self.args.name)
 
     def install(self):
diff --git a/lpbuildd/target/build_livefs.py b/lpbuildd/target/build_livefs.py
index 8881e33..1d576d0 100644
--- a/lpbuildd/target/build_livefs.py
+++ b/lpbuildd/target/build_livefs.py
@@ -1,10 +1,6 @@
 # Copyright 2013-2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 from collections import OrderedDict
 import logging
 import os
@@ -36,7 +32,7 @@ class BuildLiveFS(SnapStoreOperationMixin, Operation):
 
     @classmethod
     def add_arguments(cls, parser):
-        super(BuildLiveFS, cls).add_arguments(parser)
+        super().add_arguments(parser)
         parser.add_argument(
             "--subarch", metavar="SUBARCH",
             help="build for subarchitecture SUBARCH")
diff --git a/lpbuildd/target/build_oci.py b/lpbuildd/target/build_oci.py
index 3bfccd0..8e96b0b 100644
--- a/lpbuildd/target/build_oci.py
+++ b/lpbuildd/target/build_oci.py
@@ -1,10 +1,6 @@
 # Copyright 2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 import logging
 import os.path
 import tempfile
@@ -31,7 +27,7 @@ class BuildOCI(BuilderProxyOperationMixin, VCSOperationMixin,
 
     @classmethod
     def add_arguments(cls, parser):
-        super(BuildOCI, cls).add_arguments(parser)
+        super().add_arguments(parser)
         parser.add_argument(
             "--build-file", help="path to Dockerfile in branch")
         parser.add_argument(
@@ -45,7 +41,7 @@ class BuildOCI(BuilderProxyOperationMixin, VCSOperationMixin,
         parser.add_argument("name", help="name of image to build")
 
     def __init__(self, args, parser):
-        super(BuildOCI, self).__init__(args, parser)
+        super().__init__(args, parser)
         self.buildd_path = os.path.join("/home/buildd", self.args.name)
 
     def _add_docker_engine_proxy_settings(self):
@@ -97,7 +93,7 @@ class BuildOCI(BuilderProxyOperationMixin, VCSOperationMixin,
         if self.args.proxy_url:
             for var in ("http_proxy", "https_proxy"):
                 args.extend(
-                    ["--build-arg", "{}={}".format(var, self.args.proxy_url)])
+                    ["--build-arg", f"{var}={self.args.proxy_url}"])
         args.extend(["--tag", self.args.name])
         if self.args.build_file is not None:
             build_file_path = os.path.join(
diff --git a/lpbuildd/target/build_snap.py b/lpbuildd/target/build_snap.py
index 96cecd1..2d0886a 100644
--- a/lpbuildd/target/build_snap.py
+++ b/lpbuildd/target/build_snap.py
@@ -1,10 +1,6 @@
 # Copyright 2015-2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 import argparse
 import json
 import logging
@@ -32,13 +28,12 @@ class SnapChannelsAction(argparse.Action):
     def __init__(self, option_strings, dest, nargs=None, **kwargs):
         if nargs is not None:
             raise ValueError("nargs not allowed")
-        super(SnapChannelsAction, self).__init__(
-            option_strings, dest, **kwargs)
+        super().__init__(option_strings, dest, **kwargs)
 
     def __call__(self, parser, namespace, values, option_string=None):
         if "=" not in values:
             raise argparse.ArgumentError(
-                self, "'{}' is not of the form 'snap=channel'".format(values))
+                self, f"'{values}' is not of the form 'snap=channel'")
         snap, channel = values.split("=", 1)
         if getattr(namespace, self.dest, None) is None:
             setattr(namespace, self.dest, {})
@@ -54,7 +49,7 @@ class BuildSnap(BuilderProxyOperationMixin, VCSOperationMixin,
 
     @classmethod
     def add_arguments(cls, parser):
-        super(BuildSnap, cls).add_arguments(parser)
+        super().add_arguments(parser)
         parser.add_argument(
             "--channel", action=SnapChannelsAction, metavar="SNAP=CHANNEL",
             dest="channels", default={}, help=(
@@ -90,9 +85,9 @@ class BuildSnap(BuilderProxyOperationMixin, VCSOperationMixin,
         # lpbuildd.snap deals with it, but it's almost as easy to just
         # handle it as to assert that we don't need to.
         if proxy.username:
-            svn_servers += "http-proxy-username = {}\n".format(proxy.username)
+            svn_servers += f"http-proxy-username = {proxy.username}\n"
         if proxy.password:
-            svn_servers += "http-proxy-password = {}\n".format(proxy.password)
+            svn_servers += f"http-proxy-password = {proxy.password}\n"
         with tempfile.NamedTemporaryFile(mode="w+") as svn_servers_file:
             svn_servers_file.write(svn_servers)
             svn_servers_file.flush()
diff --git a/lpbuildd/target/chroot.py b/lpbuildd/target/chroot.py
index 436e21b..b4e1286 100644
--- a/lpbuildd/target/chroot.py
+++ b/lpbuildd/target/chroot.py
@@ -1,10 +1,6 @@
 # Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 import os.path
 import signal
 import stat
@@ -27,7 +23,7 @@ class Chroot(Backend):
     """Sets up a chroot."""
 
     def __init__(self, build_id, series=None, arch=None):
-        super(Chroot, self).__init__(build_id, series=series, arch=arch)
+        super().__init__(build_id, series=series, arch=arch)
         self.chroot_path = os.path.join(self.build_path, "chroot-autobuild")
 
     def create(self, image_path, image_type):
@@ -62,7 +58,7 @@ class Chroot(Backend):
         """See `Backend`."""
         if env:
             args = ["env"] + [
-                "%s=%s" % (key, value) for key, value in env.items()] + args
+                f"{key}={value}" for key, value in env.items()] + args
         if self.arch is not None:
             args = set_personality(args, self.arch, series=self.series)
         if cwd is not None:
@@ -71,7 +67,7 @@ class Chroot(Backend):
             # though once we have coreutils >= 8.28 everywhere we'll be able
             # to use "env --chdir".
             args = [
-                "/bin/sh", "-c", "cd %s && %s" % (
+                "/bin/sh", "-c", "cd {} && {}".format(
                     shell_escape(cwd),
                     " ".join(shell_escape(arg) for arg in args)),
                 ]
diff --git a/lpbuildd/target/cli.py b/lpbuildd/target/cli.py
index e114f35..8ef534e 100644
--- a/lpbuildd/target/cli.py
+++ b/lpbuildd/target/cli.py
@@ -1,10 +1,6 @@
 # Copyright 2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 from argparse import ArgumentParser
 import logging
 import sys
diff --git a/lpbuildd/target/generate_translation_templates.py b/lpbuildd/target/generate_translation_templates.py
index 1e708ba..190616b 100644
--- a/lpbuildd/target/generate_translation_templates.py
+++ b/lpbuildd/target/generate_translation_templates.py
@@ -1,10 +1,6 @@
 # Copyright 2010-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 import logging
 import os.path
 
@@ -27,13 +23,13 @@ class GenerateTranslationTemplates(VCSOperationMixin, Operation):
 
     @classmethod
     def add_arguments(cls, parser):
-        super(GenerateTranslationTemplates, cls).add_arguments(parser)
+        super().add_arguments(parser)
         parser.add_argument(
             "result_name",
             help="the name of the result tarball; should end in '.tar.gz'")
 
     def __init__(self, args, parser):
-        super(GenerateTranslationTemplates, self).__init__(args, parser)
+        super().__init__(args, parser)
         self.work_dir = os.environ["HOME"]
         self.branch_dir = os.path.join(self.work_dir, "source-tree")
 
diff --git a/lpbuildd/target/lifecycle.py b/lpbuildd/target/lifecycle.py
index d8fac72..b5d0e84 100644
--- a/lpbuildd/target/lifecycle.py
+++ b/lpbuildd/target/lifecycle.py
@@ -1,10 +1,6 @@
 # Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 import logging
 import os
 import sys
@@ -22,7 +18,7 @@ class Create(Operation):
 
     @classmethod
     def add_arguments(cls, parser):
-        super(Create, cls).add_arguments(parser)
+        super().add_arguments(parser)
         parser.add_argument(
             "--image-type", default="chroot", help="base image type")
         parser.add_argument("image_path", help="path to base image")
diff --git a/lpbuildd/target/lxd.py b/lpbuildd/target/lxd.py
index 08c40c9..32d2788 100644
--- a/lpbuildd/target/lxd.py
+++ b/lpbuildd/target/lxd.py
@@ -1,10 +1,6 @@
 # Copyright 2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 from contextlib import closing
 import io
 import json
@@ -88,7 +84,7 @@ class LXDException(Exception):
         self.lxdapi_exc = lxdapi_exc
 
     def __str__(self):
-        return "%s: %s" % (self.action, self.lxdapi_exc)
+        return f"{self.action}: {self.lxdapi_exc}"
 
 
 class LXD(Backend):
@@ -131,7 +127,7 @@ class LXD(Backend):
 
     @property
     def alias(self):
-        return "lp-%s-%s" % (self.series, self.arch)
+        return f"lp-{self.series}-{self.arch}"
 
     @property
     def name(self):
@@ -153,7 +149,7 @@ class LXD(Backend):
                 "os": "Ubuntu",
                 "series": self.series,
                 "architecture": self.arch,
-                "description": "Launchpad chroot for Ubuntu %s (%s)" % (
+                "description": "Launchpad chroot for Ubuntu {} ({})".format(
                     self.series, self.arch),
                 },
             }
@@ -323,7 +319,7 @@ class LXD(Backend):
             ]
 
         lxc_version = self._client.host_info["environment"]["driver_version"]
-        major, minor = [int(v) for v in lxc_version.split(".")[0:2]]
+        major, minor = (int(v) for v in lxc_version.split(".")[0:2])
 
         if major >= 3:
             raw_lxc_config.extend([
@@ -346,8 +342,7 @@ class LXD(Backend):
             "security.privileged": "true",
             "security.nesting": "true",
             "raw.lxc": "".join(
-                "{key}={value}\n".format(key=key, value=value)
-                for key, value in sorted(raw_lxc_config)),
+                f"{key}={value}\n" for key, value in sorted(raw_lxc_config)),
             }
         devices = {
             "eth0": {
@@ -389,7 +384,7 @@ class LXD(Backend):
                 hosts_file.seek(0, os.SEEK_SET)
                 hosts_file.write(fallback_hosts)
             hosts_file.seek(0, os.SEEK_END)
-            print("\n127.0.1.1\t%s %s" % (fqdn, hostname), file=hosts_file)
+            print(f"\n127.0.1.1\t{fqdn} {hostname}", file=hosts_file)
             hosts_file.flush()
             os.fchmod(hosts_file.fileno(), 0o644)
             self.copy_in(hosts_file.name, "/etc/hosts")
@@ -512,7 +507,7 @@ class LXD(Backend):
         env_params = []
         if env:
             for key, value in env.items():
-                env_params.extend(["--env", "%s=%s" % (key, value)])
+                env_params.extend(["--env", f"{key}={value}"])
         if self.arch is not None:
             args = set_personality(args, self.arch, series=self.series)
         if cwd is not None:
@@ -521,7 +516,7 @@ class LXD(Backend):
             # though once we have coreutils >= 8.28 everywhere we'll be able
             # to use "env --chdir".
             args = [
-                "/bin/sh", "-c", "cd %s && %s" % (
+                "/bin/sh", "-c", "cd {} && {}".format(
                     shell_escape(cwd),
                     " ".join(shell_escape(arg) for arg in args)),
                 ]
@@ -576,7 +571,7 @@ class LXD(Backend):
                     params=params, data=data, headers=headers)
             except LXDAPIException as e:
                 raise LXDException(
-                    "Failed to push %s:%s" % (self.name, target_path), e)
+                    f"Failed to push {self.name}:{target_path}", e)
 
     def _get_file(self, container, *args, **kwargs):
         # pylxd < 2.1.1 tries to validate the response as JSON in streaming
@@ -604,7 +599,7 @@ class LXD(Backend):
                         target_file.write(chunk)
             except LXDAPIException as e:
                 raise LXDException(
-                    "Failed to pull %s:%s" % (self.name, source_path), e)
+                    f"Failed to pull {self.name}:{source_path}", e)
 
     def stop(self):
         """See `Backend`."""
@@ -627,4 +622,4 @@ class LXD(Backend):
     def remove(self):
         """See `Backend`."""
         self.remove_image()
-        super(LXD, self).remove()
+        super().remove()
diff --git a/lpbuildd/target/operation.py b/lpbuildd/target/operation.py
index bbc17cc..63a3c64 100644
--- a/lpbuildd/target/operation.py
+++ b/lpbuildd/target/operation.py
@@ -1,10 +1,6 @@
 # Copyright 2017-2021 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 from collections import OrderedDict
 
 from lpbuildd.target.backend import make_backend
diff --git a/lpbuildd/target/proxy.py b/lpbuildd/target/proxy.py
index 57d34fd..b952d4e 100644
--- a/lpbuildd/target/proxy.py
+++ b/lpbuildd/target/proxy.py
@@ -1,10 +1,6 @@
 # Copyright 2019-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 from collections import OrderedDict
 import os
 import sys
@@ -14,12 +10,12 @@ class BuilderProxyOperationMixin:
     """Methods supporting the build time HTTP proxy for certain build types."""
 
     def __init__(self, args, parser):
-        super(BuilderProxyOperationMixin, self).__init__(args, parser)
+        super().__init__(args, parser)
         self.bin = os.path.dirname(sys.argv[0])
 
     @classmethod
     def add_arguments(cls, parser):
-        super(BuilderProxyOperationMixin, cls).add_arguments(parser)
+        super().add_arguments(parser)
         parser.add_argument("--proxy-url", help="builder proxy url")
         parser.add_argument(
             "--revocation-endpoint",
diff --git a/lpbuildd/target/run_ci.py b/lpbuildd/target/run_ci.py
index 0dac760..50e56ed 100644
--- a/lpbuildd/target/run_ci.py
+++ b/lpbuildd/target/run_ci.py
@@ -1,8 +1,6 @@
 # Copyright 2022 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 import logging
 import os
 
@@ -29,7 +27,7 @@ class RunCIPrepare(BuilderProxyOperationMixin, VCSOperationMixin,
 
     @classmethod
     def add_arguments(cls, parser):
-        super(RunCIPrepare, cls).add_arguments(parser)
+        super().add_arguments(parser)
         parser.add_argument(
             "--channel", action=SnapChannelsAction, metavar="SNAP=CHANNEL",
             dest="channels", default={}, help="install SNAP from CHANNEL")
@@ -90,7 +88,7 @@ class RunCI(BuilderProxyOperationMixin, Operation):
 
     @classmethod
     def add_arguments(cls, parser):
-        super(RunCI, cls).add_arguments(parser)
+        super().add_arguments(parser)
         parser.add_argument("job_name", help="job name to run")
         parser.add_argument(
             "job_index", type=int, help="index within job name to run")
@@ -98,7 +96,7 @@ class RunCI(BuilderProxyOperationMixin, Operation):
     def run_job(self):
         logger.info("Running job phase...")
         env = self.build_proxy_environment(proxy_url=self.args.proxy_url)
-        job_id = "%s:%s" % (self.args.job_name, self.args.job_index)
+        job_id = f"{self.args.job_name}:{self.args.job_index}"
         logger.info("Running %s" % job_id)
         output_path = os.path.join("/build", "output", job_id)
         self.backend.run(["mkdir", "-p", output_path])
@@ -108,7 +106,7 @@ class RunCI(BuilderProxyOperationMixin, Operation):
             ]
         tee_args = ["tee", "%s.log" % output_path]
         args = [
-            "/bin/bash", "-o", "pipefail", "-c", "%s 2>&1 | %s" % (
+            "/bin/bash", "-o", "pipefail", "-c", "{} 2>&1 | {}".format(
                 " ".join(shell_escape(arg) for arg in lpcraft_args),
                 " ".join(shell_escape(arg) for arg in tee_args)),
             ]
diff --git a/lpbuildd/target/snapstore.py b/lpbuildd/target/snapstore.py
index a1320ea..a46bb5a 100644
--- a/lpbuildd/target/snapstore.py
+++ b/lpbuildd/target/snapstore.py
@@ -1,10 +1,6 @@
 # Copyright 2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 import requests
 from six.moves.urllib.parse import (
     urljoin,
@@ -18,7 +14,7 @@ class SnapStoreOperationMixin:
 
     @classmethod
     def add_arguments(cls, parser):
-        super(SnapStoreOperationMixin, cls).add_arguments(parser)
+        super().add_arguments(parser)
         parser.add_argument(
             "--snap-store-proxy-url", metavar="URL",
             help="snap store proxy URL")
@@ -39,4 +35,4 @@ class SnapStoreOperationMixin:
         store_id = assertions_response.headers.get("X-Assertion-Store-Id")
         if store_id is not None:
             self.backend.run(
-                ["snap", "set", "core", "proxy.store={}".format(store_id)])
+                ["snap", "set", "core", f"proxy.store={store_id}"])
diff --git a/lpbuildd/target/status.py b/lpbuildd/target/status.py
index 3ebabb5..f05100d 100644
--- a/lpbuildd/target/status.py
+++ b/lpbuildd/target/status.py
@@ -1,10 +1,6 @@
 # Copyright 2018-2021 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 import json
 import os
 
diff --git a/lpbuildd/target/tests/matchers.py b/lpbuildd/target/tests/matchers.py
index e8e01a1..388c2ef 100644
--- a/lpbuildd/target/tests/matchers.py
+++ b/lpbuildd/target/tests/matchers.py
@@ -1,8 +1,6 @@
 # Copyright 2021 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 from testtools.matchers import (
     Equals,
     Is,
@@ -34,20 +32,19 @@ class RanCommand(MatchesListwise):
         if env:
             kwargs_matcher["env"] = MatchesDict(
                 {key: Equals(value) for key, value in env.items()})
-        super(RanCommand, self).__init__(
-            [Equals((args,)), MatchesDict(kwargs_matcher)])
+        super().__init__([Equals((args,)), MatchesDict(kwargs_matcher)])
 
 
 class RanAptGet(RanCommand):
 
     def __init__(self, *args):
-        super(RanAptGet, self).__init__(["apt-get", "-y"] + list(args))
+        super().__init__(["apt-get", "-y"] + list(args))
 
 
 class RanSnap(RanCommand):
 
     def __init__(self, *args, **kwargs):
-        super(RanSnap, self).__init__(["snap"] + list(args), **kwargs)
+        super().__init__(["snap"] + list(args), **kwargs)
 
 
 class RanBuildCommand(RanCommand):
@@ -56,4 +53,4 @@ class RanBuildCommand(RanCommand):
         kwargs.setdefault("cwd", "/build")
         kwargs.setdefault("LANG", "C.UTF-8")
         kwargs.setdefault("SHELL", "/bin/sh")
-        super(RanBuildCommand, self).__init__(args, **kwargs)
+        super().__init__(args, **kwargs)
diff --git a/lpbuildd/target/tests/test_apt.py b/lpbuildd/target/tests/test_apt.py
index 4af3ac7..749fd93 100644
--- a/lpbuildd/target/tests/test_apt.py
+++ b/lpbuildd/target/tests/test_apt.py
@@ -1,8 +1,6 @@
 # Copyright 2017-2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 import io
 import stat
 import subprocess
@@ -26,13 +24,13 @@ from lpbuildd.tests.fakebuilder import FakeMethod
 class MockCopyIn(FakeMethod):
 
     def __init__(self, *args, **kwargs):
-        super(MockCopyIn, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         self.source_bytes = None
 
     def __call__(self, source_path, *args, **kwargs):
         with open(source_path, "rb") as source:
             self.source_bytes = source.read()
-        return super(MockCopyIn, self).__call__(source_path, *args, **kwargs)
+        return super().__call__(source_path, *args, **kwargs)
 
 
 class TestOverrideSourcesList(TestCase):
@@ -124,7 +122,7 @@ class TestAddTrustedKeys(TestCase):
 class RanAptGet(MatchesListwise):
 
     def __init__(self, args_list):
-        super(RanAptGet, self).__init__([
+        super().__init__([
             MatchesListwise([
                 Equals((["/usr/bin/apt-get"] + args,)),
                 ContainsDict({
@@ -161,7 +159,7 @@ class TestUpdate(TestCase):
     def test_first_run_fails(self):
         class FailFirstTime(FakeMethod):
             def __call__(self, run_args, *args, **kwargs):
-                super(FailFirstTime, self).__call__(run_args, *args, **kwargs)
+                super().__call__(run_args, *args, **kwargs)
                 if len(self.calls) == 1:
                     raise subprocess.CalledProcessError(1, run_args)
 
diff --git a/lpbuildd/target/tests/test_build_charm.py b/lpbuildd/target/tests/test_build_charm.py
index cd4ebca..42092ba 100644
--- a/lpbuildd/target/tests/test_build_charm.py
+++ b/lpbuildd/target/tests/test_build_charm.py
@@ -1,8 +1,6 @@
 # Copyright 2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 import json
 import os
 import stat
@@ -390,7 +388,7 @@ class TestBuildCharm(TestCase):
     def test_run_install_fails(self):
         class FailInstall(FakeMethod):
             def __call__(self, run_args, *args, **kwargs):
-                super(FailInstall, self).__call__(run_args, *args, **kwargs)
+                super().__call__(run_args, *args, **kwargs)
                 if run_args[0] == "apt-get":
                     raise subprocess.CalledProcessError(1, run_args)
 
@@ -407,7 +405,7 @@ class TestBuildCharm(TestCase):
     def test_run_repo_fails(self):
         class FailRepo(FakeMethod):
             def __call__(self, run_args, *args, **kwargs):
-                super(FailRepo, self).__call__(run_args, *args, **kwargs)
+                super().__call__(run_args, *args, **kwargs)
                 if run_args[:2] == ["bzr", "branch"]:
                     raise subprocess.CalledProcessError(1, run_args)
 
@@ -424,7 +422,7 @@ class TestBuildCharm(TestCase):
     def test_run_build_fails(self):
         class FailBuild(FakeMethod):
             def __call__(self, run_args, *args, **kwargs):
-                super(FailBuild, self).__call__(run_args, *args, **kwargs)
+                super().__call__(run_args, *args, **kwargs)
                 if run_args[0] == "charmcraft":
                     raise subprocess.CalledProcessError(1, run_args)
 
diff --git a/lpbuildd/target/tests/test_build_livefs.py b/lpbuildd/target/tests/test_build_livefs.py
index 2fb2b19..e8fa92a 100644
--- a/lpbuildd/target/tests/test_build_livefs.py
+++ b/lpbuildd/target/tests/test_build_livefs.py
@@ -1,8 +1,6 @@
 # Copyright 2017-2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 import subprocess
 from textwrap import dedent
 
@@ -200,7 +198,7 @@ class TestBuildLiveFS(TestCase):
             "buildlivefs",
             "--backend=fake", "--series=xenial", "--arch=amd64", "1",
             "--project=ubuntu-cpc",
-            "--http-proxy={}".format(proxy),
+            f"--http-proxy={proxy}",
             ]
         build_livefs = parse_args(args=args).operation
         build_livefs.build()
@@ -237,7 +235,7 @@ class TestBuildLiveFS(TestCase):
     def test_run_install_fails(self):
         class FailInstall(FakeMethod):
             def __call__(self, run_args, *args, **kwargs):
-                super(FailInstall, self).__call__(run_args, *args, **kwargs)
+                super().__call__(run_args, *args, **kwargs)
                 if run_args[0] == "apt-get":
                     raise subprocess.CalledProcessError(1, run_args)
 
@@ -254,7 +252,7 @@ class TestBuildLiveFS(TestCase):
     def test_run_build_fails(self):
         class FailBuild(FakeMethod):
             def __call__(self, run_args, *args, **kwargs):
-                super(FailBuild, self).__call__(run_args, *args, **kwargs)
+                super().__call__(run_args, *args, **kwargs)
                 if run_args[0] == "rm":
                     raise subprocess.CalledProcessError(1, run_args)
 
diff --git a/lpbuildd/target/tests/test_build_oci.py b/lpbuildd/target/tests/test_build_oci.py
index 199d3b3..2d17ef3 100644
--- a/lpbuildd/target/tests/test_build_oci.py
+++ b/lpbuildd/target/tests/test_build_oci.py
@@ -1,8 +1,6 @@
 # Copyright 2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 import os.path
 import stat
 import subprocess
@@ -388,7 +386,7 @@ class TestBuildOCI(TestCase):
     def test_run_install_fails(self):
         class FailInstall(FakeMethod):
             def __call__(self, run_args, *args, **kwargs):
-                super(FailInstall, self).__call__(run_args, *args, **kwargs)
+                super().__call__(run_args, *args, **kwargs)
                 if run_args[0] == "apt-get":
                     raise subprocess.CalledProcessError(1, run_args)
 
@@ -405,7 +403,7 @@ class TestBuildOCI(TestCase):
     def test_run_repo_fails(self):
         class FailRepo(FakeMethod):
             def __call__(self, run_args, *args, **kwargs):
-                super(FailRepo, self).__call__(run_args, *args, **kwargs)
+                super().__call__(run_args, *args, **kwargs)
                 if run_args[:2] == ["bzr", "branch"]:
                     raise subprocess.CalledProcessError(1, run_args)
 
@@ -422,7 +420,7 @@ class TestBuildOCI(TestCase):
     def test_run_build_fails(self):
         class FailBuild(FakeMethod):
             def __call__(self, run_args, *args, **kwargs):
-                super(FailBuild, self).__call__(run_args, *args, **kwargs)
+                super().__call__(run_args, *args, **kwargs)
                 if run_args[0] == "docker":
                     raise subprocess.CalledProcessError(1, run_args)
 
diff --git a/lpbuildd/target/tests/test_build_snap.py b/lpbuildd/target/tests/test_build_snap.py
index 2bda8bb..48fa416 100644
--- a/lpbuildd/target/tests/test_build_snap.py
+++ b/lpbuildd/target/tests/test_build_snap.py
@@ -1,8 +1,6 @@
 # Copyright 2017-2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 import json
 import os.path
 import stat
@@ -39,11 +37,11 @@ from lpbuildd.tests.fakebuilder import FakeMethod
 class FakeRevisionID(FakeMethod):
 
     def __init__(self, revision_id):
-        super(FakeRevisionID, self).__init__()
+        super().__init__()
         self.revision_id = revision_id
 
     def __call__(self, run_args, *args, **kwargs):
-        super(FakeRevisionID, self).__call__(run_args, *args, **kwargs)
+        super().__call__(run_args, *args, **kwargs)
         if (run_args[:2] == ["bzr", "revno"] or
                 (run_args[0] == "git" and "rev-parse" in run_args)):
             return "%s\n" % self.revision_id
@@ -505,7 +503,7 @@ class TestBuildSnap(TestCase):
     def test_run_install_fails(self):
         class FailInstall(FakeMethod):
             def __call__(self, run_args, *args, **kwargs):
-                super(FailInstall, self).__call__(run_args, *args, **kwargs)
+                super().__call__(run_args, *args, **kwargs)
                 if run_args[0] == "apt-get":
                     raise subprocess.CalledProcessError(1, run_args)
 
@@ -522,7 +520,7 @@ class TestBuildSnap(TestCase):
     def test_run_repo_fails(self):
         class FailRepo(FakeMethod):
             def __call__(self, run_args, *args, **kwargs):
-                super(FailRepo, self).__call__(run_args, *args, **kwargs)
+                super().__call__(run_args, *args, **kwargs)
                 if run_args[:2] == ["bzr", "branch"]:
                     raise subprocess.CalledProcessError(1, run_args)
 
@@ -539,7 +537,7 @@ class TestBuildSnap(TestCase):
     def test_run_pull_fails(self):
         class FailPull(FakeMethod):
             def __call__(self, run_args, *args, **kwargs):
-                super(FailPull, self).__call__(run_args, *args, **kwargs)
+                super().__call__(run_args, *args, **kwargs)
                 if run_args[:2] == ["bzr", "revno"]:
                     return "42\n"
                 elif run_args[:2] == ["snapcraft", "pull"]:
@@ -559,7 +557,7 @@ class TestBuildSnap(TestCase):
     def test_run_build_fails(self):
         class FailBuild(FakeMethod):
             def __call__(self, run_args, *args, **kwargs):
-                super(FailBuild, self).__call__(run_args, *args, **kwargs)
+                super().__call__(run_args, *args, **kwargs)
                 if run_args[:2] == ["bzr", "revno"]:
                     return "42\n"
                 elif run_args == ["snapcraft"]:
diff --git a/lpbuildd/target/tests/test_chroot.py b/lpbuildd/target/tests/test_chroot.py
index 10483e1..b5a3f86 100644
--- a/lpbuildd/target/tests/test_chroot.py
+++ b/lpbuildd/target/tests/test_chroot.py
@@ -1,8 +1,6 @@
 # Copyright 2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 import io
 import os.path
 import signal
@@ -34,7 +32,7 @@ from lpbuildd.target.tests.testfixtures import (
 class TestChroot(TestCase):
 
     def setUp(self):
-        super(TestChroot, self).setUp()
+        super().setUp()
         self.useFixture(CarefulFakeProcessFixture())
 
     def test_create(self):
@@ -127,7 +125,7 @@ class TestChroot(TestCase):
         self.useFixture(EnvironmentVariable("HOME", "/expected/home"))
         processes_fixture = self.useFixture(FakeProcesses())
         processes_fixture.add(lambda _: {}, name="sudo")
-        arg = u"\N{SNOWMAN}"
+        arg = "\N{SNOWMAN}"
         Chroot("1", "xenial", "amd64").run(["echo", arg])
 
         expected_args = [
@@ -315,9 +313,9 @@ class TestChroot(TestCase):
         self.useFixture(EnvironmentVariable("HOME", "/expected/home"))
         processes_fixture = self.useFixture(FakeProcesses())
         test_proc_infos = iter([
-            {"stdout": io.StringIO(u"Package: snapd\n")},
+            {"stdout": io.StringIO("Package: snapd\n")},
             {"returncode": 100},
-            {"stderr": io.StringIO(u"N: No packages found\n")},
+            {"stderr": io.StringIO("N: No packages found\n")},
             ])
         processes_fixture.add(lambda _: next(test_proc_infos), name="sudo")
         self.assertTrue(
diff --git a/lpbuildd/target/tests/test_generate_translation_templates.py b/lpbuildd/target/tests/test_generate_translation_templates.py
index 0b72b29..97f4dac 100644
--- a/lpbuildd/target/tests/test_generate_translation_templates.py
+++ b/lpbuildd/target/tests/test_generate_translation_templates.py
@@ -1,8 +1,6 @@
 # Copyright 2010-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 import os
 import subprocess
 import tarfile
@@ -33,7 +31,7 @@ class TestGenerateTranslationTemplates(TestCase):
     result_name = "translation-templates.tar.gz"
 
     def setUp(self):
-        super(TestGenerateTranslationTemplates, self).setUp()
+        super().setUp()
         self.home_dir = self.useFixture(TempDir()).path
         self.useFixture(EnvironmentVariable("HOME", self.home_dir))
         self.logger = self.useFixture(FakeLogger())
diff --git a/lpbuildd/target/tests/test_lifecycle.py b/lpbuildd/target/tests/test_lifecycle.py
index 4ff41e3..89e03c7 100644
--- a/lpbuildd/target/tests/test_lifecycle.py
+++ b/lpbuildd/target/tests/test_lifecycle.py
@@ -1,8 +1,6 @@
 # Copyright 2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 from textwrap import dedent
 
 from fixtures import FakeLogger
diff --git a/lpbuildd/target/tests/test_lxd.py b/lpbuildd/target/tests/test_lxd.py
index 07e77cb..93c854d 100644
--- a/lpbuildd/target/tests/test_lxd.py
+++ b/lpbuildd/target/tests/test_lxd.py
@@ -1,10 +1,6 @@
 # Copyright 2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 import argparse
 from contextlib import closing
 import io
@@ -55,7 +51,7 @@ LXD_RUNNING = 103
 class FakeLXDAPIException(LXDAPIException):
 
     def __init__(self):
-        super(FakeLXDAPIException, self).__init__(None)
+        super().__init__(None)
 
     def __str__(self):
         return "Fake LXD exception"
@@ -89,20 +85,20 @@ class FakeHostname:
         parser.add_argument("--fqdn", action="store_true", default=False)
         args = parser.parse_args(proc_args["args"][1:])
         output = self.fqdn if args.fqdn else self.hostname
-        return {"stdout": io.StringIO(output + u"\n")}
+        return {"stdout": io.StringIO(output + "\n")}
 
 
 class FakeFilesystem(_FakeFilesystem):
     # Add support for os.mknod to the upstream implementation.
 
     def _setUp(self):
-        super(FakeFilesystem, self)._setUp()
+        super()._setUp()
         self._devices = {}
         self.useFixture(
             Overlay("os.mknod", self._mknod, self._is_fake_path))
 
     def _stat(self, real, path, *args, **kwargs):
-        r = super(FakeFilesystem, self)._stat(real, path, *args, **kwargs)
+        r = super()._stat(real, path, *args, **kwargs)
         if path in self._devices:
             r = os.stat_result(list(r), {"st_rdev": self._devices[path]})
         return r
@@ -117,7 +113,7 @@ class FakeFilesystem(_FakeFilesystem):
 class TestLXD(TestCase):
 
     def setUp(self):
-        super(TestLXD, self).setUp()
+        super().setUp()
         self.useFixture(CarefulFakeProcessFixture())
 
     def make_chroot_tarball(self, output_path):
@@ -294,7 +290,7 @@ class TestLXD(TestCase):
             ("lxc.mount.auto", "proc:rw sys:rw"),
             ]
 
-        major, minor = [int(v) for v in driver_version.split(".")[0:2]]
+        major, minor = (int(v) for v in driver_version.split(".")[0:2])
 
         if major >= 3:
             raw_lxc_config.extend([
@@ -310,7 +306,7 @@ class TestLXD(TestCase):
                 ])
 
         raw_lxc_config = "".join(
-            "{key}={val}\n".format(key=key, val=val)
+            f"{key}={val}\n"
             for key, val in sorted(raw_lxc_config + extra_raw_lxc_config))
 
         expected_config = {
@@ -410,7 +406,7 @@ class TestLXD(TestCase):
             elif command == "remove":
                 os.remove("/dev/dm-0")
             else:
-                self.fail("unexpected dmsetup command %r" % (command,))
+                self.fail(f"unexpected dmsetup command {command!r}")
             return {}
         processes_fixture.add(fake_sudo, name="sudo")
         processes_fixture.add(lambda _: {}, name="lxc")
@@ -636,7 +632,7 @@ class TestLXD(TestCase):
     def test_run_non_ascii_arguments(self):
         processes_fixture = self.useFixture(FakeProcesses())
         processes_fixture.add(lambda _: {}, name="lxc")
-        arg = u"\N{SNOWMAN}"
+        arg = "\N{SNOWMAN}"
         LXD("1", "xenial", "amd64").run(["echo", arg])
 
         expected_args = [
@@ -849,9 +845,9 @@ class TestLXD(TestCase):
     def test_is_package_available(self):
         processes_fixture = self.useFixture(FakeProcesses())
         test_proc_infos = iter([
-            {"stdout": io.StringIO(u"Package: snapd\n")},
+            {"stdout": io.StringIO("Package: snapd\n")},
             {"returncode": 100},
-            {"stderr": io.StringIO(u"N: No packages found\n")},
+            {"stderr": io.StringIO("N: No packages found\n")},
             ])
         processes_fixture.add(lambda _: next(test_proc_infos), name="lxc")
         self.assertTrue(
diff --git a/lpbuildd/target/tests/test_operation.py b/lpbuildd/target/tests/test_operation.py
index 00ee29a..2ee909a 100644
--- a/lpbuildd/target/tests/test_operation.py
+++ b/lpbuildd/target/tests/test_operation.py
@@ -1,8 +1,6 @@
 # Copyright 2017-2021 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 from argparse import ArgumentParser
 
 from testtools import TestCase
diff --git a/lpbuildd/target/tests/test_run_ci.py b/lpbuildd/target/tests/test_run_ci.py
index 9c6621e..d5cc266 100644
--- a/lpbuildd/target/tests/test_run_ci.py
+++ b/lpbuildd/target/tests/test_run_ci.py
@@ -1,8 +1,6 @@
 # Copyright 2022 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 import json
 import os
 import stat
@@ -39,11 +37,11 @@ from lpbuildd.tests.fakebuilder import FakeMethod
 class FakeRevisionID(FakeMethod):
 
     def __init__(self, revision_id):
-        super(FakeRevisionID, self).__init__()
+        super().__init__()
         self.revision_id = revision_id
 
     def __call__(self, run_args, *args, **kwargs):
-        super(FakeRevisionID, self).__call__(run_args, *args, **kwargs)
+        super().__call__(run_args, *args, **kwargs)
         if run_args[0] == "git" and "rev-parse" in run_args:
             return "%s\n" % self.revision_id
 
@@ -267,7 +265,7 @@ class TestRunCIPrepare(TestCase):
     def test_run_install_fails(self):
         class FailInstall(FakeMethod):
             def __call__(self, run_args, *args, **kwargs):
-                super(FailInstall, self).__call__(run_args, *args, **kwargs)
+                super().__call__(run_args, *args, **kwargs)
                 if run_args[0] == "apt-get":
                     raise subprocess.CalledProcessError(1, run_args)
 
@@ -284,7 +282,7 @@ class TestRunCIPrepare(TestCase):
     def test_run_repo_fails(self):
         class FailRepo(FakeMethod):
             def __call__(self, run_args, *args, **kwargs):
-                super(FailRepo, self).__call__(run_args, *args, **kwargs)
+                super().__call__(run_args, *args, **kwargs)
                 if run_args[0] == "git":
                     raise subprocess.CalledProcessError(1, run_args)
 
@@ -358,7 +356,7 @@ class TestRunCI(TestCase):
     def test_run_install_fails(self):
         class FailInstall(FakeMethod):
             def __call__(self, run_args, *args, **kwargs):
-                super(FailInstall, self).__call__(run_args, *args, **kwargs)
+                super().__call__(run_args, *args, **kwargs)
                 if run_args[0] == "/bin/bash":
                     raise subprocess.CalledProcessError(1, run_args)
 
diff --git a/lpbuildd/target/tests/testfixtures.py b/lpbuildd/target/tests/testfixtures.py
index 02cd995..2b056f0 100644
--- a/lpbuildd/target/tests/testfixtures.py
+++ b/lpbuildd/target/tests/testfixtures.py
@@ -1,8 +1,6 @@
 # Copyright 2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 import argparse
 import io
 import os
@@ -10,7 +8,6 @@ import shutil
 
 from fixtures import MonkeyPatch
 from fixtures._fixtures import popen
-import six
 from systemfixtures import FakeFilesystem as _FakeFilesystem
 
 
@@ -68,7 +65,7 @@ class Kill:
 class KillFixture(MonkeyPatch):
 
     def __init__(self, delays=None):
-        super(KillFixture, self).__init__("os.kill", Kill(delays=delays))
+        super().__init__("os.kill", Kill(delays=delays))
 
     @property
     def kills(self):
@@ -85,7 +82,7 @@ class FakeFilesystem(_FakeFilesystem):
     """
 
     def _setUp(self):
-        super(FakeFilesystem, self)._setUp()
+        super()._setUp()
         self._excludes = set()
 
     def remove(self, path):
@@ -96,21 +93,21 @@ class FakeFilesystem(_FakeFilesystem):
         the overlay filesystem.
         """
         if not path.startswith(os.sep):
-            raise ValueError("Non-absolute path '{}'".format(path))
+            raise ValueError(f"Non-absolute path '{path}'")
         self._excludes.add(path.rstrip(os.sep))
 
     def _is_fake_path(self, path, *args, **kwargs):
         for prefix in self._excludes:
             if path.startswith(prefix):
                 return False
-        return super(FakeFilesystem, self)._is_fake_path(path, *args, **kwargs)
+        return super()._is_fake_path(path, *args, **kwargs)
 
 
 class CarefulFakeProcess(popen.FakeProcess):
     """A version of FakeProcess that is more careful about text mode."""
 
     def __init__(self, *args, **kwargs):
-        super(CarefulFakeProcess, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         text_mode = bool(self._args.get("universal_newlines"))
         if not self.stdout:
             self.stdout = io.StringIO() if text_mode else io.BytesIO()
@@ -118,16 +115,16 @@ class CarefulFakeProcess(popen.FakeProcess):
             self.stderr = io.StringIO() if text_mode else io.BytesIO()
 
     def communicate(self, *args, **kwargs):
-        out, err = super(CarefulFakeProcess, self).communicate(*args, **kwargs)
+        out, err = super().communicate(*args, **kwargs)
         if self._args.get("universal_newlines"):
             if isinstance(out, bytes):
                 raise TypeError("Process stdout is bytes, expecting text")
             if isinstance(err, bytes):
                 raise TypeError("Process stderr is bytes, expecting text")
         else:
-            if isinstance(out, six.text_type):
+            if isinstance(out, str):
                 raise TypeError("Process stdout is text, expecting bytes")
-            if isinstance(err, six.text_type):
+            if isinstance(err, str):
                 raise TypeError("Process stderr is text, expecting bytes")
         return out, err
 
@@ -136,5 +133,5 @@ class CarefulFakeProcessFixture(MonkeyPatch):
     """Patch the Popen fixture to be more careful about text mode."""
 
     def __init__(self):
-        super(CarefulFakeProcessFixture, self).__init__(
+        super().__init__(
             "fixtures._fixtures.popen.FakeProcess", CarefulFakeProcess)
diff --git a/lpbuildd/target/vcs.py b/lpbuildd/target/vcs.py
index 164fd34..a5c9be9 100644
--- a/lpbuildd/target/vcs.py
+++ b/lpbuildd/target/vcs.py
@@ -1,10 +1,6 @@
 # Copyright 2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 from collections import OrderedDict
 import logging
 import os.path
@@ -21,7 +17,7 @@ class VCSOperationMixin(StatusOperationMixin):
 
     @classmethod
     def add_arguments(cls, parser):
-        super(VCSOperationMixin, cls).add_arguments(parser)
+        super().add_arguments(parser)
         build_from_group = parser.add_mutually_exclusive_group(required=True)
         build_from_group.add_argument(
             "--branch", metavar="BRANCH", help="build from this Bazaar branch")
@@ -33,7 +29,7 @@ class VCSOperationMixin(StatusOperationMixin):
             help="build from this ref path in REPOSITORY")
 
     def __init__(self, args, parser):
-        super(VCSOperationMixin, self).__init__(args, parser)
+        super().__init__(args, parser)
         if args.git_repository is None and args.git_path is not None:
             parser.error("--git-path requires --git-repository")
         # Set to False for local testing if your target doesn't have an
diff --git a/lpbuildd/tests/fakebuilder.py b/lpbuildd/tests/fakebuilder.py
index a00c168..268fd13 100644
--- a/lpbuildd/tests/fakebuilder.py
+++ b/lpbuildd/tests/fakebuilder.py
@@ -1,7 +1,6 @@
 # Copyright 2013-2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
 __all__ = [
     'FakeBackend',
     'FakeBuilder',
@@ -141,7 +140,7 @@ class FakeBackend(Backend):
     supports_snapd = True
 
     def __init__(self, *args, **kwargs):
-        super(FakeBackend, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         fake_methods = (
             "create", "start",
             "run",
@@ -236,7 +235,7 @@ class UncontainedBackend(Backend):
         """See `Backend`."""
         if env:
             args = ["env"] + [
-                "%s=%s" % (key, shell_escape(value))
+                f"{key}={shell_escape(value)}"
                 for key, value in env.items()] + args
         if self.arch is not None:
             args = set_personality(args, self.arch, series=self.series)
diff --git a/lpbuildd/tests/harness.py b/lpbuildd/tests/harness.py
index ce1809f..5849834 100644
--- a/lpbuildd/tests/harness.py
+++ b/lpbuildd/tests/harness.py
@@ -1,7 +1,6 @@
 # Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
 __all__ = [
     'BuilddTestCase',
     ]
@@ -25,7 +24,7 @@ test_conffile = os.path.join(
     os.path.dirname(__file__), 'buildd-slave-test.conf')
 
 
-class MockBuildManager(object):
+class MockBuildManager:
     """Mock BuildManager class.
 
     Only implements 'is_archive_private' and 'needs_sanitized_logs' as False.
diff --git a/lpbuildd/tests/matchers.py b/lpbuildd/tests/matchers.py
index 309f22e..12794b0 100644
--- a/lpbuildd/tests/matchers.py
+++ b/lpbuildd/tests/matchers.py
@@ -1,8 +1,6 @@
 # Copyright 2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 from testtools.matchers import (
     Equals,
     Matcher,
diff --git a/lpbuildd/tests/oci_tarball.py b/lpbuildd/tests/oci_tarball.py
index 35860ad..af0701d 100644
--- a/lpbuildd/tests/oci_tarball.py
+++ b/lpbuildd/tests/oci_tarball.py
@@ -39,7 +39,7 @@ class OCITarball:
     def layer_file(self, directory, layer_name):
         layer_directory = os.path.join(directory, layer_name)
         os.mkdir(layer_directory)
-        contents = "{}-contents".format(layer_name)
+        contents = f"{layer_name}-contents"
         tarinfo = tarfile.TarInfo(contents)
         tarinfo.size = len(contents)
         layer_contents = io.BytesIO(contents.encode("UTF-8"))
diff --git a/lpbuildd/tests/test_binarypackage.py b/lpbuildd/tests/test_binarypackage.py
index 22c4b0a..9e3e155 100644
--- a/lpbuildd/tests/test_binarypackage.py
+++ b/lpbuildd/tests/test_binarypackage.py
@@ -1,8 +1,6 @@
 # Copyright 2013-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 from functools import partial
 import os
 import shutil
@@ -52,7 +50,7 @@ class MockSubprocess:
 
 class MockBuildManager(BinaryPackageBuildManager):
     def __init__(self, *args, **kwargs):
-        super(MockBuildManager, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         self.commands = []
         self.iterators = []
         self.arch_indep = False
@@ -69,7 +67,7 @@ class MockBuildManager(BinaryPackageBuildManager):
 class DisableSudo(MonkeyPatch):
 
     def __init__(self):
-        super(DisableSudo, self).__init__(
+        super().__init__(
             'subprocess.call', partial(self.call_patch, subprocess.call))
 
     def call_patch(self, old_call, cmd, *args, **kwargs):
@@ -92,7 +90,7 @@ class TestBinaryPackageBuildManagerIteration(TestCase):
     run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=5)
 
     def setUp(self):
-        super(TestBinaryPackageBuildManagerIteration, self).setUp()
+        super().setUp()
         self.useFixture(DisableSudo())
         self.working_dir = tempfile.mkdtemp()
         self.addCleanup(lambda: shutil.rmtree(self.working_dir))
@@ -403,10 +401,10 @@ class TestBinaryPackageBuildManagerIteration(TestCase):
                 """))
         write_file(os.path.join(apt_lists, "other"), "some other stuff")
         expected = {
-            "foo": set(["1.0", "1.1"]),
-            "bar": set(["2.0"]),
-            "virt": set([None]),
-            "versioned-virt": set(["3.0"]),
+            "foo": {"1.0", "1.1"},
+            "bar": {"2.0"},
+            "virt": {None},
+            "versioned-virt": {"3.0"},
             }
         self.assertEqual(expected, self.buildmanager.getAvailablePackages())
 
@@ -479,13 +477,13 @@ class TestBinaryPackageBuildManagerIteration(TestCase):
         # relationMatches returns False if a dependency's package name is
         # entirely missing.
         self.assertFalse(self.buildmanager.relationMatches(
-            {"name": "foo", "version": (">=", "1")}, {"bar": set(["2"])}))
+            {"name": "foo", "version": (">=", "1")}, {"bar": {"2"}}))
 
     def test_relationMatches_unversioned(self):
         # relationMatches returns True if a dependency's package name is
         # present and the dependency is unversioned.
         self.assertTrue(self.buildmanager.relationMatches(
-            {"name": "foo", "version": None}, {"foo": set(["1"])}))
+            {"name": "foo", "version": None}, {"foo": {"1"}}))
 
     def test_relationMatches_versioned(self):
         # relationMatches handles versioned dependencies correctly.
@@ -498,8 +496,8 @@ class TestBinaryPackageBuildManagerIteration(TestCase):
                 ):
             assert_method = self.assertTrue if expected else self.assertFalse
             assert_method(self.buildmanager.relationMatches(
-                {"name": "foo", "version": version}, {"foo": set(["1"])}),
-                "%s %s 1 was not %s" % (version[1], version[0], expected))
+                {"name": "foo", "version": version}, {"foo": {"1"}}),
+                f"{version[1]} {version[0]} 1 was not {expected}")
 
     def test_relationMatches_multiple_versions(self):
         # If multiple versions of a package are present, relationMatches
@@ -512,7 +510,7 @@ class TestBinaryPackageBuildManagerIteration(TestCase):
             assert_method = self.assertTrue if expected else self.assertFalse
             assert_method(self.buildmanager.relationMatches(
                 {"name": "foo", "version": version},
-                {"foo": set(["1", "1.1"])}))
+                {"foo": {"1", "1.1"}}))
 
     def test_relationMatches_unversioned_virtual(self):
         # Unversioned dependencies match an unversioned virtual package, but
@@ -521,14 +519,14 @@ class TestBinaryPackageBuildManagerIteration(TestCase):
             assert_method = self.assertTrue if expected else self.assertFalse
             assert_method(self.buildmanager.relationMatches(
                 {"name": "foo", "version": version},
-                {"foo": set([None])}))
+                {"foo": {None}}))
 
     def test_analyseDepWait_all_satisfied(self):
         # If all direct build-dependencies are satisfied, analyseDepWait
         # returns None.
         self.assertIsNone(self.buildmanager.analyseDepWait(
             PkgRelation.parse_relations("debhelper, foo (>= 1)"),
-            {"debhelper": set(["9"]), "foo": set(["1"])}))
+            {"debhelper": {"9"}, "foo": {"1"}}))
 
     def test_analyseDepWait_unsatisfied(self):
         # If some direct build-dependencies are unsatisfied, analyseDepWait
@@ -538,7 +536,7 @@ class TestBinaryPackageBuildManagerIteration(TestCase):
             self.buildmanager.analyseDepWait(
                 PkgRelation.parse_relations(
                     "debhelper (>= 9~), foo (>= 1), bar (<< 1) | bar (>= 2)"),
-                {"debhelper": set(["9"]), "bar": set(["1", "1.5"])}))
+                {"debhelper": {"9"}, "bar": {"1", "1.5"}}))
 
     def test_analyseDepWait_strips_arch_restrictions(self):
         # analyseDepWait removes architecture restrictions (e.g. "[amd64]")
@@ -566,7 +564,7 @@ class TestBinaryPackageBuildManagerIteration(TestCase):
             "foo",
             self.buildmanager.analyseDepWait(
                 PkgRelation.parse_relations("foo:any, bar:any"),
-                {"bar": set(["1"])}))
+                {"bar": {"1"}}))
 
     def test_analyseDepWait_strips_restrictions(self):
         # analyseDepWait removes restrictions (e.g. "<stage1>") from the
diff --git a/lpbuildd/tests/test_buildd.py b/lpbuildd/tests/test_buildd.py
index 777868b..3cecb5a 100644
--- a/lpbuildd/tests/test_buildd.py
+++ b/lpbuildd/tests/test_buildd.py
@@ -11,8 +11,6 @@ This file contains the following tests:
 
 """
 
-__metaclass__ = type
-
 __all__ = ['LaunchpadBuilddTests']
 
 import difflib
@@ -206,14 +204,14 @@ class LaunchpadBuilddTests(BuilddTestCase):
 class XMLRPCBuilderTests(unittest.TestCase):
 
     def setUp(self):
-        super(XMLRPCBuilderTests, self).setUp()
+        super().setUp()
         self.builder = BuilddTestSetup()
         self.builder.setUp()
         self.server = ServerProxy('http://localhost:8321/rpc/')
 
     def tearDown(self):
         self.builder.tearDown()
-        super(XMLRPCBuilderTests, self).tearDown()
+        super().tearDown()
 
     @unittest.skipIf(
         sys.version >= '3' and
diff --git a/lpbuildd/tests/test_builder.py b/lpbuildd/tests/test_builder.py
index 177f644..db13dd8 100644
--- a/lpbuildd/tests/test_builder.py
+++ b/lpbuildd/tests/test_builder.py
@@ -32,7 +32,7 @@ class TestBuildManager(TestCase):
     run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=5)
 
     def setUp(self):
-        super(TestBuildManager, self).setUp()
+        super().setUp()
         self.log_file = io.StringIO()
         observer = FileLogObserver(
             self.log_file, lambda event: formatEvent(event) + "\n")
@@ -69,12 +69,12 @@ class TestBuildManager(TestCase):
         manager = BuildManager(builder, "123")
         d = defer.Deferred()
         manager.iterate = d.callback
-        manager.runSubProcess("echo", ["echo", u"\N{SNOWMAN}".encode("UTF-8")])
+        manager.runSubProcess("echo", ["echo", "\N{SNOWMAN}".encode()])
         code = yield d
         self.assertEqual(0, code)
         self.assertEqual(
-            u"RUN: echo '\N{SNOWMAN}'\n"
-            u"\N{SNOWMAN}\n".encode("UTF-8"),
+            "RUN: echo '\N{SNOWMAN}'\n"
+            "\N{SNOWMAN}\n".encode(),
             builder._log.getvalue())
         logged_snowman = '\N{SNOWMAN}' if six.PY3 else '\\u2603'
         self.assertEqual(
diff --git a/lpbuildd/tests/test_buildrecipe.py b/lpbuildd/tests/test_buildrecipe.py
index de7b50a..0914c04 100644
--- a/lpbuildd/tests/test_buildrecipe.py
+++ b/lpbuildd/tests/test_buildrecipe.py
@@ -1,10 +1,6 @@
 # Copyright 2014-2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from __future__ import print_function
-
-__metaclass__ = type
-
 from contextlib import contextmanager
 import imp
 import io
@@ -49,22 +45,22 @@ class RanCommand(MatchesListwise):
 
     def __init__(self, *args):
         args_matchers = [
-            Equals(arg) if isinstance(arg, six.string_types) else arg
+            Equals(arg) if isinstance(arg, str) else arg
             for arg in args]
-        super(RanCommand, self).__init__(args_matchers)
+        super().__init__(args_matchers)
 
 
 class RanInChroot(RanCommand):
 
     def __init__(self, home_dir, *args):
-        super(RanInChroot, self).__init__(
+        super().__init__(
             "sudo", "/usr/sbin/chroot",
             os.path.join(home_dir, "build-1", "chroot-autobuild"), *args)
 
 
 class TestRecipeBuilder(TestCase):
     def setUp(self):
-        super(TestRecipeBuilder, self).setUp()
+        super().setUp()
         self.save_env = dict(os.environ)
         self.home_dir = tempfile.mkdtemp()
         self.addCleanup(lambda: shutil.rmtree(self.home_dir))
@@ -86,7 +82,7 @@ class TestRecipeBuilder(TestCase):
 
     def tearDown(self):
         self.resetEnvironment()
-        super(TestRecipeBuilder, self).tearDown()
+        super().tearDown()
 
     def test_is_command_on_path_missing_environment(self):
         self.useFixture(EnvironmentVariable("PATH"))
@@ -127,10 +123,10 @@ class TestRecipeBuilder(TestCase):
 
         processes_fixture = self.useFixture(FakeProcesses())
         processes_fixture.add(
-            lambda _: {"stdout": io.StringIO(u"5.10\n")}, name="sudo")
+            lambda _: {"stdout": io.StringIO("5.10\n")}, name="sudo")
         processes_fixture.add(fake_git, name="git")
         processes_fixture.add(
-            lambda _: {"stdout": io.StringIO(u"git-build-recipe\tx.y.z\n")},
+            lambda _: {"stdout": io.StringIO("git-build-recipe\tx.y.z\n")},
             name="dpkg-query")
         processes_fixture.add(fake_git_build_recipe, name="git-build-recipe")
         self.builder = RecipeBuilder(
@@ -148,7 +144,7 @@ class TestRecipeBuilder(TestCase):
             "git-build-recipe", "--safe", "--no-build",
             "--manifest", os.path.join(self.builder.tree_path, "manifest"),
             "--distribution", "grumpy", "--allow-fallback-to-native",
-            "--append-version", u"~ubuntu5.10.1",
+            "--append-version", "~ubuntu5.10.1",
             os.path.join(self.builder.work_dir, "recipe"),
             self.builder.tree_path,
             ]
@@ -183,7 +179,7 @@ class TestRecipeBuilder(TestCase):
 
         processes_fixture = self.useFixture(FakeProcesses())
         processes_fixture.add(
-            lambda _: {"stdout": io.StringIO(u"5.10\n")}, name="sudo")
+            lambda _: {"stdout": io.StringIO("5.10\n")}, name="sudo")
         processes_fixture.add(fake_bzr, name="bzr")
         processes_fixture.add(
             fake_brz_build_daily_recipe, name="brz-build-daily-recipe")
@@ -202,7 +198,7 @@ class TestRecipeBuilder(TestCase):
             "brz-build-daily-recipe", "--safe", "--no-build",
             "--manifest", os.path.join(self.builder.tree_path, "manifest"),
             "--distribution", "grumpy", "--allow-fallback-to-native",
-            "--append-version", u"~ubuntu5.10.1",
+            "--append-version", "~ubuntu5.10.1",
             os.path.join(self.builder.work_dir, "recipe"),
             self.builder.tree_path,
             ]
@@ -236,7 +232,7 @@ class TestRecipeBuilder(TestCase):
 
         processes_fixture = self.useFixture(FakeProcesses())
         processes_fixture.add(
-            lambda _: {"stdout": io.StringIO(u"5.10\n")}, name="sudo")
+            lambda _: {"stdout": io.StringIO("5.10\n")}, name="sudo")
         processes_fixture.add(fake_bzr, name="bzr")
         with open(os.path.join(self.builder.work_dir, "recipe"), "w") as f:
             f.write("dummy recipe contents\n")
@@ -252,7 +248,7 @@ class TestRecipeBuilder(TestCase):
             "bzr", "-Derror", "dailydeb", "--safe", "--no-build",
             "--manifest", os.path.join(self.builder.tree_path, "manifest"),
             "--distribution", "grumpy", "--allow-fallback-to-native",
-            "--append-version", u"~ubuntu5.10.1",
+            "--append-version", "~ubuntu5.10.1",
             os.path.join(self.builder.work_dir, "recipe"),
             self.builder.tree_path,
             ]
diff --git a/lpbuildd/tests/test_charm.py b/lpbuildd/tests/test_charm.py
index 671ecaf..ed2e1d0 100644
--- a/lpbuildd/tests/test_charm.py
+++ b/lpbuildd/tests/test_charm.py
@@ -1,8 +1,6 @@
 # Copyright 2021 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 import os
 from unittest import mock
 
@@ -21,7 +19,7 @@ from lpbuildd.tests.matchers import HasWaitingFiles
 
 class MockBuildManager(CharmBuildManager):
     def __init__(self, *args, **kwargs):
-        super(MockBuildManager, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         self.commands = []
         self.iterators = []
 
@@ -39,7 +37,7 @@ class TestCharmBuildManagerIteration(TestCase):
     run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=5)
 
     def setUp(self):
-        super(TestCharmBuildManagerIteration, self).setUp()
+        super().setUp()
         self.working_dir = self.useFixture(TempDir()).path
         builder_dir = os.path.join(self.working_dir, "builder")
         home_dir = os.path.join(self.working_dir, "home")
diff --git a/lpbuildd/tests/test_check_implicit_pointer_functions.py b/lpbuildd/tests/test_check_implicit_pointer_functions.py
index 8d604ec..1f70eb8 100644
--- a/lpbuildd/tests/test_check_implicit_pointer_functions.py
+++ b/lpbuildd/tests/test_check_implicit_pointer_functions.py
@@ -113,7 +113,7 @@ class TestFilterLog(TestCase):
 class TestCheckImplicitPointerFunctionsScript(TestCase):
 
     def setUp(self):
-        super(TestCheckImplicitPointerFunctionsScript, self).setUp()
+        super().setUp()
         top = os.path.dirname(
             os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
         self.script = os.path.join(
diff --git a/lpbuildd/tests/test_ci.py b/lpbuildd/tests/test_ci.py
index fee8c01..fca9698 100644
--- a/lpbuildd/tests/test_ci.py
+++ b/lpbuildd/tests/test_ci.py
@@ -1,8 +1,6 @@
 # Copyright 2022 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 import os
 import shutil
 
@@ -29,7 +27,7 @@ from lpbuildd.tests.matchers import HasWaitingFiles
 
 class MockBuildManager(CIBuildManager):
     def __init__(self, *args, **kwargs):
-        super(MockBuildManager, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         self.commands = []
         self.iterators = []
 
@@ -47,7 +45,7 @@ class TestCIBuildManagerIteration(TestCase):
     run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=5)
 
     def setUp(self):
-        super(TestCIBuildManagerIteration, self).setUp()
+        super().setUp()
         self.working_dir = self.useFixture(TempDir()).path
         builder_dir = os.path.join(self.working_dir, "builder")
         home_dir = os.path.join(self.working_dir, "home")
diff --git a/lpbuildd/tests/test_debian.py b/lpbuildd/tests/test_debian.py
index 5a6d94b..0217c1a 100644
--- a/lpbuildd/tests/test_debian.py
+++ b/lpbuildd/tests/test_debian.py
@@ -1,8 +1,6 @@
 # Copyright 2017-2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 import base64
 import os.path
 import shutil
@@ -28,7 +26,7 @@ class MockBuildManager(DebianBuildManager):
     initial_build_state = MockBuildState.MAIN
 
     def __init__(self, *args, **kwargs):
-        super(MockBuildManager, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         self.commands = []
         self.iterators = []
         self.arch_indep = False
@@ -59,7 +57,7 @@ class TestDebianBuildManagerIteration(TestCase):
     """Run a generic DebianBuildManager through its iteration steps."""
 
     def setUp(self):
-        super(TestDebianBuildManagerIteration, self).setUp()
+        super().setUp()
         self.working_dir = tempfile.mkdtemp()
         self.addCleanup(lambda: shutil.rmtree(self.working_dir))
         builder_dir = os.path.join(self.working_dir, 'builder')
diff --git a/lpbuildd/tests/test_harness.py b/lpbuildd/tests/test_harness.py
index 0ea496b..e73220a 100644
--- a/lpbuildd/tests/test_harness.py
+++ b/lpbuildd/tests/test_harness.py
@@ -1,8 +1,6 @@
 # Copyright 2009 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 import doctest
 
 
diff --git a/lpbuildd/tests/test_livefs.py b/lpbuildd/tests/test_livefs.py
index d4dbed7..d121837 100644
--- a/lpbuildd/tests/test_livefs.py
+++ b/lpbuildd/tests/test_livefs.py
@@ -1,8 +1,6 @@
 # Copyright 2013-2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 import os
 
 from fixtures import (
@@ -23,7 +21,7 @@ from lpbuildd.tests.matchers import HasWaitingFiles
 
 class MockBuildManager(LiveFilesystemBuildManager):
     def __init__(self, *args, **kwargs):
-        super(MockBuildManager, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         self.commands = []
         self.iterators = []
 
@@ -41,7 +39,7 @@ class TestLiveFilesystemBuildManagerIteration(TestCase):
     run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=5)
 
     def setUp(self):
-        super(TestLiveFilesystemBuildManagerIteration, self).setUp()
+        super().setUp()
         self.working_dir = self.useFixture(TempDir()).path
         builder_dir = os.path.join(self.working_dir, "builder")
         home_dir = os.path.join(self.working_dir, "home")
diff --git a/lpbuildd/tests/test_oci.py b/lpbuildd/tests/test_oci.py
index 7fc0b3a..69e0e4a 100644
--- a/lpbuildd/tests/test_oci.py
+++ b/lpbuildd/tests/test_oci.py
@@ -1,10 +1,7 @@
 # Copyright 2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 from collections import OrderedDict
-import io
 import json
 import os
 
@@ -28,7 +25,7 @@ from lpbuildd.tests.oci_tarball import OCITarball
 
 class MockBuildManager(OCIBuildManager):
     def __init__(self, *args, **kwargs):
-        super(MockBuildManager, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         self.commands = []
         self.iterators = []
 
@@ -44,7 +41,7 @@ class MockOCITarSave():
     @property
     def stdout(self):
         tar_path = OCITarball().build_tar_file()
-        return io.open(tar_path, 'rb')
+        return open(tar_path, 'rb')
 
 
 class TestOCIBuildManagerIteration(TestCase):
@@ -53,7 +50,7 @@ class TestOCIBuildManagerIteration(TestCase):
     run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=5)
 
     def setUp(self):
-        super(TestOCIBuildManagerIteration, self).setUp()
+        super().setUp()
         self.working_dir = self.useFixture(TempDir()).path
         builder_dir = os.path.join(self.working_dir, "builder")
         home_dir = os.path.join(self.working_dir, "home")
diff --git a/lpbuildd/tests/test_snap.py b/lpbuildd/tests/test_snap.py
index 6fec8e5..59f0446 100644
--- a/lpbuildd/tests/test_snap.py
+++ b/lpbuildd/tests/test_snap.py
@@ -1,8 +1,6 @@
 # Copyright 2015-2019 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 import os
 from unittest import mock
 
@@ -37,7 +35,7 @@ from lpbuildd.tests.matchers import HasWaitingFiles
 
 class MockBuildManager(SnapBuildManager):
     def __init__(self, *args, **kwargs):
-        super(MockBuildManager, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         self.commands = []
         self.iterators = []
 
@@ -55,7 +53,7 @@ class TestSnapBuildManagerIteration(TestCase):
     run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=5)
 
     def setUp(self):
-        super(TestSnapBuildManagerIteration, self).setUp()
+        super().setUp()
         self.working_dir = self.useFixture(TempDir()).path
         builder_dir = os.path.join(self.working_dir, "builder")
         home_dir = os.path.join(self.working_dir, "home")
diff --git a/lpbuildd/tests/test_sourcepackagerecipe.py b/lpbuildd/tests/test_sourcepackagerecipe.py
index 7a2cb48..2cf7fd3 100644
--- a/lpbuildd/tests/test_sourcepackagerecipe.py
+++ b/lpbuildd/tests/test_sourcepackagerecipe.py
@@ -1,8 +1,6 @@
 # Copyright 2013-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 import os
 import shutil
 import tempfile
@@ -23,7 +21,7 @@ from lpbuildd.tests.matchers import HasWaitingFiles
 
 class MockBuildManager(SourcePackageRecipeBuildManager):
     def __init__(self, *args, **kwargs):
-        super(MockBuildManager, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         self.commands = []
         self.iterators = []
 
@@ -41,7 +39,7 @@ class TestSourcePackageRecipeBuildManagerIteration(TestCase):
     run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=5)
 
     def setUp(self):
-        super(TestSourcePackageRecipeBuildManagerIteration, self).setUp()
+        super().setUp()
         self.working_dir = tempfile.mkdtemp()
         self.addCleanup(lambda: shutil.rmtree(self.working_dir))
         builder_dir = os.path.join(self.working_dir, 'builder')
@@ -98,7 +96,7 @@ class TestSourcePackageRecipeBuildManagerIteration(TestCase):
             expected_command.append('--git')
         expected_command.extend([
             self.buildid,
-            'Steve\u1234'.encode('utf-8'), 'stevea@xxxxxxxxxxx',
+            'Steve\u1234'.encode(), 'stevea@xxxxxxxxxxx',
             'maverick', 'maverick', 'universe', 'puppies',
             ])
         self.assertEqual(expected_command, self.buildmanager.commands[-1])
diff --git a/lpbuildd/tests/test_translationtemplatesbuildmanager.py b/lpbuildd/tests/test_translationtemplatesbuildmanager.py
index 196f4d8..37103cd 100644
--- a/lpbuildd/tests/test_translationtemplatesbuildmanager.py
+++ b/lpbuildd/tests/test_translationtemplatesbuildmanager.py
@@ -1,8 +1,6 @@
 # Copyright 2010-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 import os
 
 from fixtures import (
@@ -27,7 +25,7 @@ from lpbuildd.translationtemplates import (
 
 class MockBuildManager(TranslationTemplatesBuildManager):
     def __init__(self, *args, **kwargs):
-        super(MockBuildManager, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
         self.commands = []
         self.iterators = []
 
@@ -45,7 +43,7 @@ class TestTranslationTemplatesBuildManagerIteration(TestCase):
     run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=5)
 
     def setUp(self):
-        super(TestTranslationTemplatesBuildManagerIteration, self).setUp()
+        super().setUp()
         self.working_dir = self.useFixture(TempDir()).path
         builder_dir = os.path.join(self.working_dir, 'builder')
         home_dir = os.path.join(self.working_dir, 'home')
diff --git a/lpbuildd/tests/test_util.py b/lpbuildd/tests/test_util.py
index 8b04d44..5aafbfa 100644
--- a/lpbuildd/tests/test_util.py
+++ b/lpbuildd/tests/test_util.py
@@ -1,8 +1,6 @@
 # Copyright 2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 from testtools import TestCase
 
 from lpbuildd.util import (
@@ -26,8 +24,8 @@ class TestShellEscape(TestCase):
 
     def test_bytes(self):
         self.assertEqual(
-            u"'\N{SNOWMAN}'".encode("UTF-8"),
-            shell_escape(u"\N{SNOWMAN}".encode("UTF-8")))
+            "'\N{SNOWMAN}'".encode(),
+            shell_escape("\N{SNOWMAN}".encode()))
 
 
 class TestGetArchBits(TestCase):
diff --git a/lpbuildd/translationtemplates.py b/lpbuildd/translationtemplates.py
index 36f4335..86341ac 100644
--- a/lpbuildd/translationtemplates.py
+++ b/lpbuildd/translationtemplates.py
@@ -1,8 +1,6 @@
 # Copyright 2010-2018 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 import os
 
 from lpbuildd.debian import (
@@ -30,8 +28,7 @@ class TranslationTemplatesBuildManager(DebianBuildManager):
     initial_build_state = TranslationTemplatesBuildState.GENERATE
 
     def __init__(self, builder, buildid):
-        super(TranslationTemplatesBuildManager, self).__init__(
-            builder, buildid)
+        super().__init__(builder, buildid)
         self._resultname = builder._config.get(
             "translationtemplatesmanager", "resultarchive")
 
@@ -45,8 +42,7 @@ class TranslationTemplatesBuildManager(DebianBuildManager):
         self.git_repository = extra_args.get("git_repository")
         self.git_path = extra_args.get("git_path")
 
-        super(TranslationTemplatesBuildManager, self).initiate(
-            files, chroot, extra_args)
+        super().initiate(files, chroot, extra_args)
 
     def doGenerate(self):
         """Generate templates."""
diff --git a/lpbuildd/util.py b/lpbuildd/util.py
index 28b16ab..ae28f03 100644
--- a/lpbuildd/util.py
+++ b/lpbuildd/util.py
@@ -1,8 +1,6 @@
 # Copyright 2015-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__metaclass__ = type
-
 import os
 try:
     from shlex import quote