← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:snap-build-channels-field into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:snap-build-channels-field into launchpad:master.

Commit message:
Refactor field declarations for snap build channels

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

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

We were repeating the list of known core snaps in quite a lot of places.  Start the process of reducing this by introducing a `SnapBuildChannelsField`, used instead of several similar `Dict` field declarations.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:snap-build-channels-field into launchpad:master.
diff --git a/lib/lp/charms/browser/charmrecipe.py b/lib/lp/charms/browser/charmrecipe.py
index b09b778..da8f66c 100644
--- a/lib/lp/charms/browser/charmrecipe.py
+++ b/lib/lp/charms/browser/charmrecipe.py
@@ -27,10 +27,7 @@ from zope.interface import (
     implementer,
     Interface,
     )
-from zope.schema import (
-    Dict,
-    TextLine,
-    )
+from zope.schema import TextLine
 from zope.security.interfaces import Unauthorized
 
 from lp import _
@@ -622,9 +619,9 @@ class CharmRecipeRequestBuildsView(LaunchpadFormView):
     class schema(Interface):
         """Schema for requesting a build."""
 
-        channels = Dict(
-            title="Source snap channels", key_type=TextLine(), required=True,
-            description=ICharmRecipe["auto_build_channels"].description)
+        channels = copy_field(
+            ICharmRecipe["auto_build_channels"], __name__="channels",
+            title="Source snap channels", required=True)
 
     custom_widget_channels = CharmRecipeBuildChannelsWidget
 
diff --git a/lib/lp/charms/interfaces/charmrecipe.py b/lib/lp/charms/interfaces/charmrecipe.py
index 5f95f7c..da09993 100644
--- a/lib/lp/charms/interfaces/charmrecipe.py
+++ b/lib/lp/charms/interfaces/charmrecipe.py
@@ -90,6 +90,7 @@ from lp.registry.interfaces.product import IProduct
 from lp.services.fields import (
     PersonChoice,
     PublicPersonChoice,
+    SnapBuildChannelsField,
     )
 from lp.services.webhooks.interfaces import IWebhookTarget
 from lp.snappy.validators.channels import channels_validator
@@ -332,13 +333,12 @@ class ICharmRecipeView(Interface):
 
     @call_with(requester=REQUEST_USER)
     @operation_parameters(
-        channels=Dict(
+        channels=SnapBuildChannelsField(
             title=_("Source snap channels to use for these builds."),
-            description=_(
+            description_prefix=_(
                 "A dictionary mapping snap names to channels to use for these "
-                "builds.  Currently only 'charmcraft', 'core', 'core18', "
-                "'core20', and 'core22' keys are supported."),
-            key_type=TextLine(), required=False))
+                "builds."),
+            required=False, extra_snap_names=["charmcraft"]))
     @export_factory_operation(ICharmRecipeBuildRequest, [])
     @operation_for_version("devel")
     def requestBuilds(requester, channels=None, architectures=None):
@@ -518,13 +518,12 @@ class ICharmRecipeEditableAttributes(Interface):
             "Whether this charm recipe is built automatically when its branch "
             "changes.")))
 
