← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~jtv/launchpad/db-bug-790761 into lp:launchpad/db-devel

 

Jeroen T. Vermeulen has proposed merging lp:~jtv/launchpad/db-bug-790761 into lp:launchpad/db-devel with lp:~jtv/launchpad/db-bug-790161 as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #790761 in Launchpad itself: "Package copy policy adapter"
  https://bugs.launchpad.net/launchpad/+bug/790761

For more details, see:
https://code.launchpad.net/~jtv/launchpad/db-bug-790761/+merge/63203

= Summary =

We need to look up package-copying policies based on the database representation.


== Proposed fix ==

A new interface ICopyPolicy defines what we expect from copy policies.  A new adapter gives you the ICopyPolicy for a given PackageCopyPolicy enum value.


== Pre-implementation notes ==

This was originally spec'ed to take an identifying policy string.  But pushback on review for a preceding branch made us use an enumeration instead.

The ZCML registration for the adapter actually takes any DBItem value; I'm not aware of any way to adapt a specific DBEnumeration.  Robert didn't seem to know of one either.


== Implementation details ==

I rearranged the copy-policy inheritance hierarchy a bit.  Now that there's an interface for ICopyPolicy, there's no more need for a base class that raises "not implemented" errors from every method.  Instead we can have tests verify directly whether each policy class implements the interface, which is slightly more like what a compiled language would do.

That also made it reasonable to remodel the two concrete policy classes not as one being a modification of the other, but as both delegating most of their logic to a separate base class.  That should make the consequences of change slightly easier to oversee: the style rule of "don't inherit from concrete classes" is surprisingly effective at improving designs.


== Tests ==

{{{
./bin/test -vvc lp.soyuz.adapters.tests.test_copypolicy
./bin/test -vvc lp.soyuz.browser.tests.test_archive
}}}


== Demo and Q/A ==

The new stuff isn't really reachable yet.  As long as we can still request package updates and asynchronous package syncs, it should be fine.


= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/soyuz/adapters/tests/test_copypolicy.py
  lib/lp/soyuz/adapters/copypolicy.py
  lib/lp/soyuz/model/packagecopyjob.py
  lib/lp/registry/interfaces/distroseriesdifference.py
  database/schema/patch-2208-73-0.sql
  lib/lp/soyuz/configure.zcml
  lib/lp/soyuz/interfaces/packagecopyjob.py
  lib/lp/testing/factory.py
  lib/lp/soyuz/tests/test_packagecopyjob.py
  lib/lp/registry/browser/tests/test_distroseries.py
  lib/lp/soyuz/enums.py
  lib/lp/soyuz/interfaces/copypolicy.py
  lib/lp/soyuz/browser/tests/test_package_copying_mixin.py
  lib/lp/registry/browser/distroseries.py
  lib/lp/soyuz/browser/archive.py
-- 
https://code.launchpad.net/~jtv/launchpad/db-bug-790761/+merge/63203
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~jtv/launchpad/db-bug-790761 into lp:launchpad/db-devel.
=== modified file 'lib/lp/soyuz/adapters/copypolicy.py'
--- lib/lp/soyuz/adapters/copypolicy.py	2011-05-30 11:47:46 +0000
+++ lib/lp/soyuz/adapters/copypolicy.py	2011-06-02 08:51:24 +0000
@@ -15,57 +15,30 @@
     ]
 
 
+from zope.interface import implements
+
 from lp.registry.interfaces.pocket import PackagePublishingPocket
 from lp.registry.interfaces.series import SeriesStatus
-
-
-class BaseCopyPolicy:
-    """Encapsulation of the policies for copying a package in Launchpad."""
-
-    @property
-    def name(self):
-        """The name of this policy that's used in adapter lookups."""
-        raise AssertionError("Subclass must provide name property")
-
-    def autoApprove(self, archive, distroseries, pocket):
-        """Decide if the upload can be approved automatically or
-        should be held in the queue.
-
-        This should only be called for packages that are known not new.
-
-        :param archive: The target `IArchive` for the upload.
-        :param distroseries: The target `IDistroSeries` for the upload.
-        :param pocket: The target `PackagePublishingPocket` for the upload.
-        """
-        raise AssertionError("Subclass must provide autoApprove")
-
-    def autoApproveNew(self, archive, distroseries, pocket):
-        """Decide if a previously unknown package is approved automatically
-        or should be held in the queue.
-
-        :param archive: The target `IArchive` for the upload.
-        :param distroseries: The target `IDistroSeries` for the upload.
-        :param pocket: The target `PackagePublishingPocket` for the upload.
-        """
-        raise AssertionError("Subclass must provide autoApproveNew")
-
-    @property
-    def send_email(self):
-        """Whether or not the copy should send emails after completing."""
-        raise AssertionError("Subclass must provide send_email")
-
-
-class InsecureCopyPolicy(BaseCopyPolicy):
-    """A policy for copying from insecure sources."""
-
-    name = "insecure"
+from lp.soyuz.enums import PackageCopyPolicy
+from lp.soyuz.interfaces.copypolicy import ICopyPolicy
+
+
+class BasicCopyPolicy:
+    """Useful standard copy policy.
+
+    This policy auto-approves all PPA uploads.  For distribution archives it
+    auto-approves only uploads to the Release pocket, and only while the
+    series is not frozen.
+    """
 
     def autoApproveNew(self, archive, distroseries=None, pocket=None):
