← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:upgrade-type-annotations into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:upgrade-type-annotations into launchpad:master.

Commit message:
Upgrade variable type annotations to 3.6+ style

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

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

I haven't quite got as far as upgrading `flake8` since it reports a few other issues, but this gets us pretty close.

I had to add `Optional` to a few more places since otherwise `mypy` complained.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:upgrade-type-annotations into launchpad:master.
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 8f1b599..05418f1 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -51,8 +51,6 @@ repos:
         name: isort
         args: [--profile, black]
 -   repo: https://github.com/PyCQA/flake8
-    # 6.0.0 drops support for `# type:` comments, which we need until we
-    # upgrade to Python >= 3.6.
     rev: 5.0.4
     hooks:
     -   id: flake8
diff --git a/lib/lp/answers/adapters.py b/lib/lp/answers/adapters.py
index 8a3865a..2c19616 100644
--- a/lib/lp/answers/adapters.py
+++ b/lib/lp/answers/adapters.py
@@ -7,7 +7,7 @@ from typing import List
 
 from lp.answers.interfaces.faqtarget import IFAQTarget
 
-__all__ = []  # type: List[str]
+__all__: List[str] = []
 
 
 def question_to_questiontarget(question):
diff --git a/lib/lp/answers/browser/faqcollection.py b/lib/lp/answers/browser/faqcollection.py
index bf05b47..3eb527f 100644
--- a/lib/lp/answers/browser/faqcollection.py
+++ b/lib/lp/answers/browser/faqcollection.py
@@ -31,7 +31,7 @@ from lp.services.webapp.menu import enabled_with_permission
 class FAQCollectionMenu(NavigationMenu):
     """Base menu definition for `IFAQCollection`."""
 
-    usedfor = IFAQCollection  # type: Type[Interface]
+    usedfor: Type[Interface] = IFAQCollection
     facet = "answers"
     links = ["list_all", "create_faq"]
 
diff --git a/lib/lp/answers/browser/tests/test_question.py b/lib/lp/answers/browser/tests/test_question.py
index c1583aa..a57c08c 100644
--- a/lib/lp/answers/browser/tests/test_question.py
+++ b/lib/lp/answers/browser/tests/test_question.py
@@ -14,7 +14,7 @@ from lp.testing import TestCaseWithFactory, login_person, person_logged_in
 from lp.testing.layers import DatabaseFunctionalLayer
 from lp.testing.views import create_initialized_view
 
-__all__ = []  # type: List[str]
+__all__: List[str] = []
 
 
 class TestQuestionAddView(TestCaseWithFactory):
diff --git a/lib/lp/answers/browser/tests/test_views.py b/lib/lp/answers/browser/tests/test_views.py
index c329907..d08678b 100644
--- a/lib/lp/answers/browser/tests/test_views.py
+++ b/lib/lp/answers/browser/tests/test_views.py
@@ -10,7 +10,7 @@ from lp.testing import BrowserTestCase
 from lp.testing.layers import DatabaseFunctionalLayer
 from lp.testing.systemdocs import LayeredDocFileSuite, setUp, tearDown
 
-__all__ = []  # type: List[str]
+__all__: List[str] = []
 
 
 class TestEmailObfuscated(BrowserTestCase):
diff --git a/lib/lp/answers/mail/__init__.py b/lib/lp/answers/mail/__init__.py
index 11b146c..eb81875 100644
--- a/lib/lp/answers/mail/__init__.py
+++ b/lib/lp/answers/mail/__init__.py
@@ -2,4 +2,4 @@
 # GNU Affero General Public License version 3 (see the file LICENSE).
 from typing import List
 
-__all__ = []  # type: List[str]
+__all__: List[str] = []
diff --git a/lib/lp/answers/security.py b/lib/lp/answers/security.py
index 530b4f8..1ad66c4 100644
--- a/lib/lp/answers/security.py
+++ b/lib/lp/answers/security.py
@@ -17,7 +17,7 @@ from lp.registry.interfaces.distributionsourcepackage import (
 )
 from lp.registry.security import EditByOwnersOrAdmins
 
-__all__ = []  # type: List[str]
+__all__: List[str] = []
 
 
 class AdminQuestion(AuthorizationBase):
diff --git a/lib/lp/answers/tests/test_question_workflow.py b/lib/lp/answers/tests/test_question_workflow.py
index 2569042..28dcdeb 100644
--- a/lib/lp/answers/tests/test_question_workflow.py
+++ b/lib/lp/answers/tests/test_question_workflow.py
@@ -38,7 +38,7 @@ from lp.testing import (
 from lp.testing.fixture import ZopeEventHandlerFixture
 from lp.testing.layers import DatabaseFunctionalLayer
 
-__all__ = []  # type: List[str]
+__all__: List[str] = []
 
 
 class BaseAnswerTrackerWorkflowTestCase(TestCase):
diff --git a/lib/lp/answers/tests/test_questiontarget.py b/lib/lp/answers/tests/test_questiontarget.py
index dcaf579..f2dd83a 100644
--- a/lib/lp/answers/tests/test_questiontarget.py
+++ b/lib/lp/answers/tests/test_questiontarget.py
@@ -17,7 +17,7 @@ from lp.testing import (
 )
 from lp.testing.layers import DatabaseFunctionalLayer
 
-__all__ = []  # type: List[str]
+__all__: List[str] = []
 
 
 class QuestionTargetAnswerContactTestCase(TestCaseWithFactory):
diff --git a/lib/lp/app/__init__.py b/lib/lp/app/__init__.py
index 1df36b8..d847688 100644
--- a/lib/lp/app/__init__.py
+++ b/lib/lp/app/__init__.py
@@ -16,7 +16,7 @@ from zope.formlib import itemswidgets
 # for first page load.
 import lp.app.versioninfo  # noqa: F401
 
-__all__ = []  # type: List[str]
+__all__: List[str] = []
 
 
 # Zope recently changed the behaviour of items widgets with regards to missing
diff --git a/lib/lp/app/browser/launchpadform.py b/lib/lp/app/browser/launchpadform.py
index 119bace..4ab0239 100644
--- a/lib/lp/app/browser/launchpadform.py
+++ b/lib/lp/app/browser/launchpadform.py
@@ -61,9 +61,9 @@ class LaunchpadFormView(LaunchpadView):
     prefix = "field"
 
     # The form schema
-    schema = None  # type: Type[Interface]
+    schema: Optional[Type[Interface]] = None
     # Subset of fields to use
-    field_names = None  # type: Optional[List[str]]
+    field_names: Optional[List[str]] = None
 
     # The next URL to redirect to on successful form submission
     @property
@@ -93,7 +93,7 @@ class LaunchpadFormView(LaunchpadView):
     # The for_input is passed through to create the fields.  If this value
     # is set to true in derived classes, then fields that are marked
     # read only will have editable widgets created for them.
-    for_input = None  # type: Optional[bool]
+    for_input: Optional[bool] = None
 
     def __init__(self, context, request):
         LaunchpadView.__init__(self, context, request)
diff --git a/lib/lp/app/browser/multistep.py b/lib/lp/app/browser/multistep.py
index a8df433..6ac6cfb 100644
--- a/lib/lp/app/browser/multistep.py
+++ b/lib/lp/app/browser/multistep.py
@@ -149,7 +149,7 @@ class StepView(LaunchpadFormView):
         TextWidget, visible=False
     )
 
-    _field_names = []  # type: List[str]
+    _field_names: List[str] = []
     step_name = ""
     main_action_label = "Continue"
     next_step = None
diff --git a/lib/lp/app/browser/root.py b/lib/lp/app/browser/root.py
index 5dc4181..2a87d2b 100644
--- a/lib/lp/app/browser/root.py
+++ b/lib/lp/app/browser/root.py
@@ -56,7 +56,7 @@ class LaunchpadRootIndexView(HasAnnouncementsView, LaunchpadView):
     """An view for the default view of the LaunchpadRoot."""
 
     page_title = "Launchpad"
-    featured_projects = []  # type: List[Any]
+    featured_projects: List[Any] = []
     featured_projects_top = None
 
     # Used by the footer to display the lp-arcana section.
