← Back to team overview

launchpad-reviewers team mailing list archive

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

 

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

Commit message:
lp.app: Apply "pyupgrade --py3-plus"

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/412212
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:pyupgrade-py3-app into launchpad:master.
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 34ba814..162ad2f 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -38,7 +38,7 @@ repos:
         alias: pyupgrade-py3
         name: pyupgrade (--py3-plus)
         args: [--keep-percent-format, --py3-plus]
-        files: ^lib/lp/answers/
+        files: ^lib/lp/(answers|app)/
 -   repo: https://github.com/PyCQA/isort
     rev: 5.9.2
     hooks:
diff --git a/lib/lp/app/browser/folder.py b/lib/lp/app/browser/folder.py
index 8c8c9ac..d38a5be 100644
--- a/lib/lp/app/browser/folder.py
+++ b/lib/lp/app/browser/folder.py
@@ -85,7 +85,7 @@ class ExportedFolder:
         name = os.path.basename(filename)
         try:
             fileobj = File(filename, name)
-        except IOError as ioerror:
+        except OSError as ioerror:
             expected = (errno.ENOENT, errno.EISDIR, errno.ENOTDIR)
             if ioerror.errno in expected:
                 # No such file or is a directory.
@@ -147,5 +147,4 @@ class ExportedImageFolder(ExportedFolder):
                 if os.path.exists(root + image_ext):
                     filename = filename + image_ext
                     break
-        return super(
-            ExportedImageFolder, self).prepareDataForServing(filename)
+        return super().prepareDataForServing(filename)
diff --git a/lib/lp/app/browser/launchpad.py b/lib/lp/app/browser/launchpad.py
index 6c5045f..110a34a 100644
--- a/lib/lp/app/browser/launchpad.py
+++ b/lib/lp/app/browser/launchpad.py
@@ -1105,7 +1105,7 @@ class LaunchpadTourFolder(ExportedFolder):
         """
         if name == 'source':
             raise NotFound(request, name)
-        return super(LaunchpadTourFolder, self).publishTraverse(request, name)
+        return super().publishTraverse(request, name)
 
     def browserDefault(self, request):
         """Redirect to index.html if the directory itself is requested."""
diff --git a/lib/lp/app/browser/launchpadform.py b/lib/lp/app/browser/launchpadform.py
index 642f044..220b013 100644
--- a/lib/lp/app/browser/launchpadform.py
+++ b/lib/lp/app/browser/launchpadform.py
@@ -17,7 +17,6 @@ __all__ = [
 from lazr.lifecycle.event import ObjectModifiedEvent
 from lazr.lifecycle.snapshot import Snapshot
 import simplejson
-import six
 import transaction
 from zope.event import notify
 from zope.formlib import form
@@ -212,7 +211,7 @@ class LaunchpadFormView(LaunchpadView):
             self.form_fields, self.prefix, context, self.request,
             data=self.initial_values, adapters=self.adapters,
             ignore_request=False)
-        for field_name, help_link in six.iteritems(self.help_links):
+        for field_name, help_link in self.help_links.items():
             self.widgets[field_name].help_link = help_link
 
     @property
diff --git a/lib/lp/app/browser/lazrjs.py b/lib/lp/app/browser/lazrjs.py
index 51c22da..1d4a85f 100644
--- a/lib/lp/app/browser/lazrjs.py
+++ b/lib/lp/app/browser/lazrjs.py
@@ -22,7 +22,6 @@ from lazr.restful.utils import (
     safe_hasattr,
     )
 import simplejson
-import six
 from zope.browserpage import ViewPageTemplateFile
 from zope.component import getUtility
 from zope.schema.interfaces import (
@@ -119,7 +118,7 @@ class TextWidgetBase(WidgetBase):
 
     def __init__(self, context, exported_field, title, content_box_id,
                  edit_view, edit_url, edit_title):
-        super(TextWidgetBase, self).__init__(
+        super().__init__(
             context, exported_field, content_box_id,
             edit_view, edit_url, edit_title)
         self.accept_empty = simplejson.dumps(self.optional_field)
@@ -181,7 +180,7 @@ class TextLineEditorWidget(TextWidgetBase, DefinedTagMixin):
             field value instead of the attribute's current value.
         :param width: Initial widget width.
         """