+        """See `ICopyPolicy`."""
         if archive.is_ppa:
             return True
         return False
 
     def autoApprove(self, archive, distroseries, pocket):
+        """See `ICopyPolicy`."""
         if archive.is_ppa:
             return True
 
@@ -80,16 +53,34 @@
 
         return False
 
-    @property
-    def send_email(self):
-        return True
-
-
-class SyncCopyPolicy(InsecureCopyPolicy):
+
+class InsecureCopyPolicy(BasicCopyPolicy):
+    """A policy for copying from insecure sources."""
+    implements(ICopyPolicy)
+
+    enum_value = PackageCopyPolicy.INSECURE
+
+    send_email = True
+
+
+class SyncCopyPolicy(BasicCopyPolicy):
     """A policy for mass 'sync' copies."""
-
-    name = "sync"
-
-    @property
-    def send_email(self):
-        return False
+    implements(ICopyPolicy)
+
+    enum_value = PackageCopyPolicy.SYNC
+
+    send_email = False
+
+
+policies = [
+    InsecureCopyPolicy,
+    SyncCopyPolicy,
+    ]
+
+
+enum_to_policy = dict((policy.enum_value, policy()) for policy in policies)
+
+
+def get_icopypolicy_for_packagecopypolicy(packagecopypolicy):
+    """Look up the `ICopyPolicy` for a given `PackageCopyPolicy`."""
+    return enum_to_policy[packagecopypolicy]

=== modified file 'lib/lp/soyuz/adapters/tests/test_copypolicy.py'
--- lib/lp/soyuz/adapters/tests/test_copypolicy.py	2011-05-30 11:47:46 +0000
+++ lib/lp/soyuz/adapters/tests/test_copypolicy.py	2011-06-02 08:51:24 +0000
@@ -1,16 +1,20 @@
 # Copyright 2011 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
+from canonical.launchpad.webapp.testing import verifyObject
 from canonical.testing.layers import ZopelessDatabaseLayer
-from lp.testing import TestCaseWithFactory
-
 from lp.registry.interfaces.pocket import PackagePublishingPocket
 from lp.registry.interfaces.series import SeriesStatus
 from lp.soyuz.adapters.copypolicy import (
     InsecureCopyPolicy,
     SyncCopyPolicy,
     )
-from lp.soyuz.enums import ArchivePurpose
+from lp.soyuz.interfaces.copypolicy import ICopyPolicy
+from lp.soyuz.enums import (
+    ArchivePurpose,
+    PackageCopyPolicy,
+    )
+from lp.testing import TestCaseWithFactory
 
 
 class TestCopyPolicy(TestCaseWithFactory):
@@ -30,7 +34,8 @@
         self.assertTrue(approved)
 
     def assertUnapproved(self, archive_purpose, method):
-        archive, distroseries, pocket = self._getUploadCriteria(archive_purpose)
+        archive, distroseries, pocket = self._getUploadCriteria(
+            archive_purpose)
         approved = method(archive, distroseries, pocket)
         self.assertFalse(approved)
 
@@ -42,7 +47,7 @@
         cp = InsecureCopyPolicy()
         self.assertApproved(ArchivePurpose.PPA, cp.autoApproveNew)
 
-    def test_insecure_approves_existing_distro_package_to_unfrozen_release(self):
+    def test_insecure_approves_known_distro_package_to_unfrozen_release(self):
         archive = self.factory.makeArchive(purpose=ArchivePurpose.PRIMARY)
         distroseries = self.factory.makeDistroSeries()
         pocket = PackagePublishingPocket.RELEASE
@@ -80,8 +85,6 @@
         cp = SyncCopyPolicy()
         self.assertFalse(cp.send_email)
 
-    def test_policy_names(self):
-        icp = InsecureCopyPolicy()
-        scp = SyncCopyPolicy()
-        self.assertEquals("insecure", icp.name)
-        self.assertEquals("sync", scp.name)
+    def test_policies_implement_ICopyPolicy(self):
+        for policy in PackageCopyPolicy.items:
+            self.assertTrue(verifyObject(ICopyPolicy, ICopyPolicy(policy)))