diff --git a/lib/lp/app/browser/tests/test_vocabulary.py b/lib/lp/app/browser/tests/test_vocabulary.py
index 81f80cf..cfaaa90 100644
--- a/lib/lp/app/browser/tests/test_vocabulary.py
+++ b/lib/lp/app/browser/tests/test_vocabulary.py
@@ -501,7 +501,7 @@ class TestDistributionPickerEntrySourceAdapter(TestCaseWithFactory):
 
 @implementer(IHugeVocabulary)
 class TestPersonVocabulary:
-    test_persons = []  # type: List[Person]
+    test_persons: List[Person] = []
 
     @classmethod
     def setTestData(cls, person_list: List[Person]):
diff --git a/lib/lp/app/browser/tests/test_webservice.py b/lib/lp/app/browser/tests/test_webservice.py
index 2defa9d..634003d 100644
--- a/lib/lp/app/browser/tests/test_webservice.py
+++ b/lib/lp/app/browser/tests/test_webservice.py
@@ -3,6 +3,8 @@
 
 """Tests for webservice features across Launchpad."""
 
+from typing import Optional
+
 from lazr.restful.interfaces import IFieldHTMLRenderer
 from lazr.restful.utils import get_current_web_service_request
 from zope.component import getMultiAdapter
@@ -54,7 +56,7 @@ class BaseMissingObjectWebService:
 
     layer = DatabaseFunctionalLayer
 
-    object_type = None  # type: str
+    object_type: Optional[str] = None
 
     def test_object_not_found(self):
         """Missing top-level objects generate 404s but not OOPS."""
diff --git a/lib/lp/app/browser/webservice.py b/lib/lp/app/browser/webservice.py
index 6fc2943..64f8734 100644
--- a/lib/lp/app/browser/webservice.py
+++ b/lib/lp/app/browser/webservice.py
@@ -16,7 +16,7 @@ from zope.schema.interfaces import IText
 from lp.app.browser.stringformatter import FormattersAPI
 from lp.app.browser.tales import format_link
 
-__all__ = []  # type: List[str]
+__all__: List[str] = []
 
 
 @component.adapter(Interface, IReference, IWebServiceClientRequest)
diff --git a/lib/lp/app/security.py b/lib/lp/app/security.py
index 37ccc27..3c37dce 100644
--- a/lib/lp/app/security.py
+++ b/lib/lp/app/security.py
@@ -21,8 +21,8 @@ from lp.app.interfaces.security import IAuthorization
 
 @implementer(IAuthorization)
 class AuthorizationBase:
-    permission = None  # type: Optional[str]
-    usedfor = None  # type: Optional[Type[Interface]]
+    permission: Optional[str] = None
+    usedfor: Optional[Type[Interface]] = None
 
     def __init__(self, obj):
         self.obj = obj
diff --git a/lib/lp/app/tests/test_yuitests.py b/lib/lp/app/tests/test_yuitests.py
index cfa0a8e..7c3b8fc 100644
--- a/lib/lp/app/tests/test_yuitests.py
+++ b/lib/lp/app/tests/test_yuitests.py
@@ -7,7 +7,7 @@ from typing import List
 from lp.testing import YUIUnitTestCase, build_yui_unittest_suite
 from lp.testing.layers import YUITestLayer
 
-__all__ = []  # type: List[str]
+__all__: List[str] = []
 
 
 class AppYUIUnitTestCase(YUIUnitTestCase):
diff --git a/lib/lp/app/utilities/celebrities.py b/lib/lp/app/utilities/celebrities.py
index 3f54349..e17ac36 100644
--- a/lib/lp/app/utilities/celebrities.py
+++ b/lib/lp/app/utilities/celebrities.py
@@ -105,7 +105,7 @@ class PersonCelebrityDescriptor(CelebrityDescriptor):
     """
 
     # Populated by the constructor.
-    names = set()  # type: Set[str]
+    names: Set[str] = set()
 
     def __init__(self, name):
         PersonCelebrityDescriptor.names.add(name)
diff --git a/lib/lp/app/widgets/date.py b/lib/lp/app/widgets/date.py
index a54e85f..99d8f3f 100644
--- a/lib/lp/app/widgets/date.py
+++ b/lib/lp/app/widgets/date.py
@@ -18,6 +18,7 @@ __all__ = [
 ]
 
 from datetime import datetime, timezone, tzinfo
+from typing import Optional
 
 from dateutil import tz
 from zope.browserpage import ViewPageTemplateFile
@@ -114,7 +115,7 @@ class DateTimeWidget(TextWidget):
     """
 
     timeformat = "%Y-%m-%d %H:%M:%S"
-    required_time_zone_name = None  # type: str
+    required_time_zone_name: Optional[str] = None
     display_zone = True
     from_date = None
     to_date = None
diff --git a/lib/lp/app/widgets/tests/test_itemswidgets.py b/lib/lp/app/widgets/tests/test_itemswidgets.py
index 4a604b9..7d4e427 100644
--- a/lib/lp/app/widgets/tests/test_itemswidgets.py
+++ b/lib/lp/app/widgets/tests/test_itemswidgets.py
@@ -2,7 +2,7 @@
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 import doctest
-from typing import Any, Type
+from typing import Any, Optional, Type
 
 from lazr.enum import EnumeratedType, Item
 from lazr.enum._enum import DBEnumeratedType, DBItem
@@ -32,7 +32,7 @@ class ItemWidgetTestCase(TestCaseWithFactory):
 
     layer = DatabaseFunctionalLayer
 
-    WIDGET_CLASS = None  # type: Type[Any]
+    WIDGET_CLASS: Optional[Type[Any]] = None
     SAFE_TERM = SimpleTerm("object-1", "token-1", "Safe title")
     UNSAFE_TERM = SimpleTerm("object-2", "token-2", "<unsafe> &nbsp; title")
 
diff --git a/lib/lp/archivepublisher/customupload.py b/lib/lp/archivepublisher/customupload.py
index 2759c2f..348f8f8 100644
--- a/lib/lp/archivepublisher/customupload.py
+++ b/lib/lp/archivepublisher/customupload.py
@@ -17,6 +17,7 @@ import os
 import shutil
 import tarfile
 import tempfile
+from typing import Optional
 
 from zope.interface import implementer
 
@@ -113,7 +114,7 @@ class CustomUpload:
     """Base class for custom upload handlers"""
 
     # This should be set as a class property on each subclass.
-    custom_type = None  # type: str
+    custom_type: Optional[str] = None
 
     @classmethod
     def publish(cls, packageupload, libraryfilealias, logger=None):
diff --git a/lib/lp/archivepublisher/security.py b/lib/lp/archivepublisher/security.py
index 3326cf9..8e186d6 100644
--- a/lib/lp/archivepublisher/security.py
+++ b/lib/lp/archivepublisher/security.py
@@ -7,7 +7,7 @@ from typing import List
 from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfig
 from lp.security import AdminByAdminsTeam
 
-__all__ = []  # type: List[str]
+__all__: List[str] = []
 
 
 # If edit access to this is ever opened up beyond admins, then we need to
diff --git a/lib/lp/archiveuploader/tests/test_buildduploads.py b/lib/lp/archiveuploader/tests/test_buildduploads.py
index c368db7..edb18b3 100644
--- a/lib/lp/archiveuploader/tests/test_buildduploads.py
+++ b/lib/lp/archiveuploader/tests/test_buildduploads.py
@@ -4,6 +4,7 @@
 """Test buildd uploads use-cases."""
 
 import os
+from typing import Optional
 
 from zope.component import getUtility
 
@@ -26,9 +27,9 @@ from lp.testing.gpgkeys import import_public_test_keys
 class TestStagedBinaryUploadBase(TestUploadProcessorBase):
     name = "baz"
     version = "1.0-1"
-    distribution_name = None  # type: str
-    distroseries_name = None  # type: str
-    pocket = None  # type: PackagePublishingPocket
+    distribution_name: Optional[str] = None
+    distroseries_name: Optional[str] = None
+    pocket: Optional[PackagePublishingPocket] = None
     policy = "buildd"
     no_mails = True
 