-        super(TextLineEditorWidget, self).__init__(
+        super().__init__(
             context, exported_field, title, content_box_id,
             edit_view, edit_url, edit_title)
         self.tag = tag
@@ -242,7 +241,7 @@ class TextAreaEditorWidget(TextWidgetBase):
         :param linkify_text: If True the HTML version of the text will have
             things that look like links made into anchors.
         """
-        super(TextAreaEditorWidget, self).__init__(
+        super().__init__(
             context, exported_field, title, content_box_id,
             edit_view, edit_url, edit_title)
         self.hide_empty = hide_empty
@@ -294,7 +293,7 @@ class InlineEditPickerWidget(WidgetBase):
             in and when JS is off.  Defaults to the edit_view on the context.
         :param edit_title: Used to set the title attribute of the anchor.
         """
-        super(InlineEditPickerWidget, self).__init__(
+        super().__init__(
             context, exported_field, content_box_id,
             edit_view, edit_url, edit_title)
         self.default_html = default_html
@@ -394,7 +393,7 @@ class InlinePersonEditPickerWidget(InlineEditPickerWidget):
         :param edit_title: Used to set the title attribute of the anchor.
         :param help_link: Used to set a link for help for the widget.
         """
-        super(InlinePersonEditPickerWidget, self).__init__(
+        super().__init__(
             context, exported_field, default_html, content_box_id, header,
             step_title, null_display_value,
             edit_view, edit_url, edit_title, help_link)
@@ -425,7 +424,7 @@ class InlinePersonEditPickerWidget(InlineEditPickerWidget):
         return self._show_create_team
 
     def getConfig(self):
-        config = super(InlinePersonEditPickerWidget, self).getConfig()
+        config = super().getConfig()
         config.update(dict(
             show_remove_button=self.optional_field,
             show_assign_me_button=self.show_assign_me_button,
@@ -477,7 +476,7 @@ class InlineMultiCheckboxWidget(WidgetBase):
         :param edit_title: Used to set the title attribute of the anchor.
 
         """
-        super(InlineMultiCheckboxWidget, self).__init__(
+        super().__init__(
             context, exported_field, content_box_id,
             edit_view, edit_url, edit_title)
 
@@ -502,7 +501,7 @@ class InlineMultiCheckboxWidget(WidgetBase):
             else:
                 vocabulary = exported_field.vocabularyName
 
-        if isinstance(vocabulary, six.string_types):
+        if isinstance(vocabulary, str):
             vocabulary = getVocabularyRegistry().get(context, vocabulary)
 
         # Construct checkbox data dict for each item in the vocabulary.
@@ -638,7 +637,7 @@ class BooleanChoiceWidget(WidgetBase, DefinedTagMixin):
             Automatically generated if this is not provided.
         :param header: The large text at the top of the choice popup.
         """
-        super(BooleanChoiceWidget, self).__init__(
+        super().__init__(
             context, exported_field, content_box_id,
             edit_view, edit_url, edit_title)
         self.header = header
@@ -698,7 +697,7 @@ class EnumChoiceWidget(WidgetBase):
         :param edit_title: Used to set the title attribute of the anchor.
         :param css_class_prefix: Added to the start of the enum titles.
         """
-        super(EnumChoiceWidget, self).__init__(
+        super().__init__(
             context, exported_field, content_box_id,
             edit_view, edit_url, edit_title)
         self.header = header
diff --git a/lib/lp/app/browser/multistep.py b/lib/lp/app/browser/multistep.py
index 278f8e1..6215eca 100644
--- a/lib/lp/app/browser/multistep.py
+++ b/lib/lp/app/browser/multistep.py
@@ -152,7 +152,7 @@ class StepView(LaunchpadFormView):
 
     _field_names = []
     step_name = ''
-    main_action_label = u'Continue'
+    main_action_label = 'Continue'
     next_step = None
 
     # Step information.  These get filled in by the controller view.
@@ -201,7 +201,7 @@ class StepView(LaunchpadFormView):
         `validateStep()` if they have any custom validation they need to
         perform.
         """
-        super(StepView, self).validate(data)
+        super().validate(data)
         if self.shouldProcess(data):
             self.validateStep(data)
 
@@ -243,7 +243,7 @@ class StepView(LaunchpadFormView):
                 operation.label = self.main_action_label
             actions.append(operation)
         self.actions = actions
-        return super(StepView, self).render()
+        return super().render()
 
     @property
     def cancel_url(self):
diff --git a/lib/lp/app/browser/root.py b/lib/lp/app/browser/root.py
index f0e85f7..694c8d2 100644
--- a/lib/lp/app/browser/root.py
+++ b/lib/lp/app/browser/root.py
@@ -14,7 +14,6 @@ import time
 import feedparser
 from lazr.batchnavigator.z3batching import batch
 import requests
-import six
 from zope.component import getUtility
 from zope.formlib.interfaces import ConversionError
 from zope.interface import Interface
@@ -79,7 +78,7 @@ class LaunchpadRootIndexView(HasAnnouncementsView, LaunchpadView):
 
     def initialize(self):
         """Set up featured projects list and the top featured project."""
-        super(LaunchpadRootIndexView, self).initialize()
+        super().initialize()
         # The maximum number of projects to be displayed as defined by the
         # number of items plus one top featured project.
         self.featured_projects = list(
@@ -275,7 +274,7 @@ class LaunchpadSearchView(LaunchpadFormView):
 
         Set the state of the search_params and matches.
         """
-        super(LaunchpadSearchView, self).__init__(context, request)
+        super().__init__(context, request)
         self.has_page_service = True
         self._bug = None
         self._question = None
@@ -346,7 +345,7 @@ class LaunchpadSearchView(LaunchpadFormView):
         """Focus the first widget when there are no matches."""
         if self.has_matches:
             return None
-        return super(LaunchpadSearchView, self).focusedElementScript()
+        return super().focusedElementScript()
 
     @property
     def bug(self):
@@ -427,7 +426,7 @@ class LaunchpadSearchView(LaunchpadFormView):
             if isinstance(error, ConversionError):
                 self.setFieldError(
                     'text', 'Can not convert your search term.')
-            elif isinstance(error, six.text_type):
+            elif isinstance(error, str):
                 continue
             elif (error.field_name == 'text'
                 and isinstance(error.errors, TooLong)):
@@ -435,7 +434,7 @@ class LaunchpadSearchView(LaunchpadFormView):
                     'text', 'The search text cannot exceed 250 characters.')
 
     @safe_action
-    @action(u'Search', name='search')
+    @action('Search', name='search')
     def search_action(self, action, data):
         """The Action executed when the user uses the search button.
 
@@ -579,7 +578,7 @@ class WindowedListBatch(batch._Batch):
 
     def __iter__(self):
         """Iterate over objects that are not None."""
-        for item in super(WindowedListBatch, self).__iter__():
+        for item in super().__iter__():
             if item is not None:
                 # Never yield None
                 yield item
@@ -614,7 +613,7 @@ class SiteSearchBatchNavigator(BatchNavigator):
         :param callback: Not used.
         """
         results = WindowedList(results, start, results.total)
-        super(SiteSearchBatchNavigator, self).__init__(results, request,
+        super().__init__(results, request,
             start=start, size=size, callback=callback,
             transient_parameters=transient_parameters,
             force_start=force_start, range_factory=range_factory)
diff --git a/lib/lp/app/browser/tales.py b/lib/lp/app/browser/tales.py
index 08d2a71..905ba0d 100644
--- a/lib/lp/app/browser/tales.py
+++ b/lib/lp/app/browser/tales.py
@@ -21,7 +21,6 @@ from lazr.enum import enumerated_type_registry
 from lazr.restful.utils import get_current_browser_request
 from lazr.uri import URI
 import pytz
-import six
 from six.moves.urllib.parse import quote
 from zope.browserpage import ViewPageTemplateFile
 from zope.component import (
@@ -688,9 +687,9 @@ class ObjectFormatterAPI:
         text = breadcrumb.detail
         if len(text) > 64:
             truncated = '%s...' % text[0:64]
-            if truncated.count(u'\u201c') > truncated.count(u'\u201cd'):
+            if truncated.count('\u201c') > truncated.count('\u201cd'):
                 # Close the open smartquote if it was dropped.
-                truncated += u'\u201d'
+                truncated += '\u201d'
             return truncated
         return text
 
@@ -1252,7 +1251,7 @@ class PersonFormatterAPI(ObjectFormatterAPI):
 
         The default URL for a person is to the mainsite.
         """
-        return super(PersonFormatterAPI, self).url(view_name, rootsite)
+        return super().url(view_name, rootsite)
 
     def _makeLink(self, view_name, rootsite, text):
         person = self._context
@@ -1315,7 +1314,7 @@ class MixedVisibilityError(Exception):
 class TeamFormatterAPI(PersonFormatterAPI):
     """Adapter for `ITeam` objects to a formatted string."""
 
-    hidden = u'<hidden>'
+    hidden = '<hidden>'
 
     def url(self, view_name=None, rootsite='mainsite'):
         """See `ObjectFormatterAPI`.
@@ -1327,7 +1326,7 @@ class TeamFormatterAPI(PersonFormatterAPI):
             # This person has no permission to view the team details.
             self._report_visibility_leak()
             return None
-        return super(TeamFormatterAPI, self).url(view_name, rootsite)
+        return super().url(view_name, rootsite)
 
     def api_url(self, context):
         """See `ObjectFormatterAPI`."""
@@ -1335,7 +1334,7 @@ class TeamFormatterAPI(PersonFormatterAPI):
             # This person has no permission to view the team details.
             self._report_visibility_leak()
             return None
-        return super(TeamFormatterAPI, self).api_url(context)
+        return super().api_url(context)
 
     def link(self, view_name, rootsite='mainsite'):
         """See `ObjectFormatterAPI`.
@@ -1349,7 +1348,7 @@ class TeamFormatterAPI(PersonFormatterAPI):
             self._report_visibility_leak()
             return structured(
                 '<span class="sprite team">%s</span>', self.hidden).escapedtext
-        return super(TeamFormatterAPI, self).link(view_name, rootsite)
+        return super().link(view_name, rootsite)
 
     def icon(self, view_name):
         team = self._context
@@ -1357,7 +1356,7 @@ class TeamFormatterAPI(PersonFormatterAPI):
             css_class = ObjectImageDisplayAPI(team).sprite_css()
             return '<span class="' + css_class + '"></span>'
         else:
-            return super(TeamFormatterAPI, self).icon(view_name)
+            return super().icon(view_name)
 
     def displayname(self, view_name, rootsite=None):
         """See `PersonFormatterAPI`."""
@@ -1366,7 +1365,7 @@ class TeamFormatterAPI(PersonFormatterAPI):
             # This person has no permission to view the team details.
             self._report_visibility_leak()
             return self.hidden
-        return super(TeamFormatterAPI, self).displayname(view_name, rootsite)
+        return super().displayname(view_name, rootsite)
 
     def unique_displayname(self, view_name):
         """See `PersonFormatterAPI`."""
@@ -1375,7 +1374,7 @@ class TeamFormatterAPI(PersonFormatterAPI):
             # This person has no permission to view the team details.
             self._report_visibility_leak()
             return self.hidden
-        return super(TeamFormatterAPI, self).unique_displayname(view_name)
+        return super().unique_displayname(view_name)
 
     def _report_visibility_leak(self):
         request = get_current_browser_request()
@@ -1426,7 +1425,7 @@ class CustomizableFormatter(ObjectFormatterAPI):
         """
         values = {
             k: v if v is not None else ''
-            for k, v in six.iteritems(self._link_summary_values())}
+            for k, v in self._link_summary_values().items()}
         return structured(self._link_summary_template, **values).escapedtext
 
     def _title_values(self):
@@ -1448,7 +1447,7 @@ class CustomizableFormatter(ObjectFormatterAPI):
             return None
         values = {
             k: v if v is not None else ''
-            for k, v in six.iteritems(self._title_values())}
+            for k, v in self._title_values().items()}
         return structured(title_template, **values).escapedtext
 
     def sprite_css(self):
@@ -1512,7 +1511,7 @@ class PillarFormatterAPI(CustomizableFormatter):
 
         The default URL for a pillar is to the mainsite.
         """
-        return super(PillarFormatterAPI, self).url(view_name, rootsite)
+        return super().url(view_name, rootsite)
 
     def _getLinkHTML(self, view_name, rootsite,
         template, custom_icon_template):
@@ -1550,11 +1549,11 @@ class PillarFormatterAPI(CustomizableFormatter):
         In the case of Products or ProjectGroups we display the custom
         icon, if one exists. The default URL for a pillar is to the mainsite.
         """
-        super(PillarFormatterAPI, self).link(view_name)
-        template = u'<a href="%(url)s" class="%(css_class)s">%(summary)s</a>'
+        super().link(view_name)
+        template = '<a href="%(url)s" class="%(css_class)s">%(summary)s</a>'
         custom_icon_template = (
-            u'<a href="%(url)s" class="bg-image" '
-            u'style="background-image: url(%(custom_icon)s)">%(summary)s</a>'
+            '<a href="%(url)s" class="bg-image" '
+            'style="background-image: url(%(custom_icon)s)">%(summary)s</a>'
             )
         return self._getLinkHTML(
             view_name, rootsite, template, custom_icon_template)
@@ -1566,15 +1565,15 @@ class PillarFormatterAPI(CustomizableFormatter):
         In the case of Products or ProjectGroups we display the custom
         icon, if one exists. The default URL for a pillar is to the mainsite.
         """
-        super(PillarFormatterAPI, self).link(view_name)
+        super().link(view_name)
         template = (
-            u'<a href="%(url)s" class="%(css_class)s">%(displayname)s</a>'
-            u'&nbsp;(<a href="%(url)s">%(name)s</a>)'
+            '<a href="%(url)s" class="%(css_class)s">%(displayname)s</a>'
+            '&nbsp;(<a href="%(url)s">%(name)s</a>)'
             )
         custom_icon_template = (
-            u'<a href="%(url)s" class="bg-image" '
-            u'style="background-image: url(%(custom_icon)s)">'
-            u'%(displayname)s</a>&nbsp;(<a href="%(url)s">%(name)s</a>)'
+            '<a href="%(url)s" class="bg-image" '
+            'style="background-image: url(%(custom_icon)s)">'
+            '%(displayname)s</a>&nbsp;(<a href="%(url)s">%(name)s</a>)'
             )
         return self._getLinkHTML(
             view_name, rootsite, template, custom_icon_template)
@@ -1739,7 +1738,7 @@ class GitRefFormatterAPI(CustomizableFormatter):
         """
         if self._context.repository_url is not None:
             return None
-        return super(GitRefFormatterAPI, self).url(view_name, rootsite)
+        return super().url(view_name, rootsite)
 
     def _link_summary_values(self):
         return {'display_name': self._context.display_name}
@@ -2085,7 +2084,7 @@ class BugTrackerFormatterAPI(ObjectFormatterAPI):
         """
         url = self._context.baseurl
         if url.startswith('mailto:') and getUtility(ILaunchBag).user is None:
-            return html_escape(u'mailto:<email address hidden>')
+            return html_escape('mailto:<email address hidden>')
         else:
             return structured(
                 '<a class="link-external" href="%(url)s">%(url)s</a>',
@@ -2118,7 +2117,7 @@ class BugTrackerFormatterAPI(ObjectFormatterAPI):
         anonymous = getUtility(ILaunchBag).user is None
         for alias in self._context.aliases:
             if anonymous and alias.startswith('mailto:'):
-                yield u'mailto:<email address hidden>'
+                yield 'mailto:<email address hidden>'
             else:
                 yield alias
 
@@ -2141,7 +2140,7 @@ class BugWatchFormatterAPI(ObjectFormatterAPI):
         an email address, only the summary is returned (i.e. no link).
         """
         if summary is None or len(summary) == 0:
-            summary = structured(u'&mdash;')
+            summary = structured('&mdash;')
         url = self._context.url
         if url.startswith('mailto:') and getUtility(ILaunchBag).user is None:
             return html_escape(summary)
@@ -2159,7 +2158,7 @@ class BugWatchFormatterAPI(ObjectFormatterAPI):
         summary = self._context.bugtracker.name
         remotebug = self._context.remotebug
         if remotebug is not None and len(remotebug) > 0:
-            summary = u'%s #%s' % (summary, remotebug)
+            summary = '%s #%s' % (summary, remotebug)
         return self._make_external_link(summary)
 
     def external_link_short(self):
@@ -2584,7 +2583,7 @@ class LinkFormatterAPI(ObjectFormatterAPI):
         if self._context.enabled:
             return self._context.url
         else:
-            return u''
+            return ''
 
 
 class RevisionAuthorFormatterAPI(ObjectFormatterAPI):
@@ -2629,7 +2628,7 @@ class PermissionRequiredQuery:
 
 
 class IMainTemplateFile(Interface):
-    path = TextLine(title=u'The absolute path to this main template.')
+    path = TextLine(title='The absolute path to this main template.')
 
 
 @adapter(LaunchpadLayer)
@@ -2776,8 +2775,7 @@ class TranslationGroupFormatterAPI(ObjectFormatterAPI):
 
     def url(self, view_name=None, rootsite='translations'):
         """See `ObjectFormatterAPI`."""
-        return super(TranslationGroupFormatterAPI, self).url(
-            view_name, rootsite)
+        return super().url(view_name, rootsite)
 
     def link(self, view_name, rootsite='translations'):
         """See `ObjectFormatterAPI`."""
@@ -2800,7 +2798,7 @@ class LanguageFormatterAPI(ObjectFormatterAPI):
 
     def url(self, view_name=None, rootsite='translations'):
         """See `ObjectFormatterAPI`."""
-        return super(LanguageFormatterAPI, self).url(view_name, rootsite)
+        return super().url(view_name, rootsite)
 
     def link(self, view_name, rootsite='translations'):
         """See `ObjectFormatterAPI`."""
@@ -2825,7 +2823,7 @@ class POFileFormatterAPI(ObjectFormatterAPI):
 
     def url(self, view_name=None, rootsite='translations'):
         """See `ObjectFormatterAPI`."""
-        return super(POFileFormatterAPI, self).url(view_name, rootsite)
+        return super().url(view_name, rootsite)
 
     def link(self, view_name, rootsite='translations'):
         """See `ObjectFormatterAPI`."""
diff --git a/lib/lp/app/browser/tests/test_base_layout.py b/lib/lp/app/browser/tests/test_base_layout.py
index 1cbdaa6..d3c0c0b 100644
--- a/lib/lp/app/browser/tests/test_base_layout.py
+++ b/lib/lp/app/browser/tests/test_base_layout.py
@@ -34,7 +34,7 @@ class TestBaseLayout(TestCaseWithFactory):
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        super(TestBaseLayout, self).setUp()
+        super().setUp()
         self.user = self.factory.makePerson(name='waffles')
         self.context = None
 
@@ -116,8 +116,7 @@ class TestBaseLayout(TestCaseWithFactory):
         self.assertEqual(['watermark-apps-portlet'], watermark['class'])
         if self.context.is_team:
             self.assertEqual('/@@/team-logo', watermark.img['src'])
-            self.assertEqual(
-                u'\u201cWaffles\u201d team', watermark.h2.a.string)
+            self.assertEqual('\u201cWaffles\u201d team', watermark.h2.a.string)
         else:
             self.assertEqual('/@@/person-logo', watermark.img['src'])
             self.assertEqual('Waffles', watermark.h2.a.string)
diff --git a/lib/lp/app/browser/tests/test_formatters.py b/lib/lp/app/browser/tests/test_formatters.py
index 25f355c..c917b5e 100644
--- a/lib/lp/app/browser/tests/test_formatters.py
+++ b/lib/lp/app/browser/tests/test_formatters.py
@@ -64,7 +64,7 @@ class ObjectFormatterAPITestCase(TestCaseWithFactory, FakeAdapterMixin):
         view.request.traversed_objects = [project, bug.bugtasks[0], view]
         formatter = ObjectFormatterAPI(view)
         self.assertEqual(
-            u'%s \u201cbang\u201d : Bugs : Fnord' % bug.displayname,
+            '%s \u201cbang\u201d : Bugs : Fnord' % bug.displayname,
             formatter.pagetitle())
 
     def test_pagetitle_last_breadcrumb_detail_too_long(self):
@@ -76,8 +76,8 @@ class ObjectFormatterAPITestCase(TestCaseWithFactory, FakeAdapterMixin):
             current_request=True, server_url='https://bugs.launchpad.test/')
         view.request.traversed_objects = [project, bug.bugtasks[0], view]
         formatter = ObjectFormatterAPI(view)
-        detail = u'%s \u201c%s\u201d' % (bug.displayname, title)
-        expected_title = u'%s...\u201d : Bugs : Fnord' % detail[0:64]
+        detail = '%s \u201c%s\u201d' % (bug.displayname, title)
+        expected_title = '%s...\u201d : Bugs : Fnord' % detail[0:64]
         self.assertEqual(expected_title, formatter.pagetitle())
 
     def test_global_css(self):
@@ -97,10 +97,10 @@ class TestPillarFormatterAPI(TestCaseWithFactory):
 
     layer = DatabaseFunctionalLayer
 
-    FORMATTER_CSS_CLASS = u'sprite product'
+    FORMATTER_CSS_CLASS = 'sprite product'
 
     def setUp(self):
-        super(TestPillarFormatterAPI, self).setUp()
+        super().setUp()
         self.product = self.factory.makeProduct()
         self.formatter = PillarFormatterAPI(self.product)
         self.product_url = canonical_url(
@@ -111,7 +111,7 @@ class TestPillarFormatterAPI(TestCaseWithFactory):
         # current context, formatted to include a custom icon if the
         # context has one, and to display the context summary.
         link = self.formatter.link(None)
-        template = u'<a href="%(url)s" class="%(css_class)s">%(summary)s</a>'
+        template = '<a href="%(url)s" class="%(css_class)s">%(summary)s</a>'
         mapping = {
             'url': self.product_url,
             'summary': self.product.displayname,
@@ -126,8 +126,8 @@ class TestPillarFormatterAPI(TestCaseWithFactory):
         # (displayname and name of the context).
         link = self.formatter.link_with_displayname(None)
         template = (
-            u'<a href="%(url)s" class="%(css_class)s">%(summary)s</a>'
-            u'&nbsp;(<a href="%(url)s">%(name)s</a>)'
+            '<a href="%(url)s" class="%(css_class)s">%(summary)s</a>'
+            '&nbsp;(<a href="%(url)s">%(name)s</a>)'
             )
         mapping = {
             'url': self.product_url,
diff --git a/lib/lp/app/browser/tests/test_launchpad.py b/lib/lp/app/browser/tests/test_launchpad.py
index dc697fd..3b2ceaa 100644
--- a/lib/lp/app/browser/tests/test_launchpad.py
+++ b/lib/lp/app/browser/tests/test_launchpad.py
@@ -167,8 +167,7 @@ class TestBranchTraversal(TestCaseWithFactory, TraversalMixin):
             path, notification, BrowserNotificationLevel.ERROR)
 
     def traverse(self, path, **kwargs):
-        return super(TestBranchTraversal, self).traverse(
-            path, '+branch', **kwargs)
+        return super().traverse(path, '+branch', **kwargs)
 
     def test_unique_name_traversal(self):
         # Traversing to /+branch/<unique_name> redirects to the page for that
@@ -216,14 +215,14 @@ class TestBranchTraversal(TestCaseWithFactory, TraversalMixin):
             InformationType.USERDATA)
         login(ANONYMOUS)
         requiredMessage = (
-            u"The target %s does not have a linked branch." %
+            "The target %s does not have a linked branch." %
             naked_product.name)
         self.assertDisplaysNotice(naked_product.name, requiredMessage)
 
     def test_nonexistent_product(self):
         # Traversing to /+branch/<no-such-product> displays an error message.
         non_existent = 'non-existent'
-        required_message = u"No such product: '%s'." % non_existent
+        required_message = "No such product: '%s'." % non_existent
         self.assertDisplaysError(non_existent, html_escape(required_message))
 
     def test_nonexistent_product_without_referer(self):
@@ -249,7 +248,7 @@ class TestBranchTraversal(TestCaseWithFactory, TraversalMixin):
         # user message on the same page.
         product = self.factory.makeProduct()
         requiredMessage = (
-            u"The target %s does not have a linked branch." % product.name)
+            "The target %s does not have a linked branch." % product.name)
         self.assertDisplaysNotice(product.name, requiredMessage)
 
     def test_distro_package_alias(self):
@@ -279,7 +278,7 @@ class TestBranchTraversal(TestCaseWithFactory, TraversalMixin):
         login(ANONYMOUS)
         path = ICanHasLinkedBranch(distro_package).bzr_path
         requiredMessage = (
-            u"The target %s does not have a linked branch." % path)
+            "The target %s does not have a linked branch." % path)
         self.assertDisplaysNotice(path, requiredMessage)
 
     def test_trailing_path_redirect(self):
@@ -323,7 +322,7 @@ class TestBranchTraversal(TestCaseWithFactory, TraversalMixin):
         login(ANONYMOUS)
         path = ICanHasLinkedBranch(series).bzr_path
         requiredMessage = (
-            u"The target %s does not have a linked branch." % path)
+            "The target %s does not have a linked branch." % path)
         self.assertDisplaysNotice(path, requiredMessage)
 
     def test_too_short_branch_name(self):
