← Back to team overview

canonical-ubuntu-qa team mailing list archive

[Merge] ~paride/autopkgtest-cloud:pre-commit-pylint into autopkgtest-cloud:master

 

Paride Legovini has proposed merging ~paride/autopkgtest-cloud:pre-commit-pylint into autopkgtest-cloud:master.

Commit message:
Run pylint via pre-commit and therefore in CI. Fix or disable all the pylint warnings.

Requested reviews:
  Canonical's Ubuntu QA (canonical-ubuntu-qa)

For more details, see:
https://code.launchpad.net/~paride/autopkgtest-cloud/+git/autopkgtest-cloud/+merge/445630
-- 
Your team Canonical's Ubuntu QA is requested to review the proposed merge of ~paride/autopkgtest-cloud:pre-commit-pylint into autopkgtest-cloud:master.
diff --git a/.launchpad.yaml b/.launchpad.yaml
index a744b71..bec3a7d 100755
--- a/.launchpad.yaml
+++ b/.launchpad.yaml
@@ -1,16 +1,39 @@
 pipeline:
   - pre_commit
   - build_charms
-  - lint_test
 
 jobs:
   pre_commit:
-    series: jammy
+    series: focal
     architectures: amd64
+    package-repositories:
+      - type: apt
+        formats: [deb]
+        suites: [focal]
+        ppa: paride/pre-commit-backports
+    snaps:
+      - name: yq
     packages:
       - git
       - pre-commit
-    run: pre-commit run --all-files --show-diff-on-failure
+      - pylint
+      # These are not directly declared in layer.yaml.
+      # Maybe they are brought in by some included layer?
+      - python3-systemd
+      - python3-apt
+    run-before: |
+      # Install dependencies as declared in the layer files
+      DEBIAN_FRONTEND=noninteractive \
+        apt-get install -qy \
+        $(cat charms/focal/autopkgtest-cloud-worker/layer.yaml | \
+          yq -r '.options.basic.packages[]') \
+        $(cat charms/focal/autopkgtest-cloud-worker/layer.yaml | \
+          yq -r '.options.apt.packages[]') \
+        $(cat charms/focal/autopkgtest-web/layer.yaml | \
+          yq -r '.options.apt.packages[]') \
+        $(cat charms/focal/autopkgtest-web/layer.yaml | \
+          yq -r '.options.basic.packages[]')
+    run: pre-commit run --hook-stage manual --all-files --show-diff-on-failure
   build_charms:
     series: focal
     architectures: amd64
@@ -18,8 +41,3 @@ jobs:
       - name: charmcraft
         classic: true
     run: ./ci/build_charms
-  lint_test:
-    series: focal
-    architectures: amd64
-    packages: [pylint, python3, shellcheck, yamllint]
-    run: ./ci/lint_test
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 0735120..d3bc39f 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -22,3 +22,28 @@ repos:
     rev: 5.12.0
     hooks:
       - id: isort
+  - repo: local
+    hooks:
+      # Run pylint with --disable import-error.
+      # This is meant to run on developers' machines where not all the
+      # Python modules may be installed.
+      - id: pylint
+        name: pylint
+        stages:
+          [commit-msg, post-checkout, post-commit, post-merge, post-rewrite,
+           pre-commit, pre-merge-commit, pre-push, pre-rebase,
+           prepare-commit-msg]
+        entry: pylint
+        args:
+          - "--disable=import-error"
+        language: system
+        types: [python]
+      # Run pylint without disabling import-error.
+      # This is meant to run in CI (pre-commit run --hook-stage manual).
+      # The CI environment is expected to have all the required dependencies.
+      - id: pylint
+        name: pylint (with import errors)
+        stages: [manual]
+        entry: pylint
+        language: system
+        types: [python]
diff --git a/.pylintrc b/.pylintrc
index a831f0a..ef1b3f3 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -2,4 +2,44 @@
 
 jobs=0
 