diff --git a/lib/lp/blueprints/browser/specification.py b/lib/lp/blueprints/browser/specification.py
index 262b7d6..d35775b 100644
--- a/lib/lp/blueprints/browser/specification.py
+++ b/lib/lp/blueprints/browser/specification.py
@@ -1043,7 +1043,7 @@ class SpecificationGoalDecideView(LaunchpadFormView):
     """
 
     schema = Interface
-    field_names = []  # type: List[str]
+    field_names: List[str] = []
 
     @property
     def label(self):
diff --git a/lib/lp/blueprints/browser/specificationbranch.py b/lib/lp/blueprints/browser/specificationbranch.py
index 1ee6fe9..357bf24 100644
--- a/lib/lp/blueprints/browser/specificationbranch.py
+++ b/lib/lp/blueprints/browser/specificationbranch.py
@@ -47,7 +47,7 @@ class SpecificationBranchStatusView(LaunchpadEditFormView):
     """Edit the summary of the SpecificationBranch link."""
 
     schema = ISpecificationBranch
-    field_names = []  # type: List[str]
+    field_names: List[str] = []
     label = _("Delete link between specification and branch")
 
     def initialize(self):
diff --git a/lib/lp/blueprints/browser/specificationsubscription.py b/lib/lp/blueprints/browser/specificationsubscription.py
index c8f9432..ba94b5f 100644
--- a/lib/lp/blueprints/browser/specificationsubscription.py
+++ b/lib/lp/blueprints/browser/specificationsubscription.py
@@ -79,7 +79,7 @@ class SpecificationSubscriptionDeleteView(LaunchpadFormView):
     """Used to unsubscribe someone from a blueprint."""
 
     schema = ISpecificationSubscription
-    field_names = []  # type: List[str]
+    field_names: List[str] = []
 
     @property
     def label(self):
diff --git a/lib/lp/blueprints/browser/sprint.py b/lib/lp/blueprints/browser/sprint.py
index d7eb8d0..a22ebaa 100644
--- a/lib/lp/blueprints/browser/sprint.py
+++ b/lib/lp/blueprints/browser/sprint.py
@@ -403,7 +403,7 @@ class SprintDeleteView(LaunchpadFormView):
     """Form for deleting sprints."""
 
     schema = ISprint
-    field_names = []  # type: List[str]
+    field_names: List[str] = []
     next_url = None
 
     @property
diff --git a/lib/lp/blueprints/mail/__init__.py b/lib/lp/blueprints/mail/__init__.py
index 11b146c..eb81875 100644
--- a/lib/lp/blueprints/mail/__init__.py
+++ b/lib/lp/blueprints/mail/__init__.py
@@ -2,4 +2,4 @@
 # GNU Affero General Public License version 3 (see the file LICENSE).
 from typing import List
 
-__all__ = []  # type: List[str]
+__all__: List[str] = []
diff --git a/lib/lp/bugs/browser/bug.py b/lib/lp/bugs/browser/bug.py
index 3805a15..9cbd9a4 100644
--- a/lib/lp/bugs/browser/bug.py
+++ b/lib/lp/bugs/browser/bug.py
@@ -188,7 +188,7 @@ class BugSetNavigation(Navigation):
 class BugContextMenu(ContextMenu):
     """Context menu of actions that can be performed upon a Bug."""
 
-    usedfor = IBug  # type: Type[Interface]
+    usedfor: Type[Interface] = IBug
     links = [
         "editdescription",
         "markduplicate",
diff --git a/lib/lp/bugs/browser/bugalsoaffects.py b/lib/lp/bugs/browser/bugalsoaffects.py
index 3558dc4..ff6e6cb 100644
--- a/lib/lp/bugs/browser/bugalsoaffects.py
+++ b/lib/lp/bugs/browser/bugalsoaffects.py
@@ -215,7 +215,7 @@ class BugTaskCreationStep(AlsoAffectsStep):
 
     initial_focus_widget = "bug_url"
     step_name = "specify_remote_bug_url"
-    target_field_names = ()  # type: Tuple[str, ...]
+    target_field_names: Tuple[str, ...] = ()
 
     # This is necessary so that other views which dispatch work to this one
     # have access to the newly created task.
@@ -759,7 +759,7 @@ class BugTrackerCreationStep(AlsoAffectsStep):
     )
     step_name = "bugtracker_creation"
     main_action_label = "Register Bug Tracker and Add to Bug Report"
-    _next_step = None  # type: Type[StepView]
+    _next_step: Type[StepView] = None
 
     def main_action(self, data):
         assert (
diff --git a/lib/lp/bugs/browser/bugbranch.py b/lib/lp/bugs/browser/bugbranch.py
index 1dc0cd1..02e2292 100644
--- a/lib/lp/bugs/browser/bugbranch.py
+++ b/lib/lp/bugs/browser/bugbranch.py
@@ -67,7 +67,7 @@ class BugBranchDeleteView(LaunchpadEditFormView):
 
     schema = IBugBranch
 
-    field_names = []  # type: List[str]
+    field_names: List[str] = []
 
     def initialize(self):
         LaunchpadEditFormView.initialize(self)
diff --git a/lib/lp/bugs/browser/bugnomination.py b/lib/lp/bugs/browser/bugnomination.py
index 22d8941..91b328d 100644
--- a/lib/lp/bugs/browser/bugnomination.py
+++ b/lib/lp/bugs/browser/bugnomination.py
@@ -172,7 +172,7 @@ class BugNominationEditView(LaunchpadFormView):
     """Browser view class for approving and declining nominations."""
 
     schema = Interface
-    field_names = []  # type: List[str]
+    field_names: List[str] = []
 
     @property
     def label(self):
diff --git a/lib/lp/bugs/browser/bugsubscription.py b/lib/lp/bugs/browser/bugsubscription.py
index 4514f80..7d8a776 100644
--- a/lib/lp/bugs/browser/bugsubscription.py
+++ b/lib/lp/bugs/browser/bugsubscription.py
@@ -712,7 +712,7 @@ class BugMuteSelfView(LaunchpadFormView):
     """A view to mute a user's bug mail for a given bug."""
 
     schema = IBugSubscription
-    field_names = []  # type: List[str]
+    field_names: List[str] = []
 
     @property
     def label(self):
diff --git a/lib/lp/bugs/browser/bugtask.py b/lib/lp/bugs/browser/bugtask.py
index 2b2aaed..8fcb135 100644
--- a/lib/lp/bugs/browser/bugtask.py
+++ b/lib/lp/bugs/browser/bugtask.py
@@ -1730,7 +1730,7 @@ class BugTaskDeletionView(ReturnToReferrerMixin, LaunchpadFormView):
     """Used to delete a bugtask."""
 
     schema = IBugTask
-    field_names = []  # type: List[str]
+    field_names: List[str] = []
 
     label = "Remove bug task"
     page_title = label
diff --git a/lib/lp/bugs/externalbugtracker/base.py b/lib/lp/bugs/externalbugtracker/base.py
index ee7be38..13860cd 100644
--- a/lib/lp/bugs/externalbugtracker/base.py
+++ b/lib/lp/bugs/externalbugtracker/base.py
@@ -157,7 +157,7 @@ def repost_on_redirect_hook(response, *args, **kwargs):
 class ExternalBugTracker:
     """Base class for an external bug tracker."""
 
-    batch_size = None  # type: Optional[int]
+    batch_size: Optional[int] = None
     batch_query_threshold = config.checkwatches.batch_query_threshold
     timeout = config.checkwatches.default_socket_timeout
     comment_template = "default_remotecomment_template.txt"
diff --git a/lib/lp/bugs/scripts/checkwatches/core.py b/lib/lp/bugs/scripts/checkwatches/core.py
index 9589bd4..fca0235 100644
--- a/lib/lp/bugs/scripts/checkwatches/core.py
+++ b/lib/lp/bugs/scripts/checkwatches/core.py
@@ -60,7 +60,7 @@ from lp.services.scripts.logger import log as default_log
 LOGIN = "bugwatch@xxxxxxxxxxxxxxxxxx"
 
 # A list of product names for which comments should be synchronized.
-SYNCABLE_GNOME_PRODUCTS = []  # type: List[str]
+SYNCABLE_GNOME_PRODUCTS: List[str] = []
 
 # When syncing with a remote bug tracker that reports its idea of the
 # current time, this defined the maximum acceptable skew between the
diff --git a/lib/lp/bugs/scripts/tests/test_bugnotification.py b/lib/lp/bugs/scripts/tests/test_bugnotification.py
index e6a8cd9..0e612f5 100644
--- a/lib/lp/bugs/scripts/tests/test_bugnotification.py
+++ b/lib/lp/bugs/scripts/tests/test_bugnotification.py
@@ -82,7 +82,7 @@ class MockBug:
 
     duplicateof = None
     information_type = InformationType.PUBLIC
-    messages = []  # type: List[Message]
+    messages: List[Message] = []
 
     def __init__(self, id, owner):
         self.id = id
@@ -721,12 +721,12 @@ class EmailNotificationTestBase(TestCaseWithFactory):
 
 
 class EmailNotificationsBugMixin:
-    change_class = None  # type: Optional[Type[Any]]
-    change_name = None  # type: Optional[str]
-    old = None  # type: Any
-    new = None  # type: Any
-    alt = None  # type: Any
-    unexpected_bytes = None  # type: Optional[bytes]
+    change_class: Optional[Type[Any]] = None
+    change_name: Optional[str] = None
+    old: Any = None
+    new: Any = None
+    alt: Any = None
+    unexpected_bytes: Optional[bytes] = None
 
     def change(self, old, new):
         self.bug.addChange(
diff --git a/lib/lp/bugs/scripts/tests/test_uct.py b/lib/lp/bugs/scripts/tests/test_uct.py
index c6aa91f..f029f28 100644
--- a/lib/lp/bugs/scripts/tests/test_uct.py
+++ b/lib/lp/bugs/scripts/tests/test_uct.py
@@ -713,7 +713,7 @@ class TestUCTImporterExporter(TestCaseWithFactory):
         self.checkBugAttachments(bug, cve)
 
     def checkBugTasks(self, bug: Bug, cve: CVE):
-        bug_tasks = bug.bugtasks  # type: List[BugTask]
+        bug_tasks: List[BugTask] = bug.bugtasks
 
         self.assertEqual(
             len(cve.distro_packages)
diff --git a/lib/lp/bugs/scripts/uct/models.py b/lib/lp/bugs/scripts/uct/models.py
index 91cb076..e11ce8c 100644
--- a/lib/lp/bugs/scripts/uct/models.py
+++ b/lib/lp/bugs/scripts/uct/models.py
@@ -153,15 +153,13 @@ class UCTRecord:
         applying some data transformations along the way.
         """
 
-        cve_data = load_cve(str(cve_path))  # type: Dict[str, Any]
+        cve_data: Dict[str, Any] = load_cve(str(cve_path))
 
         packages = []
-        tags = cls._pop_cve_property(
-            cve_data, "tags"
-        )  # type: Dict[str, Set[str]]
-        patches = cls._pop_cve_property(
+        tags: Dict[str, Set[str]] = cls._pop_cve_property(cve_data, "tags")
+        patches: Dict[str, List[Tuple[str, str]]] = cls._pop_cve_property(
             cve_data, "patches"
-        )  # type: Dict[str, List[Tuple[str, str]]]
+        )
         for package, statuses_dict in cls._pop_cve_property(
             cve_data, "pkgs"
         ).items():
@@ -515,7 +513,7 @@ class CVE:
         self.notes = notes
         self.mitigation = mitigation
         self.cvss = cvss
-        self.patch_urls = patch_urls or []  # type: List[CVE.PatchURL]
+        self.patch_urls: List[CVE.PatchURL] = patch_urls or []
 
     @classmethod
     def make_from_uct_record(cls, uct_record: UCTRecord) -> "CVE":
@@ -531,9 +529,9 @@ class CVE:
 
         spn_set = getUtility(ISourcePackageNameSet)
 
-        upstream_statuses = (
-            OrderedDict()
-        )  # type: Dict[SourcePackageName, UCTRecord.SeriesPackageStatus]
+        upstream_statuses: Dict[
+            SourcePackageName, UCTRecord.SeriesPackageStatus
+        ] = OrderedDict()
 
         for uct_package in uct_record.packages:
             source_package_name = spn_set.getOrCreateByName(uct_package.name)
@@ -669,22 +667,22 @@ class CVE:
 
         This maps Launchpad data structures to the format that UCT understands.
         """
-        series_packages_by_name = defaultdict(
-            list
-        )  # type: Dict[SourcePackageName, List[CVE.SeriesPackage]]
+        series_packages_by_name: Dict[
+            SourcePackageName, List[CVE.SeriesPackage]
+        ] = defaultdict(list)
         for series_package in self.series_packages:
             series_packages_by_name[series_package.package_name].append(
                 series_package
             )
 
-        packages_by_name = OrderedDict()  # type: Dict[str, UCTRecord.Package]
-        processed_packages = set()  # type: Set[SourcePackageName]
+        packages_by_name: Dict[str, UCTRecord.Package] = OrderedDict()
+        processed_packages: Set[SourcePackageName] = set()
         for distro_package in self.distro_packages:
             spn = distro_package.package_name
             if spn in processed_packages:
                 continue
             processed_packages.add(spn)
-            statuses = []  # type: List[UCTRecord.SeriesPackageStatus]
+            statuses: List[UCTRecord.SeriesPackageStatus] = []
             for series_package in series_packages_by_name[spn]:
                 series = series_package.target.distroseries
                 if series.status == SeriesStatus.DEVELOPMENT:
diff --git a/lib/lp/bugs/scripts/uct/uctexport.py b/lib/lp/bugs/scripts/uct/uctexport.py
index e91fba5..3745874 100644
--- a/lib/lp/bugs/scripts/uct/uctexport.py
+++ b/lib/lp/bugs/scripts/uct/uctexport.py
@@ -91,13 +91,13 @@ class UCTExporter:
             raise ValueError(
                 f"Bug with ID: {bug.id} does not have vulnerabilities"
             )
-        vulnerability = vulnerabilities[0]  # type: Vulnerability
+        vulnerability: Vulnerability = vulnerabilities[0]
         if not vulnerability.cve:
             raise ValueError(
                 "Bug with ID: {} - vulnerability "
                 "is not linked to a CVE".format(bug.id)
             )
-        lp_cve = vulnerability.cve  # type: CveModel
+        lp_cve: CveModel = vulnerability.cve
 
         parsed_description = self._parse_bug_description(bug.description)
 
@@ -105,7 +105,7 @@ class UCTExporter:
         for bug_watch in bug.watches:
             bug_urls.append(bug_watch.url)
 
-        bug_tasks = list(bug.bugtasks)  # type: List[BugTask]
+        bug_tasks: List[BugTask] = list(bug.bugtasks)
 
         cve_importance = vulnerability.importance
 
@@ -118,7 +118,7 @@ class UCTExporter:
         #  DistroPackage importance
         package_importances = {}
 
-        package_name_by_product = {}  # type: Dict[Product, SourcePackageName]
+        package_name_by_product: Dict[Product, SourcePackageName] = {}
         # We need to process all distribution package tasks before processing
         # the distro-series tasks to collect importance value for each package.
         distro_packages = []
diff --git a/lib/lp/bugs/scripts/uct/uctimport.py b/lib/lp/bugs/scripts/uct/uctimport.py
index 17a4ed7..845b060 100644
--- a/lib/lp/bugs/scripts/uct/uctimport.py
+++ b/lib/lp/bugs/scripts/uct/uctimport.py
@@ -104,9 +104,9 @@ class UCTImporter:
                 cve.series_packages,
             )
             return
-        lp_cve = removeSecurityProxy(
+        lp_cve: CveModel = removeSecurityProxy(
             getUtility(ICveSet)[cve.sequence]
-        )  # type: CveModel
+        )
         if lp_cve is None:
             logger.warning(
                 "%s: could not find the CVE in LP. Aborting.", cve.sequence
@@ -154,7 +154,7 @@ class UCTImporter:
         distro_package = cve.distro_packages[0]
 
         # Create the bug
-        bug = getUtility(IBugSet).createBug(
+        bug: BugModel = getUtility(IBugSet).createBug(
             CreateBugParams(
                 comment=self._make_bug_description(cve),
                 title=cve.sequence,
@@ -164,7 +164,7 @@ class UCTImporter:
                 importance=distro_package.importance,
                 cve=lp_cve,
             )
-        )  # type: BugModel
+        )
 
         self._update_external_bug_urls(bug, cve.bug_urls)
         self._update_patches(bug, cve.patch_urls)
@@ -274,7 +274,7 @@ class UCTImporter:
         :param distro_packages: list of `DistroPackage`s from a `CVE`
         :param series_packages: list of `SeriesPackage`s from a `CVE`
         """
-        bug_tasks = bug.bugtasks  # type: List[BugTask]
+        bug_tasks: List[BugTask] = bug.bugtasks
         bug_task_by_target = {t.target: t for t in bug_tasks}
         bug_task_set = getUtility(IBugTaskSet)
         for package in chain(
@@ -301,14 +301,14 @@ class UCTImporter:
         :param distribution: a `Distribution` affected by the vulnerability
         :return: a Vulnerability
         """
-        vulnerability = getUtility(IVulnerabilitySet).new(
+        vulnerability: Vulnerability = getUtility(IVulnerabilitySet).new(
             distribution=distribution,
             status=cve.status,
             importance=cve.importance,
             creator=bug.owner,
             information_type=InformationType.PUBLICSECURITY,
             cve=lp_cve,
-        )  # type: Vulnerability
+        )
         self._update_vulnerability(vulnerability, cve)
 
         vulnerability.linkBug(bug, bug.owner)
@@ -393,10 +393,10 @@ class UCTImporter:
         :param distro_packages: list of `DistroPackage`s from a `CVE`
         :param series_packages: list of `SeriesPackage`s from a `CVE`
         """
-        bug_tasks = bug.bugtasks  # type: List[BugTask]
+        bug_tasks: List[BugTask] = bug.bugtasks
         bug_task_by_target = {t.target: t for t in bug_tasks}
 
-        package_importances = {}  # type: Dict[str, BugTaskImportance]
+        package_importances: Dict[str, BugTaskImportance] = {}
 
         for dp in distro_packages:
             task = bug_task_by_target[dp.target]
diff --git a/lib/lp/bugs/tests/externalbugtracker.py b/lib/lp/bugs/tests/externalbugtracker.py
index 7e24caa..59b9e3d 100644
--- a/lib/lp/bugs/tests/externalbugtracker.py
+++ b/lib/lp/bugs/tests/externalbugtracker.py
@@ -543,11 +543,11 @@ class TestBugzillaXMLRPCTransport(RequestsTransport):
     }
 
     # Methods that require authentication.
-    auth_required_methods = (
+    auth_required_methods: Tuple[str, ...] = (
         "add_comment",
         "login_required",
         "set_link",
-    )  # type: Tuple[str, ...]
+    )
 
     expired_cookie = None
 
@@ -1363,8 +1363,8 @@ def strip_trac_comment(comment):
 class TestTracXMLRPCTransport(RequestsTransport):
     """An XML-RPC transport to be used when testing Trac."""
 
-    remote_bugs = {}  # type: Dict[str, Dict[str, Any]]
-    launchpad_bugs = {}  # type: Dict[str, int]
+    remote_bugs: Dict[str, Dict[str, Any]] = {}
+    launchpad_bugs: Dict[str, int] = {}
     seconds_since_epoch = None
     local_timezone = "UTC"
     utc_offset = 0
diff --git a/lib/lp/bugs/tests/test_buglinktarget.py b/lib/lp/bugs/tests/test_buglinktarget.py
index 9618d00..e96e5e2 100644
--- a/lib/lp/bugs/tests/test_buglinktarget.py
+++ b/lib/lp/bugs/tests/test_buglinktarget.py
@@ -20,7 +20,7 @@ from lp.testing.factory import LaunchpadObjectFactory
 from lp.testing.layers import LaunchpadFunctionalLayer
 from lp.testing.systemdocs import LayeredDocFileSuite, setUp, tearDown
 
-__all__ = []  # type: List[str]
+__all__: List[str] = []
 
 
 def questionSetUp(test):
diff --git a/lib/lp/bugs/tests/test_bugsearch_conjoined.py b/lib/lp/bugs/tests/test_bugsearch_conjoined.py
index cdb2f82..b4bbce4 100644
--- a/lib/lp/bugs/tests/test_bugsearch_conjoined.py
+++ b/lib/lp/bugs/tests/test_bugsearch_conjoined.py
@@ -20,7 +20,7 @@ from lp.testing import (
 from lp.testing.layers import DatabaseFunctionalLayer
 from lp.testing.matchers import HasQueryCount
 
-__all__ = []  # type: List[str]
+__all__: List[str] = []
 
 
 class TestSearchBase(TestCaseWithFactory):
diff --git a/lib/lp/bugs/tests/test_bugtarget.py b/lib/lp/bugs/tests/test_bugtarget.py
index f1427b7..06ff959 100644
--- a/lib/lp/bugs/tests/test_bugtarget.py
+++ b/lib/lp/bugs/tests/test_bugtarget.py
@@ -23,7 +23,7 @@ from lp.testing import TestCaseWithFactory, person_logged_in
 from lp.testing.layers import DatabaseFunctionalLayer
 from lp.testing.systemdocs import LayeredDocFileSuite, setUp, tearDown
 
-__all__ = []  # type: List[str]
+__all__: List[str] = []
 
 
 def bugtarget_filebug(bugtarget, summary, status=None):
diff --git a/lib/lp/bugs/tests/test_bugtracker_components.py b/lib/lp/bugs/tests/test_bugtracker_components.py
index 3da7150..a8c640a 100644
--- a/lib/lp/bugs/tests/test_bugtracker_components.py
+++ b/lib/lp/bugs/tests/test_bugtracker_components.py
@@ -9,7 +9,7 @@ import transaction
 from lp.testing import TestCaseWithFactory, login_person, ws_object
 from lp.testing.layers import AppServerLayer, DatabaseFunctionalLayer
 
-__all__ = []  # type: List[str]
+__all__: List[str] = []
 
 
 class BugTrackerComponentTestCase(TestCaseWithFactory):
diff --git a/lib/lp/bugs/tests/test_bugwatch.py b/lib/lp/bugs/tests/test_bugwatch.py
index d9779d1..f12097e 100644
--- a/lib/lp/bugs/tests/test_bugwatch.py
+++ b/lib/lp/bugs/tests/test_bugwatch.py
@@ -228,16 +228,16 @@ class ExtractBugTrackerAndBugTest(WithScenarios, TestCase):
     layer = LaunchpadFunctionalLayer
 
     # A URL to an unregistered bug tracker.
-    base_url = None  # type: str
+    base_url: str = None
 
     # The bug tracker type to be tested.
     bugtracker_type = None
 
     # A sample URL to a bug in the bug tracker.
-    bug_url = None  # type: str
+    bug_url: str = None
 
     # The bug id in the sample bug_url.
-    bug_id = None  # type: Optional[str]
+    bug_id: Optional[str] = None
 
     # True if the bug tracker is already registered in sampledata.
     already_registered = False
@@ -383,7 +383,7 @@ class EmailAddressExtractBugTrackerAndBugTest(ExtractBugTrackerAndBugTest):
     Ensure BugWatchSet.extractBugTrackerAndBug works with email addresses.
     """
 
-    scenarios = []  # type: List
+    scenarios: List = []
     bugtracker_type = BugTrackerType.EMAILADDRESS
     bug_url = "mailto:foo.bar@xxxxxxxxxxx";
     base_url = "mailto:foo.bar@xxxxxxxxxxx";
diff --git a/lib/lp/bugs/tests/test_bzremotecomponentfinder.py b/lib/lp/bugs/tests/test_bzremotecomponentfinder.py
index 3e2c210..b1fa024 100644
--- a/lib/lp/bugs/tests/test_bzremotecomponentfinder.py
+++ b/lib/lp/bugs/tests/test_bzremotecomponentfinder.py
@@ -20,7 +20,7 @@ from lp.testing import TestCaseWithFactory, login
 from lp.testing.layers import DatabaseFunctionalLayer
 from lp.testing.sampledata import ADMIN_EMAIL
 
-__all__ = []  # type: List[str]
+__all__: List[str] = []
 
 
 def read_test_file(name):
diff --git a/lib/lp/bugs/tests/test_externalbugtracker.py b/lib/lp/bugs/tests/test_externalbugtracker.py
index 44d2398..0762d42 100644
--- a/lib/lp/bugs/tests/test_externalbugtracker.py
+++ b/lib/lp/bugs/tests/test_externalbugtracker.py
@@ -9,7 +9,7 @@ from typing import List
 from lp.testing.layers import LaunchpadFunctionalLayer
 from lp.testing.systemdocs import LayeredDocFileSuite, setUp, tearDown
 
-__all__ = []  # type: List[str]
+__all__: List[str] = []
 
 
 def test_suite():
diff --git a/lib/lp/bugs/tests/test_yuitests.py b/lib/lp/bugs/tests/test_yuitests.py
index 7ba7c62..761a13f 100644
--- a/lib/lp/bugs/tests/test_yuitests.py
+++ b/lib/lp/bugs/tests/test_yuitests.py
@@ -7,7 +7,7 @@ from typing import List
 from lp.testing import YUIUnitTestCase, build_yui_unittest_suite
 from lp.testing.layers import YUITestLayer
 
-__all__ = []  # type: List[str]
+__all__: List[str] = []
 
 
 class BugsYUIUnitTestCase(YUIUnitTestCase):
diff --git a/lib/lp/buildmaster/downloader.py b/lib/lp/buildmaster/downloader.py
index 68aab4d..fc16852 100644
--- a/lib/lp/buildmaster/downloader.py
+++ b/lib/lp/buildmaster/downloader.py
@@ -30,7 +30,7 @@ class DownloadCommand(amp.Command):
         (b"path_to_write", amp.Unicode()),
         (b"timeout", amp.Integer()),
     ]
-    response = []  # type: List[Tuple[bytes, amp.Argument]]
+    response: List[Tuple[bytes, amp.Argument]] = []
     errors = {
         RequestException: b"REQUEST_ERROR",
         StreamingError: b"STREAMING_ERROR",
diff --git a/lib/lp/buildmaster/tests/test_buildfarmjob.py b/lib/lp/buildmaster/tests/test_buildfarmjob.py
index caf952f..86ceae3 100644
--- a/lib/lp/buildmaster/tests/test_buildfarmjob.py
+++ b/lib/lp/buildmaster/tests/test_buildfarmjob.py
@@ -31,7 +31,7 @@ from lp.testing.layers import (
 
 
 class TestBuildFarmJobBase:
-    layer = DatabaseFunctionalLayer  # type: Type[BaseLayer]
+    layer: Type[BaseLayer] = DatabaseFunctionalLayer
 
     def setUp(self, *args, **kwargs):
         """Create a build farm job with which to test."""
diff --git a/lib/lp/buildmaster/tests/test_manager.py b/lib/lp/buildmaster/tests/test_manager.py
index 78d42df..74c2626 100644
--- a/lib/lp/buildmaster/tests/test_manager.py
+++ b/lib/lp/buildmaster/tests/test_manager.py
@@ -1128,7 +1128,7 @@ class TestPrefetchedBuilderFactory(TestCaseWithFactory):
 class FakeBuilddManager:
     """A minimal fake version of `BuilddManager`."""
 
-    pending_logtails = {}  # type: Dict[int, str]
+    pending_logtails: Dict[int, str] = {}
 
     def addLogTail(self, build_queue_id, logtail):
         self.pending_logtails[build_queue_id] = logtail
diff --git a/lib/lp/charms/model/charmrecipebuildbehaviour.py b/lib/lp/charms/model/charmrecipebuildbehaviour.py
index 9e95250..5e7bf50 100644
--- a/lib/lp/charms/model/charmrecipebuildbehaviour.py
+++ b/lib/lp/charms/model/charmrecipebuildbehaviour.py
@@ -78,7 +78,7 @@ class CharmRecipeBuildBehaviour(BuilderProxyMixin, BuildFarmJobBehaviourBase):
         Return the extra arguments required by the worker for the given build.
         """
         build = self.build
-        args = yield super().extraBuildArgs(logger=logger)  # type: BuildArgs
+        args: BuildArgs = yield super().extraBuildArgs(logger=logger)
         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 def2e95..648546b 100644
--- a/lib/lp/code/model/cibuildbehaviour.py
+++ b/lib/lp/code/model/cibuildbehaviour.py
@@ -175,7 +175,7 @@ class CIBuildBehaviour(BuilderProxyMixin, BuildFarmJobBehaviourBase):
                 % (build.git_repository.unique_name, build.commit_sha1)
             )
 
-        args = yield super().extraBuildArgs(logger=logger)  # type: BuildArgs
+        args: BuildArgs = yield super().extraBuildArgs(logger=logger)
         yield self.addProxyArgs(args)
         (
             args["archives"],
diff --git a/lib/lp/code/model/recipebuilder.py b/lib/lp/code/model/recipebuilder.py
index 5b73659..8564b88 100644
--- a/lib/lp/code/model/recipebuilder.py
+++ b/lib/lp/code/model/recipebuilder.py
@@ -70,7 +70,7 @@ class RecipeBuildBehaviour(BuildFarmJobBehaviourBase):
             )
 
         # Build extra arguments.
-        args = yield super().extraBuildArgs(logger=logger)  # type: BuildArgs
+        args: BuildArgs = yield super().extraBuildArgs(logger=logger)
         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 b5fe6a7..4ce649a 100644
--- a/lib/lp/oci/model/ocirecipebuildbehaviour.py
+++ b/lib/lp/oci/model/ocirecipebuildbehaviour.py
@@ -135,7 +135,7 @@ class OCIRecipeBuildBehaviour(BuilderProxyMixin, BuildFarmJobBehaviourBase):
         Return the extra arguments required by the worker for the given build.
         """
         build = self.build
-        args = yield super().extraBuildArgs(logger=logger)  # type: BuildArgs
+        args: BuildArgs = yield super().extraBuildArgs(logger=logger)
         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/registry/browser/product.py b/lib/lp/registry/browser/product.py
index 3d55e4b..3a5008a 100644
--- a/lib/lp/registry/browser/product.py
+++ b/lib/lp/registry/browser/product.py
@@ -1437,7 +1437,7 @@ class ProductBrandingView(BrandingChangeView):
 
 @implementer(IProductEditMenu)
 class ProductConfigureBase(ReturnToReferrerMixin, LaunchpadEditFormView):
-    schema = IProduct  # type: Type[Interface]
+    schema: Type[Interface] = IProduct
     usage_fieldname = None
 
     def setUpFields(self):
diff --git a/lib/lp/registry/scripts/closeaccount.py b/lib/lp/registry/scripts/closeaccount.py
index 6f92c73..f408478 100644
--- a/lib/lp/registry/scripts/closeaccount.py
+++ b/lib/lp/registry/scripts/closeaccount.py
@@ -479,7 +479,7 @@ def close_account(username, log):
     if non_referenced_ppa_ids:
         store.find(Archive, Archive.id.is_in(non_referenced_ppa_ids)).remove()
 
-    reference_counts = []  # type: List[Tuple[str, int]]
+    reference_counts: List[Tuple[str, int]] = []
 
     # Check for non-deleted PPAs
     count = store.find(
diff --git a/lib/lp/registry/scripts/tests/test_closeaccount.py b/lib/lp/registry/scripts/tests/test_closeaccount.py
index 4a377c5..e0b0c14 100644
--- a/lib/lp/registry/scripts/tests/test_closeaccount.py
+++ b/lib/lp/registry/scripts/tests/test_closeaccount.py
@@ -1259,9 +1259,9 @@ class TestCloseAccount(TestCaseWithFactory):
             account_id = person.account.id
 
             milestone = self.factory.makeMilestone(**milestone_target)
-            product_release = milestone.createProductRelease(
+            product_release: ProductRelease = milestone.createProductRelease(
                 milestone.product.owner, datetime.now(timezone.utc)
-            )  # type: ProductRelease
+            )
             product_release.addReleaseFile(
                 "test.txt", b"test", "text/plain", person
             )
diff --git a/lib/lp/services/feeds/browser.py b/lib/lp/services/feeds/browser.py
index 80a37af..7c9fba7 100644
--- a/lib/lp/services/feeds/browser.py
+++ b/lib/lp/services/feeds/browser.py
@@ -372,7 +372,7 @@ class FeedsMixin:
     feed_links: Returns a list of objects subclassed from FeedLinkBase.
     """
 
-    feed_types = (
+    feed_types: Tuple[Type[FeedLinkBase, ...]] = (
         AnnouncementsFeedLink,
         BranchFeedLink,
         BugFeedLink,
@@ -384,7 +384,7 @@ class FeedsMixin:
         ProjectBranchesFeedLink,
         ProjectRevisionsFeedLink,
         RootAnnouncementsFeedLink,
-    )  # type: Tuple[Type[FeedLinkBase, ...]]
+    )
 
     @property
     def feed_links(self):
diff --git a/lib/lp/services/looptuner.py b/lib/lp/services/looptuner.py
index 91ce581..286b3fd 100644
--- a/lib/lp/services/looptuner.py
+++ b/lib/lp/services/looptuner.py
@@ -391,7 +391,7 @@ class TunableLoop:
 
     goal_seconds = 2
     minimum_chunk_size = 1
-    maximum_chunk_size = None  # type: int
+    maximum_chunk_size: int = None
     cooldown_time = 0
 
     def __init__(self, log, abort_time=None):
diff --git a/lib/lp/services/mail/commands.py b/lib/lp/services/mail/commands.py
index e12c670..2ff7919 100644
--- a/lib/lp/services/mail/commands.py
+++ b/lib/lp/services/mail/commands.py
@@ -59,7 +59,7 @@ class EmailCommand:
     Both name the values in the args list are strings.
     """
 
-    _numberOfArguments = None  # type: int
+    _numberOfArguments: int = None
 
     # Should command arguments be converted to lowercase?
     case_insensitive_args = True
diff --git a/lib/lp/services/scripts/base.py b/lib/lp/services/scripts/base.py
index 6184599..4674109 100644
--- a/lib/lp/services/scripts/base.py
+++ b/lib/lp/services/scripts/base.py
@@ -136,8 +136,8 @@ class LaunchpadScript:
 
     lock = None
     txn = None
-    usage = None  # type: Optional[str]
-    description = None  # type: Optional[str]
+    usage: Optional[str] = None
+    description: Optional[str] = None
     lockfilepath = None
     loglevel = logging.INFO
 
diff --git a/lib/lp/services/webapp/breadcrumb.py b/lib/lp/services/webapp/breadcrumb.py
index 8bf39b7..a21e6ff 100644
--- a/lib/lp/services/webapp/breadcrumb.py
+++ b/lib/lp/services/webapp/breadcrumb.py
@@ -23,7 +23,7 @@ class Breadcrumb:
     This class is intended for use as an adapter.
     """
 
-    text = None  # type: str
+    text: str = None
     _detail = None
     _url = None
     inside = None
diff --git a/lib/lp/services/webapp/menu.py b/lib/lp/services/webapp/menu.py
index 4341425..7b8b5c6 100644
--- a/lib/lp/services/webapp/menu.py
+++ b/lib/lp/services/webapp/menu.py
@@ -199,7 +199,7 @@ MENU_ANNOTATION_KEY = "lp.services.webapp.menu.links"
 class MenuBase(UserAttributeCache):
     """Base class for facets and menus."""
 
-    links = None  # type: Sequence[str]
+    links: Sequence[str] = None
     extra_attributes = None
     enable_only = ALL_LINKS
     _baseclassname = "MenuBase"
@@ -401,7 +401,7 @@ class NavigationMenu(MenuBase):
 
     _baseclassname = "NavigationMenu"
 
-    title = None  # type: str
+    title: str = None
     disabled = False
 
     def initLink(self, linkname, request_url):
diff --git a/lib/lp/services/webapp/publisher.py b/lib/lp/services/webapp/publisher.py
index c45cfed..18aca49 100644
--- a/lib/lp/services/webapp/publisher.py
+++ b/lib/lp/services/webapp/publisher.py
@@ -518,7 +518,7 @@ class LaunchpadView(UserAttributeCache):
         return None
 
     # Names of feature flags which affect a view.
-    related_features = {}  # type: Dict[str, bool]
+    related_features: Dict[str, bool] = {}
 
     @property
     def related_feature_info(self):
@@ -893,7 +893,7 @@ class Navigation:
         self.request = request
 
     # Set this if you want to set a new layer before doing any traversal.
-    newlayer = None  # type: Optional[Type[Any]]
+    newlayer: Optional[Type[Any]] = None
 
     def traverse(self, name):
         """Override this method to handle traversal.
diff --git a/lib/lp/services/webapp/vocabulary.py b/lib/lp/services/webapp/vocabulary.py
index b13b797..baedb43 100644
--- a/lib/lp/services/webapp/vocabulary.py
+++ b/lib/lp/services/webapp/vocabulary.py
@@ -277,7 +277,7 @@ class StormVocabularyBase(FilteredVocabularyBase):
     should derive from StormVocabularyBase.
     """
 
-    _order_by = None  # type: Optional[str]
+    _order_by: Optional[str] = None
     _clauses = []
 
     def __init__(self, context=None):
diff --git a/lib/lp/snappy/adapters/buildarch.py b/lib/lp/snappy/adapters/buildarch.py
index bd36747..df5acfd 100644
--- a/lib/lp/snappy/adapters/buildarch.py
+++ b/lib/lp/snappy/adapters/buildarch.py
@@ -99,13 +99,13 @@ class SnapArchitecture:
         :param build_error: string; build-error property from
             snapcraft.yaml.
         """
-        self.build_on = (
+        self.build_on: List[str] = (
             [build_on] if isinstance(build_on, str) else build_on
-        )  # type: List[str]
+        )
         if build_for:
-            self.build_for = (
+            self.build_for: List[str] = (
                 [build_for] if isinstance(build_for, str) else build_for
-            )  # type: List[str]
+            )
         else:
             self.build_for = self.build_on
         self.build_error = build_error
@@ -185,9 +185,7 @@ def determine_architectures_to_build(
         we can create builds for.
     :return: a list of `SnapBuildInstance`s.
     """
-    architectures_list = snapcraft_data.get(
-        "architectures"
-    )  # type: Optional[List]
+    architectures_list: Optional[List] = snapcraft_data.get("architectures")
 
     if architectures_list:
         # First, determine what style we're parsing.  Is it a list of
diff --git a/lib/lp/snappy/model/snapbuildbehaviour.py b/lib/lp/snappy/model/snapbuildbehaviour.py
index 39d8238..e0fa800 100644
--- a/lib/lp/snappy/model/snapbuildbehaviour.py
+++ b/lib/lp/snappy/model/snapbuildbehaviour.py
@@ -114,8 +114,8 @@ class SnapBuildBehaviour(BuilderProxyMixin, BuildFarmJobBehaviourBase):
         """
         Return the extra arguments required by the worker for the given build.
         """
-        build = self.build  # type: ISnapBuild
-        args = yield super().extraBuildArgs(logger=logger)  # type: BuildArgs
+        build: ISnapBuild = self.build
+        args: BuildArgs = yield super().extraBuildArgs(logger=logger)
         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/browser/archivesubscription.py b/lib/lp/soyuz/browser/archivesubscription.py
index 50e6903..2925e21 100644
--- a/lib/lp/soyuz/browser/archivesubscription.py
+++ b/lib/lp/soyuz/browser/archivesubscription.py
@@ -355,7 +355,7 @@ class PersonArchiveSubscriptionsView(LaunchpadView):
         # check results
         viewable_archives = []
         non_viewable_archives = []
-        archive_set = getUtility(IArchiveSet)  # type: IArchiveSet
+        archive_set: IArchiveSet = getUtility(IArchiveSet)
         for archive, has_view_permission in archive_set.checkViewPermission(
             archives, self.user
         ).items():
diff --git a/lib/lp/soyuz/model/binarypackagebuildbehaviour.py b/lib/lp/soyuz/model/binarypackagebuildbehaviour.py
index 4b65e9c..4f1387d 100644
--- a/lib/lp/soyuz/model/binarypackagebuildbehaviour.py
+++ b/lib/lp/soyuz/model/binarypackagebuildbehaviour.py
@@ -161,7 +161,7 @@ class BinaryPackageBuildBehaviour(BuildFarmJobBehaviourBase):
         das = build.distro_arch_series
 
         # Build extra arguments.
-        args = yield super().extraBuildArgs(logger=logger)  # type: BuildArgs
+        args: BuildArgs = yield super().extraBuildArgs(logger=logger)
         args["arch_indep"] = build.arch_indep
         args["suite"] = das.distroseries.getSuite(build.pocket)
 
diff --git a/lib/lp/soyuz/model/livefsbuildbehaviour.py b/lib/lp/soyuz/model/livefsbuildbehaviour.py
index d9f1cfd..aaa6a2f 100644
--- a/lib/lp/soyuz/model/livefsbuildbehaviour.py
+++ b/lib/lp/soyuz/model/livefsbuildbehaviour.py
@@ -101,9 +101,7 @@ class LiveFSBuildBehaviour(BuildFarmJobBehaviourBase):
         Return the extra arguments required by the worker for the given build.
         """
         build = self.build
-        base_args = yield super().extraBuildArgs(
-            logger=logger
-        )  # type: BuildArgs
+        base_args: BuildArgs = yield super().extraBuildArgs(logger=logger)
         # Non-trivial metadata values may have been security-wrapped, which
         # is pointless here and just gets in the way of xmlrpc.client
         # serialisation.
diff --git a/lib/lp/soyuz/security.py b/lib/lp/soyuz/security.py
index c7ef5a7..1622c45 100644
--- a/lib/lp/soyuz/security.py
+++ b/lib/lp/soyuz/security.py
@@ -293,7 +293,7 @@ class ViewArchive(AuthorizationBase):
 
     def checkAuthenticated(self, user):
         """Verify that the user can view the archive."""
-        archive_set = getUtility(IArchiveSet)  # type: IArchiveSet
+        archive_set: IArchiveSet = getUtility(IArchiveSet)
         return archive_set.checkViewPermission([self.obj], user.person)[
             self.obj
         ]
diff --git a/lib/lp/testing/__init__.py b/lib/lp/testing/__init__.py
index a095111..3c5f69e 100644
--- a/lib/lp/testing/__init__.py
+++ b/lib/lp/testing/__init__.py
@@ -1071,13 +1071,13 @@ class WebServiceTestCase(TestCaseWithFactory):
 
 
 class AbstractYUITestCase(TestCase):
-    layer = None  # type: Type[BaseLayer]
+    layer: Optional[Type[BaseLayer]] = None
     suite_name = ""
     # 30 seconds for the suite.
     suite_timeout = 30000
     # By default we do not restrict per-test or times.  yuixhr tests do.
-    incremental_timeout = None  # type: Optional[int]
-    initial_timeout = None  # type: Optional[int]
+    incremental_timeout: Optional[int] = None
+    initial_timeout: Optional[int] = None
     html_uri = None
     test_path = None
 
diff --git a/lib/lp/testing/pgsql.py b/lib/lp/testing/pgsql.py
index d2159d4..1ec7c26 100644
--- a/lib/lp/testing/pgsql.py
+++ b/lib/lp/testing/pgsql.py
@@ -92,7 +92,7 @@ class CursorWrapper:
     """
 
     real_cursor = None
-    last_executed_sql = []  # type: List[str]
+    last_executed_sql: List[str] = []
     record_sql = False
 
     def __init__(self, real_cursor):
@@ -167,16 +167,16 @@ def uninstallFakeConnect():
 
 class PgTestSetup:
     # Shared:
-    connections = []  # type: List[ConnectionWrapper]
+    connections: List[ConnectionWrapper] = []
     # Use a dynamically generated dbname:
     dynamic = object()
 
     template = "template1"
     # Needs to match configs/testrunner*/*:
     dbname = "launchpad_ftest"
-    dbuser = None  # type: Optional[str]
-    host = None  # type: Optional[str]
-    port = None  # type: Optional[int]
+    dbuser: Optional[str] = None
+    host: Optional[str] = None
+    port: Optional[int] = None
 
     # Class attributes. With PostgreSQL 8.4, pg_shdepend bloats
     # hugely when we drop and create databases, because this
diff --git a/lib/lp/testing/swift/tests/test_fixture.py b/lib/lp/testing/swift/tests/test_fixture.py
index 122e2b4..717b8c2 100644
--- a/lib/lp/testing/swift/tests/test_fixture.py
+++ b/lib/lp/testing/swift/tests/test_fixture.py
@@ -3,8 +3,6 @@
 
 """Testing the mock Swift test fixture."""
 
-__all__ = []  # type: List[str]
-
 from datetime import datetime
 from hashlib import md5
 from typing import List
@@ -21,6 +19,8 @@ from lp.testing.layers import BaseLayer
 from lp.testing.swift import fakeswift
 from lp.testing.swift.fixture import SwiftFixture
 
+__all__: List[str] = []
+
 
 class TestSwiftFixture(TestCase):
     layer = BaseLayer
diff --git a/lib/lp/testing/tests/test_layers.py b/lib/lp/testing/tests/test_layers.py
index 386586e..aa4546c 100644
--- a/lib/lp/testing/tests/test_layers.py
+++ b/lib/lp/testing/tests/test_layers.py
@@ -3,14 +3,14 @@
 
 """Tests for test layers."""
 
-__all__ = []  # type: List[str]
-
 import threading
 from typing import List
 
 from lp.testing import TestCase
 from lp.testing.layers import BaseLayer
 
+__all__: List[str] = []
+
 
 class TestThreadWaiting(TestCase):
     layer = BaseLayer
diff --git a/lib/lp/testing/tests/test_sampledata.py b/lib/lp/testing/tests/test_sampledata.py
index 1b17765..662267b 100644
--- a/lib/lp/testing/tests/test_sampledata.py
+++ b/lib/lp/testing/tests/test_sampledata.py
@@ -8,8 +8,6 @@ silently switching off some of our constraints. We can detect this by
 doing a dump and restore - this will fail if the data is corrupt.
 """
 
-__all__ = []  # type: List[str]
-
 import subprocess
 from typing import List
 
@@ -17,6 +15,8 @@ from lp.testing import TestCase
 from lp.testing.layers import DatabaseLayer
 from lp.testing.pgsql import PgTestSetup
 
+__all__: List[str] = []
+
 
 class SampleDataTestCase(TestCase):
     layer = DatabaseLayer
diff --git a/lib/lp/testing/tests/test_standard_yuixhr_test_template.py b/lib/lp/testing/tests/test_standard_yuixhr_test_template.py
index c7672d4..713cd4d 100644
--- a/lib/lp/testing/tests/test_standard_yuixhr_test_template.py
+++ b/lib/lp/testing/tests/test_standard_yuixhr_test_template.py
@@ -4,14 +4,14 @@
 """{Describe your test suite here}.
 """
 
-__all__ = []  # type: List[str]
-
 from typing import List
 
 from lp.testing import person_logged_in
 from lp.testing.factory import LaunchpadObjectFactory
 from lp.testing.yuixhr import login_as_person, make_suite, setup
 
+__all__: List[str] = []
+
 # This is one half of a YUI app test.  The other half is a .js test of
 # exactly the same name as your Python file, just with different file
 # extensions.
diff --git a/lib/lp/testing/tests/test_yuixhr_fixture.py b/lib/lp/testing/tests/test_yuixhr_fixture.py
index 65e6a08..87584b9 100644
--- a/lib/lp/testing/tests/test_yuixhr_fixture.py
+++ b/lib/lp/testing/tests/test_yuixhr_fixture.py
@@ -4,8 +4,6 @@
 """These are yui appserver fixtures for the yui appserver test code's tests.
 """
 
-__all__ = []  # type: List[str]
-
 from typing import Any, Dict, List, Tuple
 
 from zope.security.proxy import removeSecurityProxy
@@ -14,10 +12,12 @@ from lp.testing import login_person
 from lp.testing.factory import LaunchpadObjectFactory
 from lp.testing.yuixhr import login_as_person, make_suite, setup
 
+__all__: List[str] = []
+
 # The following are the fixtures needed by the tests.
 
 # We use this variable for test results.
-_received = []  # type: List[Tuple[str, Any, Dict[str, str]]]
+_received: List[Tuple[str, Any, Dict[str, str]]] = []
 
 
 @setup
diff --git a/lib/lp/testing/tests/test_yuixhr_fixture_facet.py b/lib/lp/testing/tests/test_yuixhr_fixture_facet.py
index 5e7a0e9..c54ea2c 100644
--- a/lib/lp/testing/tests/test_yuixhr_fixture_facet.py
+++ b/lib/lp/testing/tests/test_yuixhr_fixture_facet.py
@@ -4,12 +4,12 @@
 """Test the ability to specify a facet for the yuixhr tests.
 """
 
-__all__ = []  # type: List[str]
-
 from typing import List
 
 from lp.testing.yuixhr import make_suite
 
+__all__: List[str] = []
+
 
 def test_suite():
     # You can specify a facet, as found in the vhost.* names in
diff --git a/pyproject.toml b/pyproject.toml
index cf22bee..c276370 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3,7 +3,7 @@ line-length = 79
 target-version = ['py35']
 
 [tool.mypy]
-python_version = "3.5"
+python_version = "3.8"
 exclude = [
     '/interfaces/',
     'interfaces\.py$',