@@ -331,13 +330,13 @@ class TestBranchTraversal(TestCaseWithFactory, TraversalMixin):
         # that's too short to be a real unique name.
         owner = self.factory.makePerson()
         requiredMessage = html_escape(
-            u"Cannot understand namespace name: '%s'" % owner.name)
+            "Cannot understand namespace name: '%s'" % owner.name)
         self.assertDisplaysError('~%s' % owner.name, requiredMessage)
 
     def test_invalid_product_name(self):
         # error notification if the thing following +branch has an invalid
         # product name.
-        self.assertDisplaysError('_foo', u"Invalid name for product: _foo.")
+        self.assertDisplaysError('_foo', "Invalid name for product: _foo.")
 
     def test_invalid_product_name_without_referer(self):
         # error notification if the thing following +branch has an invalid
@@ -350,8 +349,7 @@ class TestCodeTraversal(TestCaseWithFactory, TraversalMixin):
     layer = DatabaseFunctionalLayer
 
     def traverse(self, path, **kwargs):
-        return super(TestCodeTraversal, self).traverse(
-            path, '+code', **kwargs)
+        return super().traverse(path, '+code', **kwargs)
 
     def test_project_bzr_branch(self):
         branch = self.factory.makeAnyBranch()
@@ -603,7 +601,7 @@ class TestPersonTraversal(TestCaseWithFactory, TraversalMixin):
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        super(TestPersonTraversal, self).setUp()
+        super().setUp()
         self.any_user = self.factory.makePerson()
         self.admin = getUtility(IPersonSet).getByName('name16')
         self.registry_expert = self.factory.makePerson()
@@ -638,7 +636,7 @@ class TestPersonTraversal(TestCaseWithFactory, TraversalMixin):
 
     def test_placeholder_person_visibility(self):
         # Verify a placeholder user is only traversable by an admin.
-        name = u'placeholder-person'
+        name = 'placeholder-person'
         person = getUtility(IPersonSet).createPlaceholderPerson(name, name)
         login_person(self.admin)
         segment = '~%s' % name