-disable=invalid-name, import-error, no-name-in-module
+disable=C
+
+[MESSAGES CONTROL]
+
+disable=
+  C,
+  I,
+  R,
+  anomalous-backslash-in-string,
+  arguments-differ,
+  broad-except,
+  broad-exception-caught,
+  duplicate-string-formatting-argument,
+  fixme,
+  global-statement,
+  global-variable-not-assigned,
+  logging-format-interpolation,
+  logging-fstring-interpolation,
+  logging-not-lazy,
+  missing-timeout,
+  possibly-unused-variable,
+  protected-access,
+  redefined-builtin,
+  redefined-outer-name,
+  unspecified-encoding,
+  unused-argument,
+
+[REPORTS]
+
+# Show just the errors, no full report
+reports=no
+score=no
+
+[TYPECHECK]
+
+ignored-modules=
+  amulet,
+  charmhelpers,
+  charms,
+  lib,
+  utils,
diff --git a/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/cleanup-instances b/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/cleanup-instances
index 727634e..024ad93 100755
--- a/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/cleanup-instances
+++ b/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/cleanup-instances
@@ -6,7 +6,6 @@ import re
 import socket
 import subprocess
 import time
-from urllib.error import HTTPError
 
 import novaclient.client
 import novaclient.exceptions
@@ -109,7 +108,6 @@ for instance in nova.servers.list():
             instance.delete()
         except novaclient.exceptions.NotFound:
             logging.warning("Couldn't delete instance: not found")
-            pass
         continue
 
     if not instance.name.startswith("adt-"):
@@ -144,7 +142,6 @@ for instance in nova.servers.list():
             )
         except novaclient.exceptions.NotFound:
             logging.warning("Couldn't delete instance: not found")
-            pass
 
     # check matching adt-run process for instance name
     try:
@@ -180,7 +177,6 @@ for instance in nova.servers.list():
                 )
             except novaclient.exceptions.NotFound:
                 logging.warning("Couldn't delete instance: not found")
-                pass
     except IndexError:
         logging.warning("instance %s has invalid name" % instance.name)
 
@@ -189,4 +185,3 @@ for instance in nova.servers.list():
             influx_client.write_points(measurements)
         except InfluxDBClientError as err:
             logging.warning("Write to InfluxDB failed: %s" % err)
-            pass
diff --git a/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/create-armhf-cluster-member b/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/create-armhf-cluster-member
index 75e4c74..c512b22 100755
--- a/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/create-armhf-cluster-member
+++ b/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/create-armhf-cluster-member
@@ -49,6 +49,7 @@ def usage():
 
 try:
     role = sys.argv[1]
+    to_join = None
     if role in ("leader", "node"):
         to_join = sys.argv[2]
 except IndexError:
diff --git a/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/filter-amqp b/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/filter-amqp
index 9ff9df2..76e51ca 100755
--- a/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/filter-amqp
+++ b/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/filter-amqp
@@ -2,9 +2,8 @@
 # Filter out AMQP requests that match a given regex
 
 import logging
-import optparse
+import optparse  # pylint: disable=deprecated-module
 import re
-import sys
 import urllib.parse
 
 import amqplib.client_0_8 as amqp
diff --git a/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/filter-amqp-dupes-upstream b/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/filter-amqp-dupes-upstream
index 9e49803..183315f 100755
--- a/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/filter-amqp-dupes-upstream
+++ b/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/filter-amqp-dupes-upstream
@@ -3,10 +3,8 @@
 
 import json
 import logging
-import optparse
+import optparse  # pylint: disable=deprecated-module
 import os
-import re
-import sys
 import urllib.parse
 from collections import defaultdict
 
@@ -99,6 +97,7 @@ def main():
         help="additionally show queue items that are not removed",
     )
 
+    # pylint: disable=unused-variable
     opts, args = parser.parse_args()
 
     logging.basicConfig(
diff --git a/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/metrics b/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/metrics
index f11d3ca..7224dd3 100755
--- a/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/metrics
+++ b/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/metrics
@@ -84,10 +84,9 @@ def get_units():
             continue
 
         try:
-            (region, arch, n) = name_cloud.split("-", -1)
+            (_, arch, _) = name_cloud.split("-", -1)
         except ValueError:
             # autopkgtest@lcy01-1.service
-            (region, n) = name_cloud.split("-", -1)
             arch = "amd64"
         (active, error) = counts.setdefault(arch, (0, 0))
 
@@ -111,7 +110,7 @@ def get_remotes():
         if not r.startswith("lxd"):
             continue
 
-        (_, arch, ip) = r.split("-", 3)
+        (_, arch, _) = r.split("-", 3)
         (cluster_active, cluster_error) = cluster_counts.setdefault(
             arch, (0, 0)
         )
diff --git a/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/run-autopkgtest b/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/run-autopkgtest
index f59de0e..2584323 100755
--- a/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/run-autopkgtest
+++ b/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/run-autopkgtest
@@ -122,7 +122,7 @@ def parse_args():
     # verify syntax of triggers
     for t in args.trigger:
         try:
-            (src, ver) = t.split("/")
+            (_, _) = t.split("/")
         except ValueError:
             parser.error(
                 'Invalid trigger format "%s", must be "sourcepkg/version"' % t
@@ -131,7 +131,7 @@ def parse_args():
     # verify syntax of PPAs
     for t in args.ppa:
         try:
-            (user, name) = t.split("/")
+            (_, _) = t.split("/")
         except ValueError:
             parser.error(
                 'Invalid ppa format "%s", must be "lpuser/ppaname"' % t
diff --git a/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/seed-new-release b/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/seed-new-release
index 6be9c96..9fe2fd3 100755
--- a/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/seed-new-release
+++ b/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/tools/seed-new-release
@@ -40,6 +40,8 @@ def copy_result(rel_path, old_release, new_release):
     to_path = new_release + rel_path
     try:
         print("Getting %s" % from_path)
+        # pylint: disable=used-before-assignment
+        # (false positive with older pylint versions)
         headers, contents = swift_con.get_object(
             "autopkgtest-" + old_release, from_path
         )
diff --git a/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/worker/worker b/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/worker/worker
index 1bbcbae..7903802 100755
--- a/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/worker/worker
+++ b/charms/focal/autopkgtest-cloud-worker/autopkgtest-cloud/worker/worker
@@ -267,6 +267,9 @@ def read_per_package_configs(cfg):
             }
 
             for entry in entries:
+                package = None
+                arch = None
+                release = None
                 try:
                     (package, arch, release) = entry.split("/", 3)
                 except ValueError:
@@ -564,9 +567,7 @@ def cleanup_and_sleep(out_dir):
     """Empty the output dir for the next run, otherwise autopkgtest complains"""
     shutil.rmtree(out_dir)
     os.mkdir(out_dir)
-    running_test = False
     time.sleep(300)
-    running_test = True
 
 
 def request(msg):
@@ -799,13 +800,6 @@ def request(msg):
                                 "PPA user %s, name %s has GPG fingerprint %s"
                                 % (ppauser, ppaname, fingerprint)
                             )
-                        except (IOError, ValueError, KeyError) as e:
-                            logging.error(
-                                'Cannot get PPA information: "%s". Consuming the request - it will be left dangling; retry once the problem is resolved.'
-                                % e
-                            )
-                            msg.channel.basic_ack(msg.delivery_tag)
-                            return
                         except HTTPError as e:
                             # It's quite common to get 503s from LP; retry a
                             # few times.
@@ -813,6 +807,13 @@ def request(msg):
                                 raise
                             logging.warning("Got error 503 from launchpad API")
                             time.sleep(10)
+                        except (IOError, ValueError, KeyError) as e:
+                            logging.error(
+                                'Cannot get PPA information: "%s". Consuming the request - it will be left dangling; retry once the problem is resolved.'
+                                % e
+                            )
+                            msg.channel.basic_ack(msg.delivery_tag)
+                            return
                         else:
                             break
                     else:
diff --git a/charms/focal/autopkgtest-cloud-worker/lib/systemd.py b/charms/focal/autopkgtest-cloud-worker/lib/systemd.py
index 6a87cbd..6038ef6 100644
--- a/charms/focal/autopkgtest-cloud-worker/lib/systemd.py
+++ b/charms/focal/autopkgtest-cloud-worker/lib/systemd.py
@@ -132,7 +132,7 @@ def get_units():
     lxd_object_paths = defaultdict(lambda: defaultdict(dict))
 
     for unit in units:
-        (name, _, _, active, _, _, object_path, _, _, _) = unit
+        (name, _, _, _, _, _, object_path, _, _, _) = unit
         if name.startswith("build-adt-image@") and name.endswith(".timer"):
             name_release_region_arch = name[16:][:-6]
             (release, region, arch) = name_release_region_arch.split("-", -1)
diff --git a/charms/focal/autopkgtest-cloud-worker/reactive/autopkgtest_cloud_worker.py b/charms/focal/autopkgtest-cloud-worker/reactive/autopkgtest_cloud_worker.py
index 12b0b66..ff2d0e1 100644
--- a/charms/focal/autopkgtest-cloud-worker/reactive/autopkgtest_cloud_worker.py
+++ b/charms/focal/autopkgtest-cloud-worker/reactive/autopkgtest_cloud_worker.py
@@ -26,7 +26,6 @@ from charms.reactive import (
     when_not,
     when_not_all,
 )
-from charms.reactive.relations import endpoint_from_flag
 from utils import UnixUser, install_autodep8
 
 AUTOPKGTEST_LOCATION = os.path.expanduser("~ubuntu/autopkgtest")
@@ -131,6 +130,7 @@ def set_up_systemd_units():
         dest = os.path.join(os.path.sep, "etc", "systemd", "system", base)
 
         def link_and_enable():
+            # pylint: disable=cell-var-from-loop
             os.symlink(unit, dest)
             if "@" not in base:
                 subprocess.check_call(["systemctl", "enable", base])
diff --git a/charms/focal/autopkgtest-web/reactive/autopkgtest_web.py b/charms/focal/autopkgtest-web/reactive/autopkgtest_web.py
index 08ec944..b2e1fcd 100644
--- a/charms/focal/autopkgtest-web/reactive/autopkgtest_web.py
+++ b/charms/focal/autopkgtest-web/reactive/autopkgtest_web.py
@@ -8,7 +8,6 @@ from charmhelpers.core.hookenv import charm_dir, config
 from charms.layer import status
 from charms.reactive import (
     clear_flag,
-    hook,
     set_flag,
     when,
     when_all,
diff --git a/charms/focal/autopkgtest-web/webcontrol/browse.cgi b/charms/focal/autopkgtest-web/webcontrol/browse.cgi
index efdc9b7..131d813 100755
--- a/charms/focal/autopkgtest-web/webcontrol/browse.cgi
+++ b/charms/focal/autopkgtest-web/webcontrol/browse.cgi
@@ -7,7 +7,6 @@ import json
 import os
 import re
 import sqlite3
-import urllib.parse
 from collections import OrderedDict
 from wsgiref.handlers import CGIHandler
 
@@ -168,7 +167,7 @@ def get_source_versions(db, release):
 
     srcs = {}
     for pkg, ver in db.execute(
-        "SELECT package, version " "FROM current_version " "WHERE release = ?",
+        "SELECT package, version FROM current_version WHERE release = ?",
         (release,),
     ):
         srcs[pkg] = ver
@@ -185,6 +184,7 @@ def success_count_for_release_and_arch(db, release, arch, src_versions):
     # but succeeded for a trigger that is not published), don't count it as
     # success
     cur_pkg = None
+    # pylint: disable=unused-variable
     for pkg, triggers, code in db.execute(
         "SELECT test.package, triggers, exitcode "
         "FROM test, result, current_version "
@@ -340,7 +340,11 @@ def running():
     for c in queue_info:
         for r in releases:
             for a in arches:
-                (queue_length, queue_items) = (
+                # pylint: disable=unused-variable
+                (
+                    queue_length,
+                    queue_items,
+                ) = (
                     queue_info.get(c, {}).get(r, {}).get(a, (0, []))
                 )
                 queue_lengths.setdefault(c, {}).setdefault(r, {})[
@@ -367,12 +371,12 @@ def running():
 
 @app.route("/queue_size.json")
 def queuesize_json():
-    out = {}
     queue_info = get_queue_info()[2]
     # Strip the number of queue items, this is just their contents
     for context in queue_info:
         for release in queue_info[context]:
             for arch in queue_info[context][release]:
+                # pylint: disable=unused-variable
                 (queue_size, queue_items) = queue_info[context][release][arch]
                 queue_info[context][release][arch] = len(queue_items)
     return json.dumps(queue_info, indent=2)
@@ -380,12 +384,12 @@ def queuesize_json():
 
 @app.route("/queues.json")
 def queues_json():
-    out = {}
     queue_info = get_queue_info()[2]
     # Strip the number of queue items, this is just their contents
     for context in queue_info:
         for release in queue_info[context]:
             for arch in queue_info[context][release]:
+                # pylint: disable=unused-variable
                 (queue_size, queue_items) = queue_info[context][release][arch]
                 queue_info[context][release][arch] = queue_items
     return flask.Response(
diff --git a/charms/focal/autopkgtest-web/webcontrol/download-all-results b/charms/focal/autopkgtest-web/webcontrol/download-all-results
index 0ae5c4e..0f6e78d 100755
--- a/charms/focal/autopkgtest-web/webcontrol/download-all-results
+++ b/charms/focal/autopkgtest-web/webcontrol/download-all-results
@@ -42,10 +42,10 @@ def list_remote_container(container_url):
             url += f"&marker={urllib.parse.quote(start)}"
 
         LOGGER.debug('Retrieving "%s"', url)
-        for retry in range(5):
+        for _ in range(5):
             try:
                 resp = urlopen(url)
-            except http.client.RemoteDisconnected as e:
+            except http.client.RemoteDisconnected:
                 LOGGER.debug("Got disconnected, sleeping")
                 time.sleep(5)
                 continue
@@ -108,7 +108,7 @@ def fetch_one_result(url):
         LOGGER.error("Failure to fetch %s: %s", url, str(e))
         # we tolerate "not found" (something went wrong on uploading the
         # result), but other things indicate infrastructure problems
-        if hasattr(e, "code") and e.code == 404:
+        if hasattr(e, "code") and e.code == 404:  # pylint: disable=no-member
             return
         sys.exit(1)
 
@@ -119,7 +119,7 @@ def fetch_one_result(url):
                 srcver = (
                     tar.extractfile("testpkg-version").read().decode().strip()
                 )
-            except KeyError as e:
+            except KeyError:
                 # not found
                 if exitcode in (4, 12, 20):
                     # repair it
@@ -137,7 +137,7 @@ def fetch_one_result(url):
                 requester = (
                     tar.extractfile("requester").read().decode().strip()
                 )
-            except KeyError as e:
+            except KeyError:
                 requester = ""
     except (KeyError, ValueError, tarfile.TarError) as e:
         LOGGER.debug("%s is damaged, ignoring: %s", url, str(e))
@@ -194,8 +194,6 @@ def fetch_one_result(url):
 def fetch_container(release, container_url):
     """Download new results from a swift container"""
 
-    marker = ""
-
     try:
         our_results = list_our_results(release)
         known_results = list_remote_container(container_url)
diff --git a/charms/focal/autopkgtest-web/webcontrol/download-results b/charms/focal/autopkgtest-web/webcontrol/download-results
index 419af47..0f14155 100755
--- a/charms/focal/autopkgtest-web/webcontrol/download-results
+++ b/charms/focal/autopkgtest-web/webcontrol/download-results
@@ -7,7 +7,6 @@ import os
 import socket
 import sqlite3
 import urllib.parse
-from urllib.request import urlopen
 
 import amqplib.client_0_8 as amqp
 from helpers.utils import get_test_id, init_db
diff --git a/charms/focal/autopkgtest-web/webcontrol/publish-db b/charms/focal/autopkgtest-web/webcontrol/publish-db
index f35c9e8..7ff7d1d 100755
--- a/charms/focal/autopkgtest-web/webcontrol/publish-db
+++ b/charms/focal/autopkgtest-web/webcontrol/publish-db
@@ -4,11 +4,9 @@
 # This is being used for statistics.
 
 import configparser
-import fcntl
 import gzip
 import logging
 import os
-import shutil
 import sqlite3
 import tempfile
 import urllib.request
@@ -177,7 +175,6 @@ def get_sources(db_con, release):
             except urllib.error.HTTPError as e:
                 if e.code == 304:
                     logging.debug("url {} not modified".format(url))
-                    pass
 
 
 if __name__ == "__main__":
diff --git a/charms/focal/autopkgtest-web/webcontrol/request/submit.py b/charms/focal/autopkgtest-web/webcontrol/request/submit.py
index bc283a7..861efb6 100644
--- a/charms/focal/autopkgtest-web/webcontrol/request/submit.py
+++ b/charms/focal/autopkgtest-web/webcontrol/request/submit.py
@@ -63,6 +63,7 @@ class Submit:
         assert self.amqp_creds.scheme == "amqp"
         logging.debug("AMQP credentials: %s" % repr(self.amqp_creds))
 
+    # pylint: disable=dangerous-default-value
     def validate_distro_request(
         self, release, arch, package, triggers, requester, ppas=[], **kwargs
     ):
@@ -132,10 +133,10 @@ class Submit:
         for trigger in triggers:
             try:
                 trigsrc, trigver = trigger.split("/")
-            except ValueError:
+            except ValueError as exc:
                 raise ValueError(
                     "Malformed trigger, must be srcpackage/version"
-                )
+                ) from exc
             # Debian Policy 5.6.1 and 5.6.12
             if not NAME.match(trigsrc) or not VERSION.match(trigver):
                 raise ValueError("Malformed trigger")
@@ -197,6 +198,7 @@ class Submit:
                 "service." % (package, trigsrc)
             )
 
+    # pylint: disable=dangerous-default-value
     def validate_git_request(
         self, release, arch, package, ppas=[], env=[], **kwargs
     ):
@@ -388,7 +390,7 @@ class Submit:
             )
         else:
             c.execute(
-                "SELECT count(arch) FROM test " "WHERE package=? AND arch=?",
+                "SELECT count(arch) FROM test WHERE package=? AND arch=?",
                 (package, arch),
             )
         return c.fetchone()[0] > 0
@@ -459,11 +461,12 @@ class Submit:
         )
         return code >= 200 and code < 300
 
+    # pylint: disable=dangerous-default-value
     def in_allowed_team(self, person, package=[], teams=[]):
         """Check if person is in ALLOWED_TEAMS"""
 
         for team in teams or ALLOWED_TEAMS:
-            (code, response) = self.lp_request("~%s/participants" % team, {})
+            (_, response) = self.lp_request("~%s/participants" % team, {})
             for e in response.get("entries", []):
                 if e.get("name") == person:
                     return True
diff --git a/charms/focal/autopkgtest-web/webcontrol/request/tests/test_app.py b/charms/focal/autopkgtest-web/webcontrol/request/tests/test_app.py
index 44067b9..42dac0c 100644
--- a/charms/focal/autopkgtest-web/webcontrol/request/tests/test_app.py
+++ b/charms/focal/autopkgtest-web/webcontrol/request/tests/test_app.py
@@ -1,3 +1,4 @@
+# pylint: disable=no-value-for-parameter,no-member
 """Test the Flask app."""
 
 import os
diff --git a/charms/focal/autopkgtest-web/webcontrol/update-github-jobs b/charms/focal/autopkgtest-web/webcontrol/update-github-jobs
index 334722f..6362803 100755
--- a/charms/focal/autopkgtest-web/webcontrol/update-github-jobs
+++ b/charms/focal/autopkgtest-web/webcontrol/update-github-jobs
@@ -21,7 +21,7 @@ external_url = None
 
 def result_matches_job(result_url, params):
     # download result.tar and get exit code and testinfo
-    for retry in range(5):
+    for _ in range(5):
         try:
             with urllib.request.urlopen(result_url + "/result.tar") as f:
                 tar_bytes = io.BytesIO(f.read())
diff --git a/mojo/add-floating-ip b/mojo/add-floating-ip
index 57c9839..8b1c32b 100755
--- a/mojo/add-floating-ip
+++ b/mojo/add-floating-ip
@@ -94,14 +94,14 @@ def get_unit_floating_ip(unit_name):
         try:
             # Get the existing one
             fip = nova_tenant.floating_ips.find(ip=myip)
-        except Exception:
+        except Exception as exc:
             # If this happens you're going to need to either get that back in the list,
             # or blow away the state file so it gets a new IP.
             raise (
                 RuntimeError(
                     "Desired IP {} not in floating ips list!".format(myip)
                 )
-            )
+            ) from exc
 
     if fip.instance_id:
         # If it's already associated, ensure it's associated to us

Follow ups