-    auto_build_channels = exported(Dict(
+    auto_build_channels = exported(SnapBuildChannelsField(
         title=_("Source snap channels for automatic builds"),
-        key_type=TextLine(), required=False, readonly=False,
-        description=_(
+        required=False, readonly=False, extra_snap_names=["charmcraft"],
+        description_prefix=_(
             "A dictionary mapping snap names to channels to use when building "
-            "this charm recipe.  Currently only 'charmcraft', 'core', "
-            "'core18', 'core20', and 'core22' keys are supported.")))
+            "this charm recipe.")))
 
     is_stale = exported(Bool(
         title=_("Charm recipe is stale and is due to be rebuilt."),
diff --git a/lib/lp/charms/interfaces/charmrecipebuild.py b/lib/lp/charms/interfaces/charmrecipebuild.py
index 4a33e19..0faac93 100644
--- a/lib/lp/charms/interfaces/charmrecipebuild.py
+++ b/lib/lp/charms/interfaces/charmrecipebuild.py
@@ -37,7 +37,6 @@ from zope.schema import (
     Bool,
     Choice,
     Datetime,
-    Dict,
     Int,
     TextLine,
     )
@@ -51,6 +50,7 @@ from lp.charms.interfaces.charmrecipe import (
     )
 from lp.registry.interfaces.person import IPerson
 from lp.services.database.constants import DEFAULT
+from lp.services.fields import SnapBuildChannelsField
 from lp.services.librarian.interfaces import ILibraryFileAlias
 from lp.soyuz.interfaces.distroarchseries import IDistroArchSeries
 
@@ -126,13 +126,12 @@ class ICharmRecipeBuildView(IPackageBuild):
     arch_tag = exported(
         TextLine(title=_("Architecture tag"), required=True, readonly=True))
 
-    channels = exported(Dict(
+    channels = exported(SnapBuildChannelsField(
         title=_("Source snap channels to use for this build."),
-        description=_(
+        description_prefix=_(
             "A dictionary mapping snap names to channels to use for this "
-            "build.  Currently only 'charmcraft', 'core', 'core18', 'core20', "
-            "and 'core22' keys are supported."),
-        key_type=TextLine()))
+            "build."),
+        extra_snap_names=["charmcraft"]))
 
     virtualized = Bool(
         title=_("If True, this build is virtualized."), readonly=True)
diff --git a/lib/lp/charms/interfaces/charmrecipejob.py b/lib/lp/charms/interfaces/charmrecipejob.py
index d353bb6..3f4a9b6 100644
--- a/lib/lp/charms/interfaces/charmrecipejob.py
+++ b/lib/lp/charms/interfaces/charmrecipejob.py
@@ -16,7 +16,6 @@ from zope.interface import (
     )
 from zope.schema import (
     Datetime,
-    Dict,
     List,
     Set,
     TextLine,
@@ -29,6 +28,7 @@ from lp.charms.interfaces.charmrecipe import (
     )
 from lp.charms.interfaces.charmrecipebuild import ICharmRecipeBuild
 from lp.registry.interfaces.person import IPerson
+from lp.services.fields import SnapBuildChannelsField
 from lp.services.job.interfaces.job import (
     IJob,
     IJobSource,
@@ -57,13 +57,12 @@ class ICharmRecipeRequestBuildsJob(IRunnableJob):
         title=_("The person requesting the builds."), schema=IPerson,
         required=True, readonly=True)
 
-    channels = Dict(
+    channels = SnapBuildChannelsField(
         title=_("Source snap channels to use for these builds."),
-        description=_(
+        description_prefix=_(
             "A dictionary mapping snap names to channels to use for these "
-            "builds.  Currently only 'charmcraft', 'core', 'core18', "
-            "'core20', and 'core22' keys are supported."),
-        key_type=TextLine(), required=False, readonly=True)
+            "builds."),
+        required=False, readonly=True, extra_snap_names=["charmcraft"])
 
     architectures = Set(
         title=_("If set, limit builds to these architecture tags."),
diff --git a/lib/lp/services/fields/__init__.py b/lib/lp/services/fields/__init__.py
index 7d5ff4e..07fc278 100644
--- a/lib/lp/services/fields/__init__.py
+++ b/lib/lp/services/fields/__init__.py
@@ -40,6 +40,7 @@ __all__ = [
     'ProductNameField',
     'PublicPersonChoice',
     'SearchTag',
+    'SnapBuildChannelsField',
     'StrippedTextLine',
     'Summary',
     'Tag',
@@ -75,6 +76,7 @@ from zope.schema import (
     Choice,
     Date,
     Datetime,
+    Dict,
     Int,
     Object,
     Text,
@@ -933,3 +935,25 @@ class IInlineObject(IObject):
 @implementer(IInlineObject)
 class InlineObject(Object):
     """An object that is represented as a dict rather than a URL reference."""
+
+
+class SnapBuildChannelsField(Dict):
+    """A field that holds source snap channels for builds."""
+
+    _core_snap_names = ["core", "core18", "core20", "core22"]
+
+    def __init__(self, description_prefix=None, description=None,
+                 extra_snap_names=None, **kwargs):
+        snap_names = list(self._core_snap_names)
+        if extra_snap_names is not None:
+            snap_names.extend(extra_snap_names)
+        if description is None:
+            if description_prefix is None:
+                description = ""
+            else:
+                description = description_prefix + "\n"
+            description += "Supported snap names: {}".format(", ".join(
+                "'{}'".format(snap_name) for snap_name in sorted(snap_names)))
+        super().__init__(
+            key_type=Choice(values=snap_names), value_type=TextLine(),
+            description=description, **kwargs)
diff --git a/lib/lp/snappy/browser/snap.py b/lib/lp/snappy/browser/snap.py
index 6055137..fa8396d 100644
--- a/lib/lp/snappy/browser/snap.py
+++ b/lib/lp/snappy/browser/snap.py
@@ -31,7 +31,6 @@ from zope.interface import (
     )
 from zope.schema import (
     Choice,
-    Dict,
     List,
     TextLine,
     )
@@ -418,9 +417,9 @@ class SnapRequestBuildsView(LaunchpadFormView):
                 'its configuration.'))
         pocket = copy_field(
             ISnapBuild['pocket'], title='Pocket', readonly=False)
-        channels = Dict(
-            title='Source snap channels', key_type=TextLine(), required=True,
-            description=ISnap['auto_build_channels'].description)
+        channels = copy_field(
+            ISnap['auto_build_channels'], __name__='channels',
+            title='Source snap channels', required=True)
 
     custom_widget_archive = SnapArchiveWidget
     custom_widget_distro_arch_series = LabeledMultiCheckBoxWidget
diff --git a/lib/lp/snappy/interfaces/snap.py b/lib/lp/snappy/interfaces/snap.py
index 957a378..c569135 100644
--- a/lib/lp/snappy/interfaces/snap.py
+++ b/lib/lp/snappy/interfaces/snap.py
@@ -104,6 +104,7 @@ from lp.registry.interfaces.role import IHasOwner
 from lp.services.fields import (
     PersonChoice,
     PublicPersonChoice,
+    SnapBuildChannelsField,
     URIField,
     )
 from lp.services.webhooks.interfaces import IWebhookTarget
@@ -407,13 +408,12 @@ class ISnapView(Interface):
         distro_arch_series=Reference(schema=IDistroArchSeries),
         pocket=Choice(vocabulary=PackagePublishingPocket),
         snap_base=Reference(schema=ISnapBase),
-        channels=Dict(
+        channels=SnapBuildChannelsField(
             title=_("Source snap channels to use for this build."),
-            description=_(
+            description_prefix=_(
                 "A dictionary mapping snap names to channels to use for this "
-                "build.  Currently only 'core', 'core18', 'core20', 'core22', "
-                "and 'snapcraft' keys are supported."),
-            key_type=TextLine(), required=False))
+                "build."),
+            required=False, extra_snap_names=["snapcraft"]))
     # Really ISnapBuild, patched in lp.snappy.interfaces.webservice.
     @export_factory_operation(Interface, [])
     @operation_for_version("devel")
@@ -437,13 +437,12 @@ class ISnapView(Interface):
     @operation_parameters(
         archive=Reference(schema=IArchive),
         pocket=Choice(vocabulary=PackagePublishingPocket),
-        channels=Dict(
+        channels=SnapBuildChannelsField(
             title=_("Source snap channels to use for this build."),
-            description=_(
+            description_prefix=_(
                 "A dictionary mapping snap names to channels to use for this "
-                "build.  Currently only 'core', 'core18', 'core20', 'core22', "
-                "and 'snapcraft' keys are supported."),
-            key_type=TextLine(), required=False))
+                "build."),
+            required=False, extra_snap_names=["snapcraft"]))
     @export_factory_operation(ISnapBuildRequest, [])
     @operation_for_version("devel")
     def requestBuilds(requester, archive, pocket, channels=None):
@@ -833,13 +832,12 @@ class ISnapEditableAttributes(IHasOwner):
             "used to select the pocket in the distribution's primary "
             "archive.")))
 
-    auto_build_channels = exported(Dict(
+    auto_build_channels = exported(SnapBuildChannelsField(
         title=_("Source snap channels for automatic builds"),
-        key_type=TextLine(), required=False, readonly=False,
-        description=_(
+        required=False, readonly=False, extra_snap_names=["snapcraft"],
+        description_prefix=_(
             "A dictionary mapping snap names to channels to use when building "
-            "this snap package.  Currently only 'core', 'core18', "
-            "'core20', 'core22', and 'snapcraft' keys are supported.")))
+            "this snap package.")))
 
     is_stale = exported(Bool(
         title=_("Snap package is stale and is due to be rebuilt."),
diff --git a/lib/lp/snappy/interfaces/snapbuild.py b/lib/lp/snappy/interfaces/snapbuild.py
index 6d41fbe..af9415f 100644
--- a/lib/lp/snappy/interfaces/snapbuild.py
+++ b/lib/lp/snappy/interfaces/snapbuild.py
@@ -53,6 +53,7 @@ from lp.buildmaster.interfaces.packagebuild import IPackageBuild
 from lp.registry.interfaces.person import IPerson
 from lp.registry.interfaces.pocket import PackagePublishingPocket
 from lp.services.database.constants import DEFAULT
+from lp.services.fields import SnapBuildChannelsField
 from lp.services.librarian.interfaces import ILibraryFileAlias
 from lp.snappy.interfaces.snap import (
     ISnap,
@@ -174,13 +175,12 @@ class ISnapBuildView(IPackageBuild, IPrivacy):
         title=_("The snap base to use for this build."),
         required=False, readonly=True))
 
-    channels = exported(Dict(
+    channels = exported(SnapBuildChannelsField(
         title=_("Source snap channels to use for this build."),
-        description=_(
+        description_prefix=_(
             "A dictionary mapping snap names to channels to use for this "
-            "build.  Currently only 'core', 'core18', 'core20', 'core22', "
-            "and 'snapcraft' keys are supported."),
-        key_type=TextLine()))
+            "build."),
+        extra_snap_names=["snapcraft"]))
 
     virtualized = Bool(
         title=_("If True, this build is virtualized."), readonly=True)
diff --git a/lib/lp/snappy/interfaces/snapjob.py b/lib/lp/snappy/interfaces/snapjob.py
index 9a5b1f2..131e424 100644
--- a/lib/lp/snappy/interfaces/snapjob.py
+++ b/lib/lp/snappy/interfaces/snapjob.py
@@ -17,7 +17,6 @@ from zope.interface import (
 from zope.schema import (
     Choice,
     Datetime,
-    Dict,
     List,
     Set,
     TextLine,
@@ -26,6 +25,7 @@ from zope.schema import (
 from lp import _
 from lp.registry.interfaces.person import IPerson
 from lp.registry.interfaces.pocket import PackagePublishingPocket
+from lp.services.fields import SnapBuildChannelsField
 from lp.services.job.interfaces.job import (
     IJob,
     IJobSource,
@@ -68,13 +68,12 @@ class ISnapRequestBuildsJob(IRunnableJob):
         title=_("The pocket that should be targeted."),
         vocabulary=PackagePublishingPocket, required=True, readonly=True)
 
-    channels = Dict(
+    channels = SnapBuildChannelsField(
         title=_("Source snap channels to use for these builds."),
-        description=_(
+        description_prefix=_(
             "A dictionary mapping snap names to channels to use for these "
-            "builds.  Currently only 'core', 'core18', 'core20', 'core22', "
-            "and 'snapcraft' keys are supported."),
-        key_type=TextLine(), required=False, readonly=True)
+            "builds."),
+        required=False, readonly=True, extra_snap_names=["snapcraft"])
 
     architectures = Set(
         title=_("If set, limit builds to these architecture tags."),