@@ -765,7 +763,7 @@ class TestProductTraversal(TestCaseWithFactory, TraversalMixin):
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        super(TestProductTraversal, self).setUp()
+        super().setUp()
         self.active_public_product = self.factory.makeProduct()
         self.inactive_public_product = self.factory.makeProduct()
         removeSecurityProxy(self.inactive_public_product).active = False
diff --git a/lib/lp/app/browser/tests/test_launchpadform.py b/lib/lp/app/browser/tests/test_launchpadform.py
index 95190a5..cb08991 100644
--- a/lib/lp/app/browser/tests/test_launchpadform.py
+++ b/lib/lp/app/browser/tests/test_launchpadform.py
@@ -41,11 +41,11 @@ from lp.testing.layers import (
 class TestInterface(Interface):
     """Test interface for the view below."""
 
-    normal = Text(title=u'normal', description=u'plain text')
+    normal = Text(title='normal', description='plain text')
 
     structured = has_structured_doc(
-        Text(title=u'structured',
-             description=u'<strong>structured text</strong'))
+        Text(title='structured',
+             description='<strong>structured text</strong'))
 
 
 class TestView(LaunchpadFormView):
@@ -90,9 +90,9 @@ class TestQueryTalesForHasStructuredDoc(TestCase):
 class TestHelpLinksInterface(Interface):
     """Test interface for the view below."""
 
-    nickname = Text(title=u'nickname')
+    nickname = Text(title='nickname')
 
-    displayname = Text(title=u'displayname')
+    displayname = Text(title='displayname')
 
 
 class TestHelpLinksView(LaunchpadFormView):
@@ -100,13 +100,13 @@ class TestHelpLinksView(LaunchpadFormView):
 
     schema = TestHelpLinksInterface
 
-    page_title = u"TestHelpLinksView"
+    page_title = "TestHelpLinksView"
     template = ViewPageTemplateFile(
         config.root + '/lib/lp/app/templates/generic-edit.pt')
 
     help_links = {
-        "nickname": u"http://widget.example.com/name";,
-        "displayname": u"http://widget.example.com/displayname";,
+        "nickname": "http://widget.example.com/name";,
+        "displayname": "http://widget.example.com/displayname";,
         }
 
 
@@ -122,10 +122,10 @@ class TestHelpLinks(TestCaseWithFactory):
         view.initialize()
         nickname_widget, displayname_widget = view.widgets
         self.assertEqual(
-            u"http://widget.example.com/name";,
+            "http://widget.example.com/name";,
             nickname_widget.help_link)
         self.assertEqual(
-            u"http://widget.example.com/displayname";,
+            "http://widget.example.com/displayname";,
             displayname_widget.help_link)
 
     def test_help_links_render(self):
@@ -140,20 +140,20 @@ class TestHelpLinks(TestCaseWithFactory):
         [nickname_help_link] = root.cssselect(
             "label[for$=nickname] ~ a[target=help]")
         self.assertEqual(
-            u"http://widget.example.com/name";,
+            "http://widget.example.com/name";,
             nickname_help_link.get("href"))
         [displayname_help_link] = root.cssselect(
             "label[for$=displayname] ~ a[target=help]")
         self.assertEqual(
-            u"http://widget.example.com/displayname";,
+            "http://widget.example.com/displayname";,
             displayname_help_link.get("href"))
 
 
 class TestWidgetDivInterface(Interface):
     """Test interface for the view below."""
 
-    single_line = TextLine(title=u'single_line')
-    multi_line = Text(title=u'multi_line')
+    single_line = TextLine(title='single_line')
+    multi_line = Text(title='multi_line')
     checkbox = Choice(
         vocabulary=SimpleVocabulary.fromItems(
             (('yes', True), ('no', False))))
diff --git a/lib/lp/app/browser/tests/test_launchpadroot.py b/lib/lp/app/browser/tests/test_launchpadroot.py
index 92cc935..5c3b1d7 100644
--- a/lib/lp/app/browser/tests/test_launchpadroot.py
+++ b/lib/lp/app/browser/tests/test_launchpadroot.py
@@ -127,7 +127,7 @@ class LaunchpadRootIndexViewTestCase(TestCaseWithFactory):
     layer = LaunchpadFunctionalLayer
 
     def setUp(self):
-        super(LaunchpadRootIndexViewTestCase, self).setUp()
+        super().setUp()
         # Use a FakeLogger fixture to prevent Memcached warnings to be
         # printed to stdout while browsing pages.
         self.useFixture(FakeLogger())
diff --git a/lib/lp/app/browser/tests/test_mixed_visibility.py b/lib/lp/app/browser/tests/test_mixed_visibility.py
index be6014a..ee7bcce 100644
--- a/lib/lp/app/browser/tests/test_mixed_visibility.py
+++ b/lib/lp/app/browser/tests/test_mixed_visibility.py
@@ -21,7 +21,7 @@ class TestMixedVisibility(TestCaseWithFactory):
         viewer = self.factory.makePerson()
         with person_logged_in(viewer):
             self.assertEqual(
-                u'<hidden>', TeamFormatterAPI(team).displayname(None))
+                '<hidden>', TeamFormatterAPI(team).displayname(None))
         self.assertEqual(1, len(self.oopses))
         self.assertTrue(
             'MixedVisibilityError' in self.oopses[0]['tb_text'])
diff --git a/lib/lp/app/browser/tests/test_page_macro.py b/lib/lp/app/browser/tests/test_page_macro.py
index 6184afd..fb8f1fd 100644
--- a/lib/lp/app/browser/tests/test_page_macro.py
+++ b/lib/lp/app/browser/tests/test_page_macro.py
@@ -63,7 +63,7 @@ class PageMacroDispatcherTestCase(TestPageMacroDispatcherMixin, TestCase):
     layer = FunctionalLayer
 
     def setUp(self):
-        super(PageMacroDispatcherTestCase, self).setUp()
+        super().setUp()
         self._setUpView()
 
     def test_base_template(self):
@@ -152,7 +152,7 @@ class PageMacroDispatcherInteractionTestCase(TestPageMacroDispatcherMixin,
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        super(PageMacroDispatcherInteractionTestCase, self).setUp()
+        super().setUp()
         self._setUpView()
         login_person(self.factory.makePerson())
 
@@ -165,7 +165,7 @@ class PageMacroDispatcherInteractionTestCase(TestPageMacroDispatcherMixin,
                 return FakeSecurityChecker(adaptee)
 
             def __init__(self, adaptee=None):
-                super(FakeSecurityChecker, self).__init__(adaptee)
+                super().__init__(adaptee)
 
             def checkUnauthenticated(self):
                 return has_permission
diff --git a/lib/lp/app/browser/tests/test_stringformatter.py b/lib/lp/app/browser/tests/test_stringformatter.py
index 0266453..1663f49 100644
--- a/lib/lp/app/browser/tests/test_stringformatter.py
+++ b/lib/lp/app/browser/tests/test_stringformatter.py
@@ -315,8 +315,8 @@ class TestParseDiff(TestCase):
     def test_unicode(self):
         # Diffs containing Unicode work too.
         self.assertEqual(
-            [('text', 1, 0, 0, u'Unicode \u1010')],
-            list(parse_diff(u'Unicode \u1010')))
+            [('text', 1, 0, 0, 'Unicode \u1010')],
+            list(parse_diff('Unicode \u1010')))
 
     def assertParses(self, expected, diff):
         diff_lines = diff.splitlines()
@@ -488,10 +488,10 @@ class TestDiffFormatter(TestCase):
     def test_format_unicode(self):
         # Sometimes the strings contain unicode, those should work too.
         self.assertEqual(
-            u'<table class="diff unidiff"><tr id="diff-line-1">'
-            u'<td class="line-no unselectable">1</td><td class="text">'
-            u'Unicode \u1010</td></tr></table>',
-            FormattersAPI(u'Unicode \u1010').format_diff())
+            '<table class="diff unidiff"><tr id="diff-line-1">'
+            '<td class="line-no unselectable">1</td><td class="text">'
+            'Unicode \u1010</td></tr></table>',
+            FormattersAPI('Unicode \u1010').format_diff())
 
     def test_cssClasses(self):
         # Different parts of the diff have different css classes.
@@ -586,14 +586,14 @@ class TestSideBySideDiffFormatter(TestCase):
     def test_format_unicode(self):
         # Sometimes the strings contain unicode, those should work too.
         self.assertEqual(
-            u'<table class="diff ssdiff"><tr id="diff-line-1">'
-            u'<td class="line-no unselectable" style="display: none">1</td>'
-            u'<td class="ss-line-no unselectable">0</td>'
-            u'<td class="text">Unicode \u1010</td>'
-            u'<td class="ss-line-no unselectable">0</td>'
-            u'<td class="text">Unicode \u1010</td>'
-            u'</tr></table>',
-            FormattersAPI(u'Unicode \u1010').format_ssdiff())
+            '<table class="diff ssdiff"><tr id="diff-line-1">'
+            '<td class="line-no unselectable" style="display: none">1</td>'
+            '<td class="ss-line-no unselectable">0</td>'
+            '<td class="text">Unicode \u1010</td>'
+            '<td class="ss-line-no unselectable">0</td>'
+            '<td class="text">Unicode \u1010</td>'
+            '</tr></table>',
+            FormattersAPI('Unicode \u1010').format_ssdiff())
 
     def test_cssClasses(self):
         # Different parts of the diff have different css classes.
@@ -743,7 +743,7 @@ class TestMarkdownDisabled(TestCase):
     layer = DatabaseFunctionalLayer  # Fixtures need the database for now
 
     def setUp(self):
-        super(TestMarkdownDisabled, self).setUp()
+        super().setUp()
         self.useFixture(FeatureFixture({'markdown.enabled': None}))
 
     def test_plain_text(self):
@@ -762,7 +762,7 @@ class TestMarkdown(TestCase):
     layer = DatabaseFunctionalLayer  # Fixtures need the database for now
 
     def setUp(self):
-        super(TestMarkdown, self).setUp()
+        super().setUp()
         self.useFixture(FeatureFixture({'markdown.enabled': 'on'}))
 
     def test_plain_text(self):