=== modified file 'lib/lp/soyuz/configure.zcml'
--- lib/lp/soyuz/configure.zcml	2011-05-29 21:18:09 +0000
+++ lib/lp/soyuz/configure.zcml	2011-06-02 08:51:24 +0000
@@ -919,6 +919,18 @@
       <allow interface=".interfaces.packagecopyjob.IPlainPackageCopyJob" />
     </class>
 
+    <!-- PackageCopyPolicy -->
+    <class class=".adapters.copypolicy.InsecureCopyPolicy">
+      <allow interface=".interfaces.copypolicy.ICopyPolicy"/>
+    </class>
+    <class class=".adapters.copypolicy.SyncCopyPolicy">
+      <allow interface=".interfaces.copypolicy.ICopyPolicy"/>
+    </class>
+    <adapter
+      for="lazr.enum.DBItem"
+      provides="lp.soyuz.interfaces.copypolicy.ICopyPolicy"
+      factory="lp.soyuz.adapters.copypolicy.get_icopypolicy_for_packagecopypolicy"/>
+
     <!-- PackageCopyRequestSet -->
     <securedutility
         class="lp.soyuz.model.packagecopyrequest.PackageCopyRequestSet"

=== modified file 'lib/lp/soyuz/enums.py'
--- lib/lp/soyuz/enums.py	2011-06-02 08:51:16 +0000
+++ lib/lp/soyuz/enums.py	2011-06-02 08:51:24 +0000
@@ -236,7 +236,10 @@
 
 
 class PackageCopyPolicy(DBEnumeratedType):
-    """Package copying policy."""
+    """Package copying policy.
+
+    Each of these is associated with one `ICopyPolicy`.
+    """
 
     INSECURE = DBItem(1, """
         Copy from insecure source.

=== added file 'lib/lp/soyuz/interfaces/copypolicy.py'
--- lib/lp/soyuz/interfaces/copypolicy.py	1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/interfaces/copypolicy.py	2011-06-02 08:51:24 +0000
@@ -0,0 +1,53 @@
+# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Package copy policies."""
+
+__metaclass__ = type
+__all__ = [
+    'ICopyPolicy',
+    ]
+
+
+from zope.interface import Interface
+from zope.schema import (
+    Bool,
+    Choice,
+    )
+
+from canonical.launchpad import _
+from lp.soyuz.enums import PackageCopyPolicy
+
+
+class ICopyPolicy(Interface):
+    """Policies for copying packages, as enumerated by `PackageCopyPolicy`."""
+
+    enum_value = Choice(
+        title=_("PackageCopyPolicy number associated with this policy."),
+        values=PackageCopyPolicy, readonly=True, required=True)
+
+    send_email = Bool(
+        title=_("Should completion of this copy be announced by email?"),
+        readonly=True, required=True)
+
+    def autoApprove(archive, distroseries, pocket):
+        """Can this upload of a known package be approved automatically?
+
+        This should only be called for packages that are known not new.
+
+        :param archive: The target `IArchive` for the upload.
+        :param distroseries: The target `IDistroSeries` for the upload.
+        :param pocket: The target `PackagePublishingPocket` for the upload.
+        :return: True if the upload can be auto-approved, or False if it
+            should be held in the queue.
+        """
+
+    def autoApproveNew(archive, distroseries, pocket):
+        """Can this upload of a new package be approved automatically?
+
+        :param archive: The target `IArchive` for the upload.
+        :param distroseries: The target `IDistroSeries` for the upload.
+        :param pocket: The target `PackagePublishingPocket` for the upload.
+        :return: True if the upload can be auto-approved, or False if it
+            should be held in the queue.
+        """

=== modified file 'lib/lp/soyuz/interfaces/packagecopyjob.py'
--- lib/lp/soyuz/interfaces/packagecopyjob.py	2011-06-02 08:51:16 +0000
+++ lib/lp/soyuz/interfaces/packagecopyjob.py	2011-06-02 08:51:24 +0000
@@ -86,7 +86,8 @@
 
     def create(cls, package_name, source_archive,
                target_archive, target_distroseries, target_pocket,
-               include_binaries=False, package_version=None):
+               include_binaries=False, package_version=None,
+               copy_policy=None):
         """Create a new `IPackageCopyJob`.
 
         :param package_name: The name of the source package to copy.
@@ -100,6 +101,7 @@
         :param include_binaries: See `do_copy`.
         :param package_version: The version string for the package version
             that is to be copied.
+        :param copy_policy: Applicable `PackageCopyPolicy`.
         """
 
     def getActiveJobs(target_archive):