← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:annotate-build-args into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:annotate-build-args into launchpad:master with ~cjwatson/launchpad:mypy-buildmaster as a prerequisite.

Commit message:
Add type annotations for arguments sent to builders

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

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

It's difficult to do a completely accurate job of this with Python 3.5, but we can get most of the benefit (in particular, protection against simple typos and against passing the wrong type for an argument) from a single `TypedDict`.  The annotation is long and complicated because the protocol has evolved in quite an ad-hoc way for many years, and a number of the arguments were difficult to describe concisely without context; still, the exercise of going through the whole thing was quite a useful one and found a few forgotten deprecations to clean up.

There should be no functional change from this commit.

Dependencies MP: https://code.launchpad.net/~cjwatson/lp-source-dependencies/+git/lp-source-dependencies/+merge/435138
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:annotate-build-args into launchpad:master.
diff --git a/lib/lp/buildmaster/builderproxy.py b/lib/lp/buildmaster/builderproxy.py
index a49a251..2182194 100644
--- a/lib/lp/buildmaster/builderproxy.py
+++ b/lib/lp/buildmaster/builderproxy.py
@@ -16,11 +16,13 @@ __all__ = [
 
 import base64
 import time
+from typing import Dict, Generator
 
 from twisted.internet import defer
 
 from lp.buildmaster.downloader import RequestProxyTokenCommand
 from lp.buildmaster.interfaces.builder import CannotBuild
+from lp.buildmaster.interfaces.buildfarmjobbehaviour import BuildArgs
 from lp.services.config import config
 
 
@@ -33,7 +35,9 @@ class BuilderProxyMixin:
     """Methods for handling builds with the Snap Build Proxy enabled."""
 
     @defer.inlineCallbacks
-    def addProxyArgs(self, args, allow_internet=True):
+    def addProxyArgs(
+        self, args: BuildArgs, allow_internet: bool = True
+    ) -> Generator[None, Dict[str, str], None]:
         if _get_proxy_config("builder_proxy_host") and allow_internet:
             token = yield self._requestProxyToken()
             args[
diff --git a/lib/lp/buildmaster/interfaces/buildfarmjobbehaviour.py b/lib/lp/buildmaster/interfaces/buildfarmjobbehaviour.py
index 56bd4cf..9f847a7 100644
--- a/lib/lp/buildmaster/interfaces/buildfarmjobbehaviour.py
+++ b/lib/lp/buildmaster/interfaces/buildfarmjobbehaviour.py
@@ -4,11 +4,155 @@
 """Interface for build farm job behaviours."""
 
 __all__ = [
+    "BuildArgs",
     "IBuildFarmJobBehaviour",
 ]
 
+from typing import Any, Dict, Generator, List, Union
+
+from typing_extensions import TypedDict
 from zope.interface import Attribute, Interface
 
+# XXX cjwatson 2023-01-04: This should ultimately end up as a protocol
+# specification maintained in launchpad-buildd as (probably) pydantic
+# models, but this is difficult while Launchpad runs on Python < 3.7.
+# XXX cjwatson 2023-01-04: Several of these items are only valid for certain
+# build job types; however, TypedDict only supports inheritance if you're
+# using the class-based syntax, which isn't available in Python 3.5.  As a
+# result, it's difficult to construct a completely accurate type declaration
+# on Python 3.5.  In the meantime, job type constraints are noted in the
+# comments with the type name (i.e. `IBuildFarmJobBehaviour.builder_type`)
+# in brackets.
+BuildArgs = TypedDict(
+    "BuildArgs",
+    {
+        # True if this build should build architecture-independent packages
+        # as well as architecture-dependent packages [binarypackage].
+        "arch_indep": bool,
+        # The architecture tag to build for.
+        "arch_tag": str,
+        # Whether this is a build in a private archive.  (This causes URLs
+        # in the build log to be sanitized.)
+        "archive_private": bool,
+        # The name of the target archive's purpose, e.g. PRIMARY or PPA
+        # [binarypackage; required for sourcepackagerecipe].
+        "archive_purpose": str,
+        # A list of sources.list lines to use for this build.
+        "archives": List[str],
+        # The email address of the person who requested the recipe build
+        # [required for sourcepackagerecipe].
+        "author_email": str,
+        # The name of the person who requested the recipe build [required
+        # for sourcepackagerecipe].
+        "author_name": str,
+        # The URL of the Bazaar branch to build from [charm, ci, oci, snap,
+        # translation-templates].
+        "branch": str,
+        # The URL of the Bazaar branch to build from
+        # [translation-templates].  Deprecated alias for branch.
+        "branch_url": str,
+        # ARG variables to pass when building this OCI recipe [oci].
+        "build_args": Dict[str, str],
+        # If True, this build should also build debug symbol packages
+        # [binarypackage].
+        "build_debug_symbols": bool,
+        # The relative path to the build file within this recipe's branch
+        # [oci].
+        "build_file": str,
+        # The subdirectory within this recipe's branch containing the build
+        # file [charm, oci].
+        "build_path": str,
+        # The ID of the build request that prompted this build [snap].
+        "build_request_id": int,
+        # The RFC3339-formatted time when the build request that prompted
+        # this build was made [snap].
+        "build_request_timestamp": str,
+        # If True, also build a tarball containing all source code [snap].
+        "build_source_tarball": bool,
+        # The URL of this build.
+        "build_url": str,
+        # Source snap channels to use for this build [charm, ci, snap].
+        "channels": Dict[str, str],
+        # The date stamp to set in the built image [livefs].
+        "datestamp": str,
+        # The name of the distribution to build for [no longer used, but
+        # currently required for binarypackage].
+        "distribution": str,
+        # The name of the series to build for [sourcepackagerecipe].
+        # XXX cjwatson 2017-07-26: This duplicates "series", which is common
+        # to all build types; this name for it is deprecated and should be
+        # removed once launchpad-buildd no longer requires it.
+        "distroseries_name": str,
+        # A dictionary of additional environment variables to pass to the CI
+        # build runner [ci].
+        "environment_variables": Dict[str, str],
+        # If True, this build is running in an ephemeral environment; skip
+        # final cleanup steps.
+        "fast_cleanup": bool,
+        # True if this build is for a Git-based source package recipe,
+        # otherwise False [sourcepackagerecipe].
+        "git": bool,
+        # The Git branch path to build from [charm, ci, oci, snap,
+        # translation-templates].
+        "git_path": str,
+        # The URL of the Git repository to build from [charm, ci, oci, snap,
+        # translation-templates].
+        "git_repository": str,
+        # A list of stages in this build's configured pipeline [required for
+        # ci].
+        "jobs": List[str],
+        # Dictionary of additional metadata to pass to the build [oci].
+        # XXX cjwatson 2023-01-04: This doesn't appear to be used by
+        # launchpad-buildd at the moment.
+        "metadata": Dict[str, Any],
+        # The name of the recipe [required for charm, oci, snap].
+        "name": str,
+        # The name of the component to build for [required for
+        # binarypackage, sourcepackagerecipe].  This argument has a strange
+        # name due to a historical in-joke: because components form a sort
+        # of layered structure where "outer" components like universe
+        # include "inner" components like main, the component structure was
+        # at one point referred to as the "ogre model" (from the movie
+        # "Shrek": "Ogres have layers.  Onions have layers.  You get it?  We
+        # both have layers.").
+        "ogrecomponent": str,
+        # A list of sources.list lines for the CI build runner to use [ci].
+        "package_repositories": List[str],
+        # A dictionary of plugin settings to pass to the CI build runner
+        # [ci].
+        "plugin_settings": Dict[str, str],
+        # The lower-cased name of the pocket to build from [required for
+        # livefs].
+        "pocket": str,
+        # If True, the source of this build is private [snap; also passed
+        # for charm and ci but currently unused there].
+        "private": bool,
+        # The URL of the proxy for internet access [charm, ci, oci, snap].
+        "proxy_url": str,
+        # The text of the recipe to build [required for
+        # sourcepackagerecipe].
+        "recipe_text": str,
+        # The URL for revoking proxy authorization tokens [charm, ci, oci,
+        # snap].
+        "revocation_endpoint": str,
+        # If True, scan job output for malware [ci].
+        "scan_malware": bool,
+        # A dictionary of secrets to pass to the CI build runner [ci].
+        "secrets": Dict[str, str],
+        # The name of the series to build for [required for all types].
+        "series": str,
+        # The name of the suite to build for [required for binarypackage,
+        # sourcepackagerecipe].
+        "suite": str,
+        # A list of target architecture tags to build for [snap].
+        "target_architectures": List[str],
+        # A list of base64-encoded public keys for apt archives used by this
+        # build.
+        "trusted_keys": List[str],
+    },
+    total=False,
+)
+
 
 class IBuildFarmJobBehaviour(Interface):
 
@@ -53,7 +197,9 @@ class IBuildFarmJobBehaviour(Interface):
             fault.
         """
 
-    def extraBuildArgs(logger=None):
+    def extraBuildArgs(
+        logger=None,
+    ) -> Union[BuildArgs, Generator[Any, Any, BuildArgs]]:
         """Return extra arguments required by the builder for this build.
 
         :param logger: An optional logger.
diff --git a/lib/lp/buildmaster/model/buildfarmjobbehaviour.py b/lib/lp/buildmaster/model/buildfarmjobbehaviour.py
index 25f4678..e2bc1d8 100644
--- a/lib/lp/buildmaster/model/buildfarmjobbehaviour.py
+++ b/lib/lp/buildmaster/model/buildfarmjobbehaviour.py
@@ -25,6 +25,7 @@ from lp.buildmaster.enums import (
     BuildStatus,
 )
 from lp.buildmaster.interfaces.builder import BuildDaemonError, CannotBuild
+from lp.buildmaster.interfaces.buildfarmjobbehaviour import BuildArgs
 from lp.registry.interfaces.pocket import PackagePublishingPocket
 from lp.services.config import config
 from lp.services.helpers import filenameToContentType
@@ -93,15 +94,15 @@ class BuildFarmJobBehaviourBase:
             "This build type does not support accessing private resources."
         )
 
-    def extraBuildArgs(self, logger=None):
+    def extraBuildArgs(self, logger=None) -> BuildArgs:
         """The default behaviour is to send only common extra arguments."""
-        args = {}
-        args["arch_tag"] = self.distro_arch_series.architecturetag
-        args["archive_private"] = self.archive.private
-        args["build_url"] = canonical_url(self.build)
-        args["fast_cleanup"] = self._builder.virtualized
-        args["series"] = self.distro_arch_series.distroseries.name
-        return args
+        return {
+            "arch_tag": self.distro_arch_series.architecturetag,
+            "archive_private": self.archive.private,
+            "build_url": canonical_url(self.build),
+            "fast_cleanup": self._builder.virtualized,
+            "series": self.distro_arch_series.distroseries.name,
+        }
 
     @defer.inlineCallbacks
     def composeBuildRequest(self, logger):
diff --git a/lib/lp/charms/model/charmrecipebuildbehaviour.py b/lib/lp/charms/model/charmrecipebuildbehaviour.py
index fffe7cf..9e95250 100644
--- a/lib/lp/charms/model/charmrecipebuildbehaviour.py
+++ b/lib/lp/charms/model/charmrecipebuildbehaviour.py
@@ -10,6 +10,8 @@ __all__ = [
     "CharmRecipeBuildBehaviour",
 ]
 
+from typing import Any, Generator
+
 from twisted.internet import defer
 from zope.component import adapter
 from zope.interface import implementer
@@ -19,6 +21,7 @@ from lp.buildmaster.builderproxy import BuilderProxyMixin
 from lp.buildmaster.enums import BuildBaseImageType
 from lp.buildmaster.interfaces.builder import CannotBuild
 from lp.buildmaster.interfaces.buildfarmjobbehaviour import (
+    BuildArgs,
     IBuildFarmJobBehaviour,
 )
 from lp.buildmaster.model.buildfarmjobbehaviour import (
@@ -70,12 +73,12 @@ class CharmRecipeBuildBehaviour(BuilderProxyMixin, BuildFarmJobBehaviourBase):
             )
 
     @defer.inlineCallbacks
-    def extraBuildArgs(self, logger=None):
+    def extraBuildArgs(self, logger=None) -> Generator[Any, Any, BuildArgs]:
         """
         Return the extra arguments required by the worker for the given build.
         """
         build = self.build
-        args = yield super().extraBuildArgs(logger=logger)
+        args = yield super().extraBuildArgs(logger=logger)  # type: BuildArgs
         yield self.addProxyArgs(args)
         args["name"] = build.recipe.store_name or build.recipe.name
         channels = build.channels or {}
diff --git a/lib/lp/code/model/cibuildbehaviour.py b/lib/lp/code/model/cibuildbehaviour.py
index 6a50dd8..def2e95 100644
--- a/lib/lp/code/model/cibuildbehaviour.py
+++ b/lib/lp/code/model/cibuildbehaviour.py
@@ -10,6 +10,7 @@ __all__ = [
 import json
 from configparser import NoSectionError
 from copy import deepcopy
+from typing import Any, Generator
 
 from twisted.internet import defer
 from zope.component import adapter
@@ -20,6 +21,7 @@ from lp.buildmaster.builderproxy import BuilderProxyMixin
 from lp.buildmaster.enums import BuildBaseImageType
 from lp.buildmaster.interfaces.builder import CannotBuild
 from lp.buildmaster.interfaces.buildfarmjobbehaviour import (
+    BuildArgs,
     IBuildFarmJobBehaviour,
 )
 from lp.buildmaster.model.buildfarmjobbehaviour import (
@@ -164,7 +166,7 @@ class CIBuildBehaviour(BuilderProxyMixin, BuildFarmJobBehaviourBase):
         )
 
     @defer.inlineCallbacks
-    def extraBuildArgs(self, logger=None):
+    def extraBuildArgs(self, logger=None) -> Generator[Any, Any, BuildArgs]:
         """Return extra builder arguments for this build."""
         build = self.build
         if not build.stages:
@@ -173,7 +175,7 @@ class CIBuildBehaviour(BuilderProxyMixin, BuildFarmJobBehaviourBase):
                 % (build.git_repository.unique_name, build.commit_sha1)
             )
 
-        args = yield super().extraBuildArgs(logger=logger)
+        args = yield super().extraBuildArgs(logger=logger)  # type: BuildArgs
         yield self.addProxyArgs(args)
         (
             args["archives"],
diff --git a/lib/lp/code/model/recipebuilder.py b/lib/lp/code/model/recipebuilder.py
index 0ca91c0..9b6e49a 100644
--- a/lib/lp/code/model/recipebuilder.py
+++ b/lib/lp/code/model/recipebuilder.py
@@ -7,6 +7,8 @@ __all__ = [
     "RecipeBuildBehaviour",
 ]
 
+from typing import Any, Generator
+
 from twisted.internet import defer
 from zope.component import adapter
 from zope.interface import implementer
@@ -14,6 +16,7 @@ from zope.security.proxy import removeSecurityProxy
 
 from lp.buildmaster.interfaces.builder import CannotBuild
 from lp.buildmaster.interfaces.buildfarmjobbehaviour import (
+    BuildArgs,
     IBuildFarmJobBehaviour,
 )
 from lp.buildmaster.model.buildfarmjobbehaviour import (
@@ -53,7 +56,7 @@ class RecipeBuildBehaviour(BuildFarmJobBehaviourBase):
             return None
 
     @defer.inlineCallbacks
-    def extraBuildArgs(self, logger=None):
+    def extraBuildArgs(self, logger=None) -> Generator[Any, Any, BuildArgs]:
         """
         Return the extra arguments required by the worker for the given build.
         """
@@ -67,7 +70,7 @@ class RecipeBuildBehaviour(BuildFarmJobBehaviourBase):
             )
 
         # Build extra arguments.
-        args = yield super().extraBuildArgs(logger=logger)
+        args = yield super().extraBuildArgs(logger=logger)  # type: BuildArgs
         args["suite"] = self.build.distroseries.getSuite(self.build.pocket)
         requester = self.build.requester
         if requester.preferredemail is None:
diff --git a/lib/lp/oci/model/ocirecipebuildbehaviour.py b/lib/lp/oci/model/ocirecipebuildbehaviour.py
index d35259c..5f026df 100644
--- a/lib/lp/oci/model/ocirecipebuildbehaviour.py
+++ b/lib/lp/oci/model/ocirecipebuildbehaviour.py
@@ -14,6 +14,7 @@ __all__ = [
 import json
 import os
 from datetime import datetime
+from typing import Any, Generator
 
 import pytz
 from twisted.internet import defer
@@ -25,6 +26,7 @@ from lp.buildmaster.builderproxy import BuilderProxyMixin
 from lp.buildmaster.enums import BuildBaseImageType
 from lp.buildmaster.interfaces.builder import BuildDaemonError, CannotBuild
 from lp.buildmaster.interfaces.buildfarmjobbehaviour import (
+    BuildArgs,
     IBuildFarmJobBehaviour,
 )
 from lp.buildmaster.model.buildfarmjobbehaviour import (
@@ -130,12 +132,12 @@ class OCIRecipeBuildBehaviour(BuilderProxyMixin, BuildFarmJobBehaviourBase):
         return info
 
     @defer.inlineCallbacks
-    def extraBuildArgs(self, logger=None):
+    def extraBuildArgs(self, logger=None) -> Generator[Any, Any, BuildArgs]:
         """
         Return the extra arguments required by the worker for the given build.
         """
         build = self.build
-        args = yield super().extraBuildArgs(logger=logger)
+        args = yield super().extraBuildArgs(logger=logger)  # type: BuildArgs
         yield self.addProxyArgs(args, build.recipe.allow_internet)
         # XXX twom 2020-02-17 This may need to be more complex, and involve
         # distribution name.
diff --git a/lib/lp/snappy/model/snapbuildbehaviour.py b/lib/lp/snappy/model/snapbuildbehaviour.py
index 670eb71..7d40251 100644
--- a/lib/lp/snappy/model/snapbuildbehaviour.py
+++ b/lib/lp/snappy/model/snapbuildbehaviour.py
@@ -10,7 +10,7 @@ __all__ = [
     "SnapBuildBehaviour",
 ]
 
-import typing
+from typing import Any, Generator
 
 from twisted.internet import defer
 from zope.component import adapter
@@ -21,6 +21,7 @@ from lp.buildmaster.builderproxy import BuilderProxyMixin
 from lp.buildmaster.enums import BuildBaseImageType
 from lp.buildmaster.interfaces.builder import CannotBuild
 from lp.buildmaster.interfaces.buildfarmjobbehaviour import (
+    BuildArgs,
     IBuildFarmJobBehaviour,
 )
 from lp.buildmaster.model.buildfarmjobbehaviour import (
@@ -109,12 +110,12 @@ class SnapBuildBehaviour(BuilderProxyMixin, BuildFarmJobBehaviourBase):
         )
 
     @defer.inlineCallbacks
-    def extraBuildArgs(self, logger=None) -> typing.Dict[str, typing.Any]:
+    def extraBuildArgs(self, logger=None) -> Generator[Any, Any, BuildArgs]:
         """
         Return the extra arguments required by the worker for the given build.
         """
         build = self.build  # type: ISnapBuild
-        args = yield super().extraBuildArgs(logger=logger)
+        args = yield super().extraBuildArgs(logger=logger)  # type: BuildArgs
         yield self.addProxyArgs(args, build.snap.allow_internet)
         args["name"] = build.snap.store_name or build.snap.name
         channels = build.channels or {}
diff --git a/lib/lp/soyuz/model/binarypackagebuildbehaviour.py b/lib/lp/soyuz/model/binarypackagebuildbehaviour.py
index 3878a0f..a038114 100644
--- a/lib/lp/soyuz/model/binarypackagebuildbehaviour.py
+++ b/lib/lp/soyuz/model/binarypackagebuildbehaviour.py
@@ -8,12 +8,14 @@ __all__ = [
 ]
 
 from collections import OrderedDict
+from typing import Any, Generator
 
 from twisted.internet import defer
 from zope.interface import implementer
 
 from lp.buildmaster.interfaces.builder import CannotBuild
 from lp.buildmaster.interfaces.buildfarmjobbehaviour import (
+    BuildArgs,
     IBuildFarmJobBehaviour,
 )
 from lp.buildmaster.model.buildfarmjobbehaviour import (
@@ -151,7 +153,7 @@ class BinaryPackageBuildBehaviour(BuildFarmJobBehaviourBase):
         )
 
     @defer.inlineCallbacks
-    def extraBuildArgs(self, logger=None):
+    def extraBuildArgs(self, logger=None) -> Generator[Any, Any, BuildArgs]:
         """
         Return the extra arguments required by the worker for the given build.
         """
@@ -159,7 +161,7 @@ class BinaryPackageBuildBehaviour(BuildFarmJobBehaviourBase):
         das = build.distro_arch_series
 
         # Build extra arguments.
-        args = yield super().extraBuildArgs(logger=logger)
+        args = yield super().extraBuildArgs(logger=logger)  # type: BuildArgs
         args["arch_indep"] = build.arch_indep
         args["distribution"] = das.distroseries.distribution.name
         args["suite"] = das.distroseries.getSuite(build.pocket)
diff --git a/lib/lp/soyuz/model/livefsbuildbehaviour.py b/lib/lp/soyuz/model/livefsbuildbehaviour.py
index 58aa1fc..d9f1cfd 100644
--- a/lib/lp/soyuz/model/livefsbuildbehaviour.py
+++ b/lib/lp/soyuz/model/livefsbuildbehaviour.py
@@ -10,6 +10,8 @@ __all__ = [
     "LiveFSBuildBehaviour",
 ]
 
+from typing import Any, Generator, cast
+
 from twisted.internet import defer
 from zope.component import adapter
 from zope.interface import implementer
@@ -18,6 +20,7 @@ from zope.security.proxy import removeSecurityProxy
 from lp.buildmaster.enums import BuildBaseImageType
 from lp.buildmaster.interfaces.builder import CannotBuild
 from lp.buildmaster.interfaces.buildfarmjobbehaviour import (
+    BuildArgs,
     IBuildFarmJobBehaviour,
 )
 from lp.buildmaster.model.buildfarmjobbehaviour import (
@@ -93,20 +96,25 @@ class LiveFSBuildBehaviour(BuildFarmJobBehaviourBase):
         )
 
     @defer.inlineCallbacks
-    def extraBuildArgs(self, logger=None):
+    def extraBuildArgs(self, logger=None) -> Generator[Any, Any, BuildArgs]:
         """
         Return the extra arguments required by the worker for the given build.
         """
         build = self.build
-        base_args = yield super().extraBuildArgs(logger=logger)
+        base_args = yield super().extraBuildArgs(
+            logger=logger
+        )  # type: BuildArgs
         # Non-trivial metadata values may have been security-wrapped, which
         # is pointless here and just gets in the way of xmlrpc.client
         # serialisation.
-        args = dict(removeSecurityProxy(build.livefs.metadata))
+        args = cast(
+            BuildArgs, dict(removeSecurityProxy(build.livefs.metadata))
+        )
         if build.metadata_override is not None:
             args.update(removeSecurityProxy(build.metadata_override))
         # Everything else overrides anything in the metadata.
-        args.update(base_args)
+        # https://github.com/python/mypy/issues/6462
+        args.update(base_args)  # type: ignore[typeddict-item]
         args["pocket"] = build.pocket.name.lower()
         args["datestamp"] = build.version
         (
diff --git a/lib/lp/translations/model/translationtemplatesbuildbehaviour.py b/lib/lp/translations/model/translationtemplatesbuildbehaviour.py
index f159ca2..c2a2f27 100644
--- a/lib/lp/translations/model/translationtemplatesbuildbehaviour.py
+++ b/lib/lp/translations/model/translationtemplatesbuildbehaviour.py
@@ -22,6 +22,7 @@ from zope.interface import implementer
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
 from lp.buildmaster.enums import BuildStatus
 from lp.buildmaster.interfaces.buildfarmjobbehaviour import (
+    BuildArgs,
     IBuildFarmJobBehaviour,
 )
 from lp.buildmaster.model.buildfarmjobbehaviour import (
@@ -68,7 +69,7 @@ class TranslationTemplatesBuildBehaviour(BuildFarmJobBehaviourBase):
     def pocket(self):
         return PackagePublishingPocket.RELEASE
 
-    def extraBuildArgs(self, logger=None):
+    def extraBuildArgs(self, logger=None) -> BuildArgs:
         args = super().extraBuildArgs(logger=logger)
         args["branch_url"] = self.build.branch.composePublicURL()
         return args
diff --git a/requirements/launchpad.txt b/requirements/launchpad.txt
index fe72892..a6e8a51 100644
--- a/requirements/launchpad.txt
+++ b/requirements/launchpad.txt
@@ -174,6 +174,7 @@ treq==18.6.0
 Twisted==20.3.0+lp9
 txfixtures==0.4.3
 txpkgupload==0.4
+typing-extensions==3.10.0.2
 urllib3==1.25.11
 van.testing==3.0.0
 vine==1.3.0
diff --git a/setup.cfg b/setup.cfg
index f3f51e3..f93f218 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -116,6 +116,7 @@ install_requires =
     Twisted[conch,tls]
     txfixtures
     txpkgupload
+    typing-extensions
     virtualenv-tools3
     wadllib
     WebOb
diff --git a/tox.ini b/tox.ini
index 9d78638..83877d4 100644
--- a/tox.ini
+++ b/tox.ini
@@ -27,7 +27,7 @@ commands_pre =
     {toxinidir}/scripts/update-version-info.sh
 commands =
     mypy --follow-imports=silent \
-    {posargs:lib/lp/answers lib/lp/app lib/lp/archivepublisher lib/lp/archiveuploader lib/lp/buildmaster}
+    {posargs:lib/lp/answers lib/lp/app lib/lp/archivepublisher lib/lp/archiveuploader lib/lp/buildmaster lib/lp/charms/model/charmrecipebuildbehaviour.py lib/lp/code/model/cibuildbehaviour.py lib/lp/code/model/recipebuilder.py lib/lp/oci/model/ocirecipebuildbehaviour.py lib/lp/snappy/model/snapbuildbehaviour.py lib/lp/soyuz/model/binarypackagebuildbehaviour.py lib/lp/soyuz/model/livefsbuildbehaviour.py lib/lp/translations/model/translationtemplatesbuildbehaviour.py}
 
 [testenv:docs]
 basepython = python3