diff --git a/lib/lp/app/browser/tests/test_vocabulary.py b/lib/lp/app/browser/tests/test_vocabulary.py
index c7cf33d..2585a02 100644
--- a/lib/lp/app/browser/tests/test_vocabulary.py
+++ b/lib/lp/app/browser/tests/test_vocabulary.py
@@ -487,7 +487,7 @@ class HugeVocabularyJSONViewTestCase(TestCaseWithFactory):
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        super(HugeVocabularyJSONViewTestCase, self).setUp()
+        super().setUp()
         test_persons = []
         for name in range(1, 7):
             test_persons.append(
diff --git a/lib/lp/app/browser/tests/test_webservice.py b/lib/lp/app/browser/tests/test_webservice.py
index 0237760..7af01a7 100644
--- a/lib/lp/app/browser/tests/test_webservice.py
+++ b/lib/lp/app/browser/tests/test_webservice.py
@@ -33,7 +33,7 @@ class TestXHTMLRepresentations(TestCaseWithFactory):
 
     def test_text(self):
         # Test the XHTML representation of a text field.
-        text = u'\N{SNOWMAN} snowman@xxxxxxxxxxx bug 1'
+        text = '\N{SNOWMAN} snowman@xxxxxxxxxxx bug 1'
         # We need something that has an IPersonChoice, a project will do.
         product = self.factory.makeProduct()
         field = IProduct['description']
@@ -42,7 +42,7 @@ class TestXHTMLRepresentations(TestCaseWithFactory):
             (product, field, request), IFieldHTMLRenderer)
         # The representation is linkified html.
         self.assertEqual(
-            u'<p>\N{SNOWMAN} snowman@xxxxxxxxxxx '
+            '<p>\N{SNOWMAN} snowman@xxxxxxxxxxx '
             '<a href="/bugs/1" class="bug-link">bug 1</a></p>',
             renderer(text))
 
diff --git a/lib/lp/app/browser/vocabulary.py b/lib/lp/app/browser/vocabulary.py
index 4f3cde4..7cf7eb3 100644
--- a/lib/lp/app/browser/vocabulary.py
+++ b/lib/lp/app/browser/vocabulary.py
@@ -114,7 +114,7 @@ class IPickerEntrySource(Interface):
 
 @adapter(Interface)
 @implementer(IPickerEntrySource)
-class DefaultPickerEntrySourceAdapter(object):
+class DefaultPickerEntrySourceAdapter:
     """Adapts Interface to IPickerEntrySource."""
 
     def __init__(self, context):
@@ -151,9 +151,7 @@ class PersonPickerEntrySourceAdapter(DefaultPickerEntrySourceAdapter):
 
     def getPickerEntries(self, term_values, context_object, **kwarg):
         """See `IPickerEntrySource`"""
-        picker_entries = (
-            super(PersonPickerEntrySourceAdapter, self)
-                .getPickerEntries(term_values, context_object))
+        picker_entries = super().getPickerEntries(term_values, context_object)
 
         affiliated_context = IHasAffiliation(context_object, None)
         if affiliated_context is not None:
@@ -214,8 +212,7 @@ class BranchPickerEntrySourceAdapter(DefaultPickerEntrySourceAdapter):
     def getPickerEntries(self, term_values, context_object, **kwarg):
         """See `IPickerEntrySource`"""
         entries = (
-            super(BranchPickerEntrySourceAdapter, self)
-                    .getPickerEntries(term_values, context_object, **kwarg))
+            super().getPickerEntries(term_values, context_object, **kwarg))
         for branch, picker_entry in izip(term_values, entries):
             picker_entry.description = branch.bzr_identity
         return entries
@@ -241,8 +238,7 @@ class TargetPickerEntrySourceAdapter(DefaultPickerEntrySourceAdapter):
     def getPickerEntries(self, term_values, context_object, **kwarg):
         """See `IPickerEntrySource`"""
         entries = (
-            super(TargetPickerEntrySourceAdapter, self)
-                .getPickerEntries(term_values, context_object, **kwarg))
+            super().getPickerEntries(term_values, context_object, **kwarg))
         for target, picker_entry in izip(term_values, entries):
             picker_entry.description = self.getDescription(target)
             picker_entry.details = []
@@ -284,8 +280,7 @@ class SourcePackageNamePickerEntrySourceAdapter(
     def getPickerEntries(self, term_values, context_object, **kwarg):
         """See `IPickerEntrySource`"""
         entries = (
-            super(SourcePackageNamePickerEntrySourceAdapter, self)
-                .getPickerEntries(term_values, context_object, **kwarg))
+            super().getPickerEntries(term_values, context_object, **kwarg))
         for sourcepackagename, picker_entry in izip(term_values, entries):
             descriptions = getSourcePackageDescriptions([sourcepackagename])
             picker_entry.description = descriptions.get(
@@ -313,7 +308,7 @@ class DistributionSourcePackagePickerEntrySourceAdapter(
         return description
 
     def getPickerEntries(self, term_values, context_object, **kwarg):
-        this = super(DistributionSourcePackagePickerEntrySourceAdapter, self)
+        this = super()
         entries = this.getPickerEntries(term_values, context_object, **kwarg)
         for picker_entry in entries:
             picker_entry.alt_title = None
@@ -384,8 +379,7 @@ class ArchivePickerEntrySourceAdapter(DefaultPickerEntrySourceAdapter):
     def getPickerEntries(self, term_values, context_object, **kwarg):
         """See `IPickerEntrySource`"""
         entries = (
-            super(ArchivePickerEntrySourceAdapter, self)
-                    .getPickerEntries(term_values, context_object, **kwarg))
+            super().getPickerEntries(term_values, context_object, **kwarg))
         for archive, picker_entry in izip(term_values, entries):
             picker_entry.description = archive.reference
         return entries
diff --git a/lib/lp/app/model/launchpad.py b/lib/lp/app/model/launchpad.py
index 22156c3..b03359d 100644
--- a/lib/lp/app/model/launchpad.py
+++ b/lib/lp/app/model/launchpad.py
@@ -37,7 +37,7 @@ class ExceptionPrivacy(Privacy):
             private = True
         else:
             private = False
-        super(ExceptionPrivacy, self).__init__(error, private)
+        super().__init__(error, private)
 
 
 class InformationTypeMixin:
diff --git a/lib/lp/app/security.py b/lib/lp/app/security.py
index e3fb8b1..2500a80 100644
--- a/lib/lp/app/security.py
+++ b/lib/lp/app/security.py
@@ -129,7 +129,7 @@ class non_boolean_izip(izip):
 class DelegatedAuthorization(AuthorizationBase):
 
     def __init__(self, obj, forwarded_object=None, permission=None):
-        super(DelegatedAuthorization, self).__init__(obj)
+        super().__init__(obj)
         self.forwarded_object = forwarded_object
         if permission is not None:
             self.permission = permission
diff --git a/lib/lp/app/services.py b/lib/lp/app/services.py
index 154d44f..f18ccad 100644
--- a/lib/lp/app/services.py
+++ b/lib/lp/app/services.py
@@ -26,7 +26,7 @@ class ServiceFactory(Navigation):
     """
 
     def __init__(self):
-        super(ServiceFactory, self).__init__(None)
+        super().__init__(None)
 
     def traverse(self, name):
         return self.getService(name)
diff --git a/lib/lp/app/tests/test_security.py b/lib/lp/app/tests/test_security.py
index febb5b9..cd8ecce 100644
--- a/lib/lp/app/tests/test_security.py
+++ b/lib/lp/app/tests/test_security.py
@@ -59,7 +59,7 @@ def registerFakeSecurityAdapter(interface, permission, adapter=None):
 class FakeSecurityAdapter(AuthorizationBase):
 
     def __init__(self, adaptee=None):
-        super(FakeSecurityAdapter, self).__init__(adaptee)
+        super().__init__(adaptee)
         self.checkAuthenticated = FakeMethod()
         self.checkUnauthenticated = FakeMethod()
 
diff --git a/lib/lp/app/tests/test_tales.py b/lib/lp/app/tests/test_tales.py
index ffd5f91..d238e2b 100644
--- a/lib/lp/app/tests/test_tales.py
+++ b/lib/lp/app/tests/test_tales.py
@@ -162,7 +162,7 @@ class TestTeamFormatterAPI(TestCaseWithFactory):
     layer = LaunchpadFunctionalLayer
 
     def setUp(self):
-        super(TestTeamFormatterAPI, self).setUp()
+        super().setUp()
         icon = self.factory.makeLibraryFileAlias(
             filename='smurf.png', content_type='image/png')
         self.team = self.factory.makeTeam(
@@ -227,7 +227,7 @@ class TestTeamFormatterAPI(TestCaseWithFactory):
 
     def test_can_view_link(self):
         self._test_can_view_attribute(
-            'link', u'<span class="sprite team">&lt;hidden&gt;</span>')
+            'link', '<span class="sprite team">&lt;hidden&gt;</span>')
 
     def test_can_view_api_url(self):
         self._test_can_view_attribute('api_url')
@@ -396,7 +396,7 @@ class TestIRCNicknameFormatterAPI(TestCaseWithFactory):
         expected_html = test_tales(
             'nick/fmt:formatted_displayname', nick=ircID)
         self.assertEqual(
-            u'<strong>fred</strong>\n'
+            '<strong>fred</strong>\n'
             '<span class="lesser"> on </span>\n'
             '<strong>&lt;b&gt;irc.canonical.com&lt;/b&gt;</strong>\n',
             expected_html)
diff --git a/lib/lp/app/utilities/celebrities.py b/lib/lp/app/utilities/celebrities.py
index 99a7f70..b26d830 100644
--- a/lib/lp/app/utilities/celebrities.py
+++ b/lib/lp/app/utilities/celebrities.py
@@ -104,7 +104,7 @@ class PersonCelebrityDescriptor(CelebrityDescriptor):
 
     def __init__(self, name):
         PersonCelebrityDescriptor.names.add(name)
-        super(PersonCelebrityDescriptor, self).__init__(IPersonSet, name)
+        super().__init__(IPersonSet, name)
 
 
 class LanguageCelebrityDescriptor(CelebrityDescriptor):
diff --git a/lib/lp/app/validators/attachment.py b/lib/lp/app/validators/attachment.py
index 404790f..566ab85 100644
--- a/lib/lp/app/validators/attachment.py
+++ b/lib/lp/app/validators/attachment.py
@@ -17,9 +17,9 @@ def attachment_size_constraint(value):
     size = len(value)
     max_size = config.launchpad.max_attachment_size
     if size == 0:
-        raise LaunchpadValidationError(u'Cannot upload empty file.')
+        raise LaunchpadValidationError('Cannot upload empty file.')
     elif max_size > 0 and size > max_size:
         raise LaunchpadValidationError(
-            u'Cannot upload files larger than %i bytes' % max_size)
+            'Cannot upload files larger than %i bytes' % max_size)
     else:
         return True
diff --git a/lib/lp/app/vocabularies.py b/lib/lp/app/vocabularies.py
index a6e7a87..33eea4a 100644
--- a/lib/lp/app/vocabularies.py
+++ b/lib/lp/app/vocabularies.py
@@ -44,4 +44,4 @@ class InformationTypeVocabulary(SimpleVocabulary):
             term.name = type.name
             term.description = type.description
             terms.append(term)
-        super(InformationTypeVocabulary, self).__init__(terms)
+        super().__init__(terms)
diff --git a/lib/lp/app/webservice/tests/test_marshallers.py b/lib/lp/app/webservice/tests/test_marshallers.py
index 1a6ce21..96b305f 100644
--- a/lib/lp/app/webservice/tests/test_marshallers.py
+++ b/lib/lp/app/webservice/tests/test_marshallers.py
@@ -48,15 +48,15 @@ class TestTextFieldMarshaller(TestCaseWithFactory):
     def test_unmarshall_obfuscated(self):
         # Data is obfuscated if the user is anonynous.
         marshaller = TextFieldMarshaller(None, WebServiceTestRequest())
-        result = marshaller.unmarshall(None, u"foo@xxxxxxxxxxx")
-        self.assertEqual(u"<email address hidden>", result)
+        result = marshaller.unmarshall(None, "foo@xxxxxxxxxxx")
+        self.assertEqual("<email address hidden>", result)
 
     def test_unmarshall_not_obfuscated(self):
         # Data is not obfuscated if the user is authenticated.
         marshaller = TextFieldMarshaller(None, WebServiceTestRequest())
         with person_logged_in(self.factory.makePerson()):
-            result = marshaller.unmarshall(None, u"foo@xxxxxxxxxxx")
-        self.assertEqual(u"foo@xxxxxxxxxxx", result)
+            result = marshaller.unmarshall(None, "foo@xxxxxxxxxxx")
+        self.assertEqual("foo@xxxxxxxxxxx", result)
 
 
 class TestWebServiceObfuscation(TestCaseWithFactory):
diff --git a/lib/lp/app/widgets/date.py b/lib/lp/app/widgets/date.py
index 7d9a780..0f9c52d 100644
--- a/lib/lp/app/widgets/date.py
+++ b/lib/lp/app/widgets/date.py
@@ -128,7 +128,7 @@ class DateTimeWidget(TextWidget):
     __call__ = ViewPageTemplateFile('templates/datetime.pt')
 
     def __init__(self, context, request):
-        super(DateTimeWidget, self).__init__(context, request)
+        super().__init__(context, request)
         launchbag = getUtility(ILaunchBag)
         self.system_time_zone = launchbag.time_zone
 
@@ -313,7 +313,7 @@ class DateTimeWidget(TextWidget):
 
     def getInputValue(self):
         """Return the date, if it is in the allowed date range."""
-        value = super(DateTimeWidget, self).getInputValue()
+        value = super().getInputValue()
         if value is None:
             return None
         # Establish if the value is within the date range.
@@ -613,6 +613,6 @@ class DatetimeDisplayWidget(DisplayWidget):
         else:
             value = self.context.default
         if value == self.context.missing_value:
-            return u""
+            return ""
         value = value.astimezone(time_zone)
         return html_escape(value.strftime("%Y-%m-%d %H:%M:%S %Z"))
diff --git a/lib/lp/app/widgets/exception.py b/lib/lp/app/widgets/exception.py
index a7c68a7..e91f643 100644
--- a/lib/lp/app/widgets/exception.py
+++ b/lib/lp/app/widgets/exception.py
@@ -36,7 +36,7 @@ class WidgetInputError(_WidgetInputError):
 
 
 @implementer(IWidgetInputErrorView)
-class WidgetInputErrorView(object):
+class WidgetInputErrorView:
     """Rendering of IWidgetInputError"""
 
     def __init__(self, context, request):
diff --git a/lib/lp/app/widgets/itemswidgets.py b/lib/lp/app/widgets/itemswidgets.py
index 571f65f..c3142ba 100644
--- a/lib/lp/app/widgets/itemswidgets.py
+++ b/lib/lp/app/widgets/itemswidgets.py
@@ -38,7 +38,7 @@ class LaunchpadDropdownWidget(DropdownWidget):
 class PlainMultiCheckBoxWidget(MultiCheckBoxWidget):
     """MultiCheckBoxWidget that copes with CustomWidgetFactory."""
 
-    _joinButtonToMessageTemplate = u'%s&nbsp;%s '
+    _joinButtonToMessageTemplate = '%s&nbsp;%s '
 
     def __init__(self, field, vocabulary, request):
         # XXX flacoste 2006-07-23 Workaround Zope3 bug #545:
@@ -70,7 +70,7 @@ class PlainMultiCheckBoxWidget(MultiCheckBoxWidget):
         text = html_escape(text)
         id = '%s.%s' % (name, index)
         element = renderElement(
-            u'input', value=value, name=name, id=id,
+            'input', value=value, name=name, id=id,
             cssClass=cssClass, type='checkbox', **kw)
         return self._joinButtonToMessageTemplate % (element, text)
 
@@ -81,7 +81,7 @@ class LabeledMultiCheckBoxWidget(PlainMultiCheckBoxWidget):
     """
 
     _joinButtonToMessageTemplate = (
-        u'<label for="%s" style="font-weight: normal">%s&nbsp;%s</label> ')
+        '<label for="%s" style="font-weight: normal">%s&nbsp;%s</label> ')
 
     def _renderItem(self, index, text, value, name, cssClass, checked=False):
         """Render a checkbox and text in a label with a style attribute."""
@@ -93,7 +93,7 @@ class LabeledMultiCheckBoxWidget(PlainMultiCheckBoxWidget):
         value = html_escape(value)
         text = html_escape(text)
         id = '%s.%s' % (name, index)
-        elem = renderElement(u'input',
+        elem = renderElement('input',
                              value=value,
                              name=name,
                              id=id,
@@ -119,7 +119,7 @@ class LaunchpadRadioWidget(RadioWidget):
         value = html_escape(value)
         text = html_escape(text)
         id = '%s.%s' % (name, index)
-        elem = renderElement(u'input',
+        elem = renderElement('input',
                              value=value,
                              name=name,
                              id=id,
@@ -129,7 +129,7 @@ class LaunchpadRadioWidget(RadioWidget):
         if '<label' in text:
             return '%s&nbsp;%s' % (elem, text)
         else:
-            return renderElement(u'label',
+            return renderElement('label',
                                  contents='%s&nbsp;%s' % (elem, text),
                                  **{'style': 'font-weight: normal'})
 
@@ -145,27 +145,26 @@ class LaunchpadRadioWidgetWithDescription(LaunchpadRadioWidget):
     """
 
     _labelWithDescriptionTemplate = (
-        u'''<tr>
-              <td rowspan="2">%s</td>
-              <td><label for="%s">%s</label></td>
-            </tr>
-            <tr>
-              <td class="formHelp">%s</td>
-            </tr>
-         ''')
+        '''<tr>
+             <td rowspan="2">%s</td>
+             <td><label for="%s">%s</label></td>
+           </tr>
+           <tr>
+             <td class="formHelp">%s</td>
+           </tr>
+        ''')
     _labelWithoutDescriptionTemplate = (
-        u'''<tr>
-              <td>%s</td>
-              <td><label for="%s">%s</label></td>
-            </tr>
-         ''')
+        '''<tr>
+             <td>%s</td>
+             <td><label for="%s">%s</label></td>
+           </tr>
+        ''')
 
     def __init__(self, field, vocabulary, request):
         """Initialize the widget."""
         assert IEnumeratedType.providedBy(vocabulary), (
             'The vocabulary must implement IEnumeratedType')
-        super(LaunchpadRadioWidgetWithDescription, self).__init__(
-            field, vocabulary, request)
+        super().__init__(field, vocabulary, request)
         self.extra_hint = None
         self.extra_hint_class = None
 
@@ -187,7 +186,7 @@ class LaunchpadRadioWidgetWithDescription(LaunchpadRadioWidget):
         """Render an item of the list."""
         text = html_escape(text)
         id = '%s.%s' % (name, index)
-        elem = renderElement(u'input',
+        elem = renderElement('input',
                              value=value,
                              name=name,
                              id=id,
@@ -199,7 +198,7 @@ class LaunchpadRadioWidgetWithDescription(LaunchpadRadioWidget):
         """Render a selected item of the list."""
         text = html_escape(text)
         id = '%s.%s' % (name, index)
-        elem = renderElement(u'input',
+        elem = renderElement('input',
                              value=value,
                              name=name,
                              id=id,
@@ -245,8 +244,7 @@ class LaunchpadBooleanRadioWidget(LaunchpadRadioWidget):
         """Initialize the widget."""
         vocabulary = SimpleVocabulary.fromItems(
             ((self.TRUE, True), (self.FALSE, False)))
-        super(LaunchpadBooleanRadioWidget, self).__init__(
-            field, vocabulary, request)
+        super().__init__(field, vocabulary, request)
         # Suppress the missing value behaviour; this is a boolean field.
         self.required = True
         self._displayItemForMissingValue = False
@@ -261,7 +259,7 @@ class LaunchpadBooleanRadioWidget(LaunchpadRadioWidget):
         else:
             # value == self.FALSE.
             text = self.false_label
-        return super(LaunchpadBooleanRadioWidget, self)._renderItem(
+        return super()._renderItem(
             index, text, value, name, cssClass, checked=checked)
 
 
diff --git a/lib/lp/app/widgets/launchpadtarget.py b/lib/lp/app/widgets/launchpadtarget.py
index 57d684e..3a9b805 100644
--- a/lib/lp/app/widgets/launchpadtarget.py
+++ b/lib/lp/app/widgets/launchpadtarget.py
@@ -66,14 +66,14 @@ class LaunchpadTargetWidget(BrowserWidget, InputWidget):
             return
         fields = [
             Choice(
-                __name__='product', title=u'Project',
+                __name__='product', title='Project',
                 required=True, vocabulary=self.getProductVocabulary()),
             Choice(
-                __name__='distribution', title=u"Distribution",
+                __name__='distribution', title="Distribution",
                 required=True, vocabulary=self.getDistributionVocabulary(),
                 default=getUtility(ILaunchpadCelebrities).ubuntu),
             Choice(
-                __name__='package', title=u"Package",
+                __name__='package', title="Package",
                 required=False, vocabulary=self.getPackageVocabularyName()),
             ]
         self.distribution_widget = CustomWidgetFactory(
diff --git a/lib/lp/app/widgets/owner.py b/lib/lp/app/widgets/owner.py
index e9fccae..477f98c 100644
--- a/lib/lp/app/widgets/owner.py
+++ b/lib/lp/app/widgets/owner.py
@@ -15,7 +15,7 @@ from lp.services.webapp.interfaces import ILaunchBag
 
 
 @implementer(IInputWidget, IBrowserWidget)
-class RequestWidget(object):
+class RequestWidget:
     '''A widget that sets itself to a value calculated from request
 
     This is a bit of a hack, but necessary. If we are using the Zope
diff --git a/lib/lp/app/widgets/popup.py b/lib/lp/app/widgets/popup.py
index d5e4ec5..34264ae 100644
--- a/lib/lp/app/widgets/popup.py
+++ b/lib/lp/app/widgets/popup.py
@@ -15,7 +15,6 @@ __all__ = [
 
 from lazr.restful.utils import safe_hasattr
 import simplejson
-import six
 from zope.browserpage import ViewPageTemplateFile
 from zope.component import getUtility
 from zope.formlib.interfaces import ConversionError
@@ -77,14 +76,14 @@ class VocabularyPickerWidget(SingleDataHelper, ItemsWidgetBase):
         user currently has entered in the form.
         """
         # Pull form value using the parent class to avoid loop
-        formValue = super(VocabularyPickerWidget, self)._getFormInput()
+        formValue = super()._getFormInput()
         if not formValue:
             return []
 
         vocab = self.vocabulary
         # Special case - if the entered value is valid, it is an object
         # rather than a string (I think this is a bug somewhere)
-        if not isinstance(formValue, six.string_types):
+        if not isinstance(formValue, str):
             return [vocab.getTerm(formValue)]
 
         search_results = vocab.searchForTerms(formValue)
@@ -101,7 +100,7 @@ class VocabularyPickerWidget(SingleDataHelper, ItemsWidgetBase):
         val = self._getFormValue()
 
         # We have a valid object - return the corresponding token
-        if not isinstance(val, six.string_types):
+        if not isinstance(val, str):
             return self.vocabulary.getTerm(val).token
 
         # Just return the existing invalid token
@@ -323,8 +322,7 @@ class SourcePackageNameWidgetBase(DistributionSourcePackagePickerWidget):
     distribution_id = ''
 
     def __init__(self, field, vocabulary, request):
-        super(SourcePackageNameWidgetBase, self).__init__(
-            field, vocabulary, request)
+        super().__init__(field, vocabulary, request)
         self.cached_values = {}
         if bool(getFeatureFlag('disclosure.dsp_picker.enabled')):
             # The distribution may change later when we process form input,
diff --git a/lib/lp/app/widgets/product.py b/lib/lp/app/widgets/product.py
index 86e1d91..1b7ca66 100644
--- a/lib/lp/app/widgets/product.py
+++ b/lib/lp/app/widgets/product.py
@@ -57,7 +57,7 @@ from lp.services.webapp.vhosts import allvhosts
 class ProductBugTrackerWidget(LaunchpadRadioWidget):
     """Widget for selecting a product bug tracker."""
 
-    _joinButtonToMessageTemplate = u'%s&nbsp;%s'
+    _joinButtonToMessageTemplate = '%s&nbsp;%s'
     template = ViewPageTemplateFile('templates/product-bug-tracker.pt')
 
     def __init__(self, field, vocabulary, request):
@@ -101,7 +101,7 @@ class ProductBugTrackerWidget(LaunchpadRadioWidget):
         if checked:
             kw['checked'] = 'checked'
         id = '%s.%s' % (name, index)
-        elem = renderElement(u'input',
+        elem = renderElement('input',
                              value=value,
                              name=name,
                              id=id,
@@ -140,13 +140,12 @@ class ProductBugTrackerWidget(LaunchpadRadioWidget):
     def _renderLabel(self, text, index):
         """Render a label for the option with the specified index."""
         option_id = '%s.%s' % (self.name, index)
-        return u'<label for="%s" style="font-weight: normal">%s</label>' % (
+        return '<label for="%s" style="font-weight: normal">%s</label>' % (
             option_id, text)
 
     def error(self):
         """Concatenate errors from this widget and sub-widgets."""
-        errors = [super(ProductBugTrackerWidget, self).error(),
-                  self.upstream_email_address_widget.error()]
+        errors = [super().error(), self.upstream_email_address_widget.error()]
         return '; '.join(err for err in errors if len(err) > 0)
 
     def renderItems(self, value):
@@ -309,7 +308,7 @@ class LicenseWidget(CheckBoxMatrixWidget):
     items_by_category = None
 
     def __init__(self, field, vocabulary, request):
-        super(LicenseWidget, self).__init__(field, vocabulary, request)
+        super().__init__(field, vocabulary, request)
         # We want to put the license_info widget inside the licences widget's
         # HTML, for better alignment and JavaScript dynamism.  This is
         # accomplished by ghosting the form's license_info widget (see
@@ -344,7 +343,7 @@ class LicenseWidget(CheckBoxMatrixWidget):
         # This will return just the DBItem's text.  We want to wrap that text
         # in the URL to the licence, which is stored in the DBItem's
         # description.
-        value = super(LicenseWidget, self).textForValue(term)
+        value = super().textForValue(term)
         if term.value.url is None:
             return value
         else:
@@ -355,14 +354,13 @@ class LicenseWidget(CheckBoxMatrixWidget):
 
     def renderItem(self, index, text, value, name, cssClass):
         """See `ItemsEditWidgetBase`."""
-        rendered = super(LicenseWidget, self).renderItem(
-            index, text, value, name, cssClass)
+        rendered = super().renderItem(index, text, value, name, cssClass)
         self._categorize(value, rendered)
         return rendered
 
     def renderSelectedItem(self, index, text, value, name, cssClass):
         """See `ItemsEditWidgetBase`."""
-        rendered = super(LicenseWidget, self).renderSelectedItem(
+        rendered = super().renderSelectedItem(
             index, text, value, name, cssClass)
         category = self._categorize(value, rendered)
         # Increment the category counter.  This is used by the template to
@@ -390,7 +388,7 @@ class LicenseWidget(CheckBoxMatrixWidget):
         # individual checkbox items.  We don't actually care about the return
         # value though since we'll be building up our checkbox tables
         # manually.
-        super(LicenseWidget, self).__call__()
+        super().__call__()
         self.recommended = self._renderTable('recommended', 3)
         self.more = self._renderTable('more', 3)
         self.deprecated = self._renderTable('deprecated')
diff --git a/lib/lp/app/widgets/project.py b/lib/lp/app/widgets/project.py
index 4e5b796..151b15c 100644
--- a/lib/lp/app/widgets/project.py
+++ b/lib/lp/app/widgets/project.py
@@ -33,7 +33,7 @@ class ProjectScopeWidget(BrowserWidget, InputWidget):
     _error = None
 
     def __init__(self, field, vocabulary, request):
-        super(ProjectScopeWidget, self).__init__(field, request)
+        super().__init__(field, request)
 
         # We copy the title, description and vocabulary from the main
         # field since it determines the valid target types.
@@ -143,4 +143,4 @@ class ProjectScopeWidget(BrowserWidget, InputWidget):
         if self._error:
             return self._error.doc()
         else:
-            return u""
+            return ""
diff --git a/lib/lp/app/widgets/suggestion.py b/lib/lp/app/widgets/suggestion.py
index 6ce8e46..294489e 100644
--- a/lib/lp/app/widgets/suggestion.py
+++ b/lib/lp/app/widgets/suggestion.py
@@ -141,7 +141,7 @@ class SuggestionWidget(LaunchpadRadioWidget):
     def _renderLabel(self, text, index):
         """Render a label for the option with the specified index."""
         label = structured(
-            u'<label for="%s" style="font-weight: normal">%s</label>',
+            '<label for="%s" style="font-weight: normal">%s</label>',
             self._optionId(index), text)
         return label
 
@@ -183,7 +183,7 @@ class SuggestionWidget(LaunchpadRadioWidget):
         other_selection_onclick = (
             "this.form['%s'].focus()" % self.other_selection_widget.name)
 
-        elem = renderElement(u'input',
+        elem = renderElement('input',
                              value="other",
                              name=self.name,
                              id='%s.%s' % (self.name, index),
@@ -264,13 +264,13 @@ class TargetBranchWidget(SuggestionWidget):
         # radio buttons that is not a hyperlink in order to select the radio
         # button.  It was decided not to have the entire text as a link, but
         # instead to have a separate link to the branch details.
-        text = u'%s (<a href="%s">branch details</a>)'
+        text = '%s (<a href="%s">branch details</a>)'
         # If the branch is the development focus, say so.
         if branch == self.context.context.target.default_merge_target:
-            text += u"&ndash; <em>development focus</em>"
+            text += "&ndash; <em>development focus</em>"
         label = (
-            u'<label for="%s" style="font-weight: normal">' + text +
-            u'</label>')
+            '<label for="%s" style="font-weight: normal">' + text +
+            '</label>')
         return structured(
             label, self._optionId(index), branch.displayname,
             canonical_url(branch))
@@ -345,17 +345,17 @@ class TargetGitRepositoryWidget(SuggestionWidget):
         # radio buttons that is not a hyperlink in order to select the radio
         # button.  It was decided not to have the entire text as a link, but
         # instead to have a separate link to the repository details.
-        text = u'%s (<a href="%s">repository details</a>)'
+        text = '%s (<a href="%s">repository details</a>)'
         # If the repository is the default for the target, say so.
         if not IPerson.providedBy(repository.target):
             repository_set = getUtility(IGitRepositorySet)
             default_target = repository_set.getDefaultRepository(
                 repository.target)
             if repository == default_target:
-                text += u"&ndash; <em>default repository</em>"
+                text += "&ndash; <em>default repository</em>"
         label = (
-            u'<label for="%s" style="font-weight: normal">' + text +
-            u'</label>')
+            '<label for="%s" style="font-weight: normal">' + text +
+            '</label>')
         return structured(
             label, self._optionId(index), repository.display_name,
             canonical_url(repository))
diff --git a/lib/lp/app/widgets/tests/test_datetime.py b/lib/lp/app/widgets/tests/test_datetime.py
index fb1f8df..c748e93 100644
--- a/lib/lp/app/widgets/tests/test_datetime.py
+++ b/lib/lp/app/widgets/tests/test_datetime.py
@@ -17,8 +17,8 @@ class TestDateTimeWidget(TestCase):
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        super(TestDateTimeWidget, self).setUp()
-        field = Field(__name__='foo', title=u'Foo')
+        super().setUp()
+        field = Field(__name__='foo', title='Foo')
         request = LaunchpadTestRequest()
         self.widget = DateTimeWidget(field, request)
 
diff --git a/lib/lp/app/widgets/tests/test_itemswidgets.py b/lib/lp/app/widgets/tests/test_itemswidgets.py
index 51b31e2..903611b 100644
--- a/lib/lp/app/widgets/tests/test_itemswidgets.py
+++ b/lib/lp/app/widgets/tests/test_itemswidgets.py
@@ -49,7 +49,7 @@ class ItemWidgetTestCase(TestCaseWithFactory):
     UNSAFE_TERM = SimpleTerm('object-2', 'token-2', '<unsafe> &nbsp; title')
 
     def setUp(self):
-        super(ItemWidgetTestCase, self).setUp()
+        super().setUp()
         self.request = LaunchpadTestRequest()
         self.vocabulary = SimpleVocabulary([self.SAFE_TERM, self.UNSAFE_TERM])
         field = Choice(__name__='test_field', vocabulary=self.vocabulary)
@@ -186,7 +186,7 @@ class TestLaunchpadRadioWidgetWithDescription(TestCaseWithFactory):
         UNSAFE_TERM = Item('item-<2>', description='<unsafe> &nbsp; title')
 
     def setUp(self):
-        super(TestLaunchpadRadioWidgetWithDescription, self).setUp()
+        super().setUp()
         self.request = LaunchpadTestRequest()
         field = Choice(__name__='test_field', vocabulary=self.TestEnum)
         self.field = field.bind(object())
diff --git a/lib/lp/app/widgets/tests/test_launchpadtarget.py b/lib/lp/app/widgets/tests/test_launchpadtarget.py
index 5deb272..e94e145 100644
--- a/lib/lp/app/widgets/tests/test_launchpadtarget.py
+++ b/lib/lp/app/widgets/tests/test_launchpadtarget.py
@@ -59,15 +59,14 @@ class LaunchpadTargetWidgetTestCase(TestCaseWithFactory):
         }
 
     def setUp(self):
-        super(LaunchpadTargetWidgetTestCase, self).setUp()
+        super().setUp()
         self.distribution = self.factory.makeDistribution(name='fnord')
         distroseries = self.factory.makeDistroSeries(
             distribution=self.distribution)
         self.package = self.factory.makeDSPCache(
             distroseries=distroseries, sourcepackagename='snarf')
         self.project = self.factory.makeProduct('pting')
-        field = Reference(
-            __name__='target', schema=Interface, title=u'target')
+        field = Reference(__name__='target', schema=Interface, title='target')
         field = field.bind(Thing())
         request = LaunchpadTestRequest()
         self.widget = LaunchpadTargetWidget(field, request)
@@ -129,7 +128,7 @@ class LaunchpadTargetWidgetTestCase(TestCaseWithFactory):
     def test_setUpSubWidgets_dsp_picker_feature_flag(self):
         # The DistributionSourcePackageVocabulary is used when the
         # disclosure.dsp_picker.enabled is true.
-        with FeatureFixture({u"disclosure.dsp_picker.enabled": u"on"}):
+        with FeatureFixture({"disclosure.dsp_picker.enabled": "on"}):
             self.widget.setUpSubWidgets()
         self.assertIsInstance(
             self.widget.package_widget.context.vocabulary,
@@ -200,7 +199,7 @@ class LaunchpadTargetWidgetTestCase(TestCaseWithFactory):
         # The field value is the package when the package radio button
         # is selected and the package sub field has valid input.
         self.widget.request = LaunchpadTestRequest(form=self.form)
-        with FeatureFixture({u"disclosure.dsp_picker.enabled": u"on"}):
+        with FeatureFixture({"disclosure.dsp_picker.enabled": "on"}):
             self.widget.setUpSubWidgets()
             self.assertEqual(self.package, self.widget.getInputValue())
 
diff --git a/lib/lp/app/widgets/tests/test_popup.py b/lib/lp/app/widgets/tests/test_popup.py
index 5627eaa..3ea8bb0 100644
--- a/lib/lp/app/widgets/tests/test_popup.py
+++ b/lib/lp/app/widgets/tests/test_popup.py
@@ -27,7 +27,7 @@ class TestMetaClass(InterfaceClass):
             "test_filtered.item": Choice(vocabulary='DistributionOrProduct'),
             "test_target": Choice(vocabulary='DistributionSourcePackage'),
             }
-        super(TestMetaClass, self).__init__(
+        super().__init__(
             name, bases=bases, attrs=attrs, __doc__=__doc__,
             __module__=__module__)
 
@@ -42,7 +42,7 @@ class TestVocabularyPickerWidget(TestCaseWithFactory):
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
-        super(TestVocabularyPickerWidget, self).setUp()
+        super().setUp()
         self.context = self.factory.makeTeam()
         self.vocabulary_registry = getVocabularyRegistry()
         self.vocabulary = self.vocabulary_registry.get(
diff --git a/lib/lp/app/widgets/tests/test_suggestion.py b/lib/lp/app/widgets/tests/test_suggestion.py
index cb1b031..6139a9d 100644
--- a/lib/lp/app/widgets/tests/test_suggestion.py
+++ b/lib/lp/app/widgets/tests/test_suggestion.py
@@ -84,7 +84,7 @@ class TestSuggestionWidget(TestCaseWithFactory):
             self.other_selection_widget.onKeyPress = on_key_press
 
     def setUp(self):
-        super(TestSuggestionWidget, self).setUp()
+        super().setUp()
         request = LaunchpadTestRequest()
         vocabulary = SimpleHugeVocabulary(
             [self.SAFE_TERM, self.UNSAFE_TERM])
@@ -234,13 +234,13 @@ class TestTargetGitRepositoryWidget(TestCaseWithFactory):
         owner = self.factory.makePerson()
         this_source, this_target = self.factory.makeGitRefs(
             owner=owner, target=owner,
-            paths=[u"refs/heads/source", u"refs/heads/target"])
+            paths=["refs/heads/source", "refs/heads/target"])
         bmp = self.factory.makeBranchMergeProposalForGit(
             source_ref=this_source, target_ref=this_target,
             date_created=datetime.now(utc) - timedelta(days=1))
         other_source, other_target = self.factory.makeGitRefs(
             owner=owner, target=owner,
-            paths=[u"refs/heads/source", u"refs/heads/target"])
+            paths=["refs/heads/source", "refs/heads/target"])
         self.factory.makeBranchMergeProposalForGit(
             source_ref=other_source, target_ref=other_target,
             date_created=datetime.now(utc) - timedelta(days=1))
@@ -272,7 +272,7 @@ class TestTargetGitRepositoryWidget(TestCaseWithFactory):
         owner = self.factory.makePerson()
         source, target = self.factory.makeGitRefs(
             owner=owner, target=owner,
-            paths=[u"refs/heads/source", u"refs/heads/target"])
+            paths=["refs/heads/source", "refs/heads/target"])
         bmp = self.factory.makeBranchMergeProposalForGit(
             source_ref=source, target_ref=target,
             date_created=datetime.now(utc) - timedelta(days=1))
diff --git a/lib/lp/app/widgets/textwidgets.py b/lib/lp/app/widgets/textwidgets.py
index f4dd12c..7657af2 100644
--- a/lib/lp/app/widgets/textwidgets.py
+++ b/lib/lp/app/widgets/textwidgets.py
@@ -3,7 +3,6 @@
 
 import re
 
-import six
 from zope.browserpage import ViewPageTemplateFile
 from zope.formlib.textwidgets import (
     TextAreaWidget,
@@ -42,14 +41,14 @@ class TokensTextWidget(StrippedTextWidget):
         else is replaced with a single space.
         """
         normalised_text = re.sub(r'[^\w-]+', ' ', input)
-        return super(TokensTextWidget, self)._toFieldValue(normalised_text)
+        return super()._toFieldValue(normalised_text)
 
 
 class NoneableTextWidget(StrippedTextWidget):
     """A widget that that is None if it's value is empty or whitespace."""
 
     def _toFieldValue(self, input):
-        value = super(NoneableTextWidget, self)._toFieldValue(input)
+        value = super()._toFieldValue(input)
         if value == '':
             return None
         else:
@@ -63,13 +62,13 @@ class URIWidget(StrippedTextWidget):
     cssClass = 'urlTextType'
 
     def __init__(self, field, request):
-        super(URIWidget, self).__init__(field, request)
+        super().__init__(field, request)
         self.field = field
 
     def _toFieldValue(self, input):
         if isinstance(input, list):
             raise UnexpectedFormData('Only a single value is expected')
-        return super(URIWidget, self)._toFieldValue(input)
+        return super()._toFieldValue(input)
 
 
 class URIComponentWidget(LowerCaseTextWidget):
@@ -106,17 +105,17 @@ class DelimitedListWidget(TextAreaWidget):
 
     def __init__(self, field, value_type, request):
         # We don't use value_type.
-        super(DelimitedListWidget, self).__init__(field, request)
+        super().__init__(field, request)
 
     # The default splitting function, which splits on
     # white-space. Subclasses can override this if different
     # delimiting rules are needed.
-    split = staticmethod(six.text_type.split)
+    split = staticmethod(str.split)
 
     # The default joining function, which simply separates each list
     # item with a newline. Subclasses can override this if different
     # delimiters are needed.
-    join = staticmethod(u'\n'.join)
+    join = staticmethod('\n'.join)
 
     def _toFormValue(self, value):
         """Converts a list to a newline separated string.
@@ -134,7 +133,7 @@ class DelimitedListWidget(TextAreaWidget):
         By default, lists are displayed one item on a line:
 
           >>> names = ['fred', 'bob', 'harry']
-          >>> six.ensure_str(widget._toFormValue(names))
+          >>> widget._toFormValue(names)
           'fred\\r\\nbob\\r\\nharry'
         """
         if value == self.context.missing_value:
@@ -143,7 +142,7 @@ class DelimitedListWidget(TextAreaWidget):
             value = self._missing
         else:
             value = self.join(value)
-        return super(DelimitedListWidget, self)._toFormValue(value)
+        return super()._toFormValue(value)
 
     def _toFieldValue(self, value):
         """Convert the input string into a list.
@@ -166,8 +165,7 @@ class DelimitedListWidget(TextAreaWidget):
           'bob'
           'harry'
         """
-        value = super(
-            DelimitedListWidget, self)._toFieldValue(value)
+        value = super()._toFieldValue(value)
         if value == self.context.missing_value:
             return value
         else:
@@ -195,8 +193,7 @@ class NoneableDescriptionWidget(DescriptionWidget):
     """A widget that is None if it's value is empty or whitespace.."""
 
     def _toFieldValue(self, input):
-        value = super(
-            NoneableDescriptionWidget, self)._toFieldValue(input.strip())
+        value = super()._toFieldValue(input.strip())
         if value == '':
             return None
         else: