← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~jtv/launchpad/lint-distroseriesqueue-notify into lp:launchpad

 

Jeroen T. Vermeulen has proposed merging lp:~jtv/launchpad/lint-distroseriesqueue-notify into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~jtv/launchpad/lint-distroseriesqueue-notify/+merge/80541

= Summary =

Clean up some pre-existing lint in a doctest that I touched in the pre-cleanup for bug 876594.  I promised in the MP.

I ran the formatdoctest tool (with the -f option) and also cleaned up a bit by hand.


= Launchpad lint =

None left in this file.
-- 
https://code.launchpad.net/~jtv/launchpad/lint-distroseriesqueue-notify/+merge/80541
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~jtv/launchpad/lint-distroseriesqueue-notify into lp:launchpad.
=== modified file 'lib/lp/archiveuploader/tests/nascentupload-announcements.txt'
--- lib/lp/archiveuploader/tests/nascentupload-announcements.txt	2011-08-22 04:38:02 +0000
+++ lib/lp/archiveuploader/tests/nascentupload-announcements.txt	2011-10-27 08:29:29 +0000
@@ -46,8 +46,8 @@
     ...     return cmp(a[1], b[1])
 
     >>> def print_addrlist(field):
-    ...    for entry in field.split(','):
-    ...        print entry.strip()
+    ...    for entry in sorted([addr.strip() for addr in field.split(',')]):
+    ...        print entry
 
 Import the test keys to use 'insecure' policy.
 
@@ -443,8 +443,8 @@
     'Launchpad actually'
 
     >>> print_addrlist(notification['To'])
+    Daniel Silverstone <daniel.silverstone@xxxxxxxxxxxxx>
     Foo Bar <foo.bar@xxxxxxxxxxxxx>
-    Daniel Silverstone <daniel.silverstone@xxxxxxxxxxxxx>
 
     >>> notification['Subject']
     '[ubuntu/hoary-updates] bar 1.0-2 (Waiting for approval)'
@@ -479,8 +479,8 @@
     'Launchpad actually'
 
     >>> print_addrlist(notification['To'])
+    Daniel Silverstone <daniel.silverstone@xxxxxxxxxxxxx>
     Foo Bar <foo.bar@xxxxxxxxxxxxx>
-    Daniel Silverstone <daniel.silverstone@xxxxxxxxxxxxx>
 
     >>> notification['Subject']
     '[ubuntu/hoary-backports] bar 1.0-3 (Waiting for approval)'

=== modified file 'lib/lp/soyuz/adapters/notification.py'
--- lib/lp/soyuz/adapters/notification.py	2011-08-31 05:06:54 +0000
+++ lib/lp/soyuz/adapters/notification.py	2011-10-27 08:29:29 +0000
@@ -6,7 +6,6 @@
 __metaclass__ = type
 
 __all__ = [
-    'get_recipients',  # Available for testing only.
     'notify',
     ]
 
@@ -68,7 +67,7 @@
     }
     template = get_template(archive, 'rejected')
     body = template % information
-    to_addrs = get_recipients(
+    to_addrs = get_upload_notification_recipients(
         blamer, archive, distroseries, logger, changes=changes)
     debug(logger, "Sending rejection email.")
     if not to_addrs:
@@ -128,7 +127,7 @@
            summary_text=None, changes=None, changesfile_content=None,
            changesfile_object=None, action=None, dry_run=False,
            logger=None, announce_from_person=None, previous_version=None):
-    """Notify about
+    """Notify about an upload or package copy.
 
     :param blamer: The `IPerson` who is to blame for this notification.
     :param spr: The `ISourcePackageRelease` that was created.
@@ -187,7 +186,7 @@
     if not files and action != 'rejected':
         return
 
-    recipients = get_recipients(
+    recipients = get_upload_notification_recipients(
         blamer, archive, distroseries, logger, changes=changes, spr=spr,
         bprs=bprs)
 
@@ -304,13 +303,12 @@
     if distroseries.changeslist:
         information['ANNOUNCE'] = "Announcing to %s" % (
             distroseries.changeslist)
-    try:
-        changedby_person = email_to_person(info['changedby'])
-    except ParseMaintError:
-        # Some syncs (e.g. from Debian) will involve packages whose
-        # changed-by person was auto-created in LP and hence does not
-        # have a preferred email address set.
-        changedby_person = None
+
+    # Some syncs (e.g. from Debian) will involve packages whose
+    # changed-by person was auto-created in LP and hence does not have a
+    # preferred email address set.  We'll get a None here.
+    changedby_person = email_to_person(info['changedby'])
+
     if blamer is not None and blamer != changedby_person:
         signer_signature = person_to_email(blamer)
         if signer_signature != info['changedby']:
@@ -448,73 +446,69 @@
         return guess_encoding(s)
 
 
-def debug(logger, msg):
+def debug(logger, msg, *args, **kwargs):
     """Shorthand debug notation for publish() methods."""
     if logger is not None:
-        logger.debug(msg)
-
-
-def get_recipients(blamer, archive, distroseries, logger, changes=None,
-                   spr=None, bprs=None):
+        logger.debug(msg, *args, **kwargs)
+
+
+def is_valid_uploader(person, distribution):
+    """Is `person` an uploader for `distribution`?
+
+    A `None` person is not an uploader.
+    """
+    if person is None:
+        return None
+    else:
+        return person.isUploader(distribution)
+
+
+def get_upload_notification_recipients(blamer, archive, distroseries,
+                                       logger=None, changes=None, spr=None,
+                                       bprs=None):
     """Return a list of recipients for notification emails."""
-    candidate_recipients = []
     debug(logger, "Building recipients list.")
+    candidate_recipients = [
+        blamer,
+        ]
     info = fetch_information(spr, bprs, changes)
 
-    if info['changedby']:
-        try:
-            changer = email_to_person(info['changedby'])
-        except ParseMaintError:
-            changer = None
-    else:
-        changer = None
-
-    if info['maintainer']:
-        try:
-            maintainer = email_to_person(info['maintainer'])
-        except ParseMaintError:
-            maintainer = None
-    else:
-        maintainer = None
-
-    if blamer:
-        # This is a signed upload.
-        candidate_recipients.append(blamer)
-    else:
-        debug(logger,
-            "Changes file is unsigned, adding changer as recipient")
+    changer = email_to_person(info['changedby'])
+    maintainer = email_to_person(info['maintainer'])
+
+    if blamer is None:
+        debug(
+            logger, "Changes file is unsigned; adding changer as recipient.")
         candidate_recipients.append(changer)
 
     if archive.is_ppa:
         # For PPAs, any person or team mentioned explicitly in the
         # ArchivePermissions as uploaders for the archive will also
         # get emailed.
-        uploaders = [
-            permission.person for permission in
-                archive.getUploadersForComponent()]
-        candidate_recipients.extend(uploaders)
-
-    # If this is not a PPA, we also consider maintainer and changed-by.
-    elif blamer is not None:
-        if (maintainer and maintainer != blamer and
-                maintainer.isUploader(distroseries.distribution)):
-            debug(logger, "Adding maintainer to recipients")
-            candidate_recipients.append(maintainer)
-
-        if (changer and changer != blamer and
-                changer.isUploader(distroseries.distribution)):
-            debug(logger, "Adding changed-by to recipients")
-            candidate_recipients.append(changer)
-
-    # Now filter list of recipients for persons only registered in
-    # Launchpad to avoid spamming the innocent.
-    recipients = []
-    for person in candidate_recipients:
-        if person is None or person.preferredemail is None:
-            continue
-        recipient = format_address_for_person(person)
-        debug(logger, "Adding recipient: '%s'" % recipient)
-        recipients.append(recipient)
+        candidate_recipients.extend([
+            permission.person
+            for permission in archive.getUploadersForComponent()])
+    else:
+        # If this is not a PPA, we also consider maintainer and changed-by.
+        if blamer is not None:
+            if is_valid_uploader(maintainer, distroseries.distribution):
+                debug(logger, "Adding maintainer to recipients")
+                candidate_recipients.append(maintainer)
+
+            if is_valid_uploader(changer, distroseries.distribution):
+                debug(logger, "Adding changed-by to recipients")
+                candidate_recipients.append(changer)
+
+    # Collect email addresses to notify.  Skip persons who do not have a
+    # preferredemail set, such as people who have not activated their
+    # Launchpad accounts (and are therefore not expecting this email).
+    recipients = [
+        format_address_for_person(person)
+        for person in filter(None, set(candidate_recipients))
+            if person.preferredemail is not None]
+
+    for recipient in recipients:
+        debug(logger, "Adding recipient: '%s'", recipient)
 
     return recipients
 
@@ -572,18 +566,29 @@
 
 
 def email_to_person(fullemail):
-    """Return an IPerson given an RFC2047 email address."""
-    # The 2nd arg to s_f_m() doesn't matter as it won't fail since every-
-    # thing will have already parsed at this point.
-    (rfc822, rfc2047, name, email) = safe_fix_maintainer(
-        fullemail, "email")
-    return getUtility(IPersonSet).getByEmail(email)
+    """Return an `IPerson` given an RFC2047 email address.
+
+    :param fullemail: Potential email address.
+    :return: `IPerson` with the given email address.  None if there
+        isn't one, or if `fullemail` isn't a proper email address.
+    """
+    if not fullemail:
+        return None
+
+    try:
+        # The 2nd arg to s_f_m() doesn't matter as it won't fail since every-
+        # thing will have already parsed at this point.
+        (rfc822, rfc2047, name, email) = safe_fix_maintainer(
+            fullemail, "email")
+        return getUtility(IPersonSet).getByEmail(email)
+    except ParseMaintError:
+        return None
 
 
 def person_to_email(person):
     """Return a string of full name <e-mail address> given an IPerson."""
-    # This will use email.Header to encode any unicode.
     if person and person.preferredemail:
+        # This will use email.Header to encode any non-ASCII characters.
         return format_address_for_person(person)
 
 
@@ -595,12 +600,11 @@
     user (archive@xxxxxxxxxx).
     """
     katie = getUtility(ILaunchpadCelebrities).katie
-    try:
-        changed_by = email_to_person(changed_by_email)
-    except ParseMaintError:
-        return False
+    changed_by = email_to_person(changed_by_email)
     return (
-        spr and not bprs and changed_by == katie and
+        spr and
+        not bprs and
+        changed_by == katie and
         pocket != PackagePublishingPocket.SECURITY)
 
 

=== modified file 'lib/lp/soyuz/adapters/tests/test_notification.py'
--- lib/lp/soyuz/adapters/tests/test_notification.py	2011-08-31 05:06:54 +0000
+++ lib/lp/soyuz/adapters/tests/test_notification.py	2011-10-27 08:29:29 +0000
@@ -6,8 +6,9 @@
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 from email.utils import formataddr
+from textwrap import dedent
+
 from storm.store import Store
-from textwrap import dedent
 from zope.component import getUtility
 from zope.security.proxy import removeSecurityProxy
 
@@ -18,24 +19,24 @@
     )
 from lp.archivepublisher.utils import get_ppa_reference
 from lp.registry.interfaces.pocket import PackagePublishingPocket
+from lp.services.log.logger import BufferLogger
 from lp.services.mail.sendmail import format_address_for_person
-from lp.services.log.logger import BufferLogger
 from lp.soyuz.adapters.notification import (
     assemble_body,
     calculate_subject,
-    get_recipients,
     fetch_information,
+    get_upload_notification_recipients,
     is_auto_sync_upload,
+    notify,
+    person_to_email,
     reject_changes_file,
-    person_to_email,
-    notify,
     )
-from lp.soyuz.interfaces.component import IComponentSet
-from lp.soyuz.model.component import ComponentSelection
 from lp.soyuz.enums import (
     ArchivePurpose,
     PackageUploadCustomFormat,
     )
+from lp.soyuz.interfaces.component import IComponentSet
+from lp.soyuz.model.component import ComponentSelection
 from lp.soyuz.model.distroseriessourcepackagerelease import (
     DistroSeriesSourcePackageRelease,
     )
@@ -231,8 +232,8 @@
     def test_fetch_information_changes(self):
         changes = {
             'Date': '2001-01-01',
-            'Changed-By': 'Foo Bar <foo.bar@xxxxxxxxxxxxx>',
-            'Maintainer': 'Foo Bar <foo.bar@xxxxxxxxxxxxx>',
+            'Changed-By': 'Foo Bar <foo.bar@xxxxxxxxxxx>',
+            'Maintainer': 'Foo Bar <foo.bar@xxxxxxxxxxx>',
             'Changes': ' * Foo!',
             }
         info = fetch_information(
@@ -246,7 +247,7 @@
             info['maintainer_displayname'],
             ]
         for field in fields:
-            self.assertEqual('Foo Bar <foo.bar@xxxxxxxxxxxxx>', field)
+            self.assertEqual('Foo Bar <foo.bar@xxxxxxxxxxx>', field)
 
     def test_fetch_information_spr(self):
         creator = self.factory.makePerson(displayname=u"foø")
@@ -365,64 +366,67 @@
                     distroseries=distroseries, component=component))
         archive.newComponentUploader(maintainer, component)
         archive.newComponentUploader(changer, component)
-        return get_recipients(
+        return get_upload_notification_recipients(
             blamer, archive, distroseries, logger=None, changes=changes)
 
-    def test_get_recipients_good_emails(self):
-        # Test get_recipients with good email addresses..
-        blamer = self.factory.makePerson()
-        maintainer = self.factory.makePerson(
-            'maintainer@xxxxxxxxxxxxx', displayname='Maintainer')
-        changer = self.factory.makePerson(
-            'changer@xxxxxxxxxxxxx', displayname='Changer')
-        changes = {
-            'Date': '2001-01-01',
-            'Changed-By': 'Changer <changer@xxxxxxxxxxxxx>',
-            'Maintainer': 'Maintainer <maintainer@xxxxxxxxxxxxx>',
-            'Changes': ' * Foo!',
-            }
-        recipients = self._run_recipients_test(
-            changes, blamer, maintainer, changer)
-        expected = [format_address_for_person(p)
-                    for p in (blamer, maintainer, changer)]
-        self.assertEqual(expected, recipients)
-
-    def test_get_recipients_bad_maintainer_email(self):
-        blamer = self.factory.makePerson()
-        maintainer = self.factory.makePerson(
-            'maintainer@xxxxxxxxxxxxx', displayname='Maintainer')
-        changer = self.factory.makePerson(
-            'changer@xxxxxxxxxxxxx', displayname='Changer')
-        changes = {
-            'Date': '2001-01-01',
-            'Changed-By': 'Changer <changer@xxxxxxxxxxxxx>',
-            'Maintainer': 'Maintainer <maintainer at canonical.com>',
-            'Changes': ' * Foo!',
-            }
-        recipients = self._run_recipients_test(
-            changes, blamer, maintainer, changer)
-        expected = [format_address_for_person(p)
-                    for p in (blamer, changer)]
-        self.assertEqual(expected, recipients)
-
-    def test_get_recipients_bad_changedby_email(self):
-        # Test get_recipients with invalid changedby email address.
-        blamer = self.factory.makePerson()
-        maintainer = self.factory.makePerson(
-            'maintainer@xxxxxxxxxxxxx', displayname='Maintainer')
-        changer = self.factory.makePerson(
-            'changer@xxxxxxxxxxxxx', displayname='Changer')
-        changes = {
-            'Date': '2001-01-01',
-            'Changed-By': 'Changer <changer at canonical.com>',
-            'Maintainer': 'Maintainer <maintainer@xxxxxxxxxxxxx>',
-            'Changes': ' * Foo!',
-            }
-        recipients = self._run_recipients_test(
-            changes, blamer, maintainer, changer)
-        expected = [format_address_for_person(p)
-                    for p in (blamer, maintainer)]
-        self.assertEqual(expected, recipients)
+    def test_get_upload_notification_recipients_good_emails(self):
+        # Test get_upload_notification_recipients with good email addresses..
+        blamer = self.factory.makePerson()
+        maintainer = self.factory.makePerson(
+            'maintainer@xxxxxxxxxxx', displayname='Maintainer')
+        changer = self.factory.makePerson(
+            'changer@xxxxxxxxxxx', displayname='Changer')
+        changes = {
+            'Date': '2001-01-01',
+            'Changed-By': 'Changer <changer@xxxxxxxxxxx>',
+            'Maintainer': 'Maintainer <maintainer@xxxxxxxxxxx>',
+            'Changes': ' * Foo!',
+            }
+        recipients = self._run_recipients_test(
+            changes, blamer, maintainer, changer)
+        expected = [
+            format_address_for_person(person)
+            for person in (blamer, maintainer, changer)]
+        self.assertContentEqual(expected, recipients)
+
+    def test_get_upload_notification_recipients_bad_maintainer_email(self):
+        blamer = self.factory.makePerson()
+        maintainer = self.factory.makePerson(
+            'maintainer@xxxxxxxxxxx', displayname='Maintainer')
+        changer = self.factory.makePerson(
+            'changer@xxxxxxxxxxx', displayname='Changer')
+        changes = {
+            'Date': '2001-01-01',
+            'Changed-By': 'Changer <changer@xxxxxxxxxxx>',
+            'Maintainer': 'Maintainer <maintainer at example.com>',
+            'Changes': ' * Foo!',
+            }
+        recipients = self._run_recipients_test(
+            changes, blamer, maintainer, changer)
+        expected = [
+            format_address_for_person(person) for person in (blamer, changer)]
+        self.assertContentEqual(expected, recipients)
+
+    def test_get_upload_notification_recipients_bad_changedby_email(self):
+        # Test get_upload_notification_recipients with invalid changedby
+        # email address.
+        blamer = self.factory.makePerson()
+        maintainer = self.factory.makePerson(
+            'maintainer@xxxxxxxxxxx', displayname='Maintainer')
+        changer = self.factory.makePerson(
+            'changer@xxxxxxxxxxx', displayname='Changer')
+        changes = {
+            'Date': '2001-01-01',
+            'Changed-By': 'Changer <changer at example.com>',
+            'Maintainer': 'Maintainer <maintainer@xxxxxxxxxxx>',
+            'Changes': ' * Foo!',
+            }
+        recipients = self._run_recipients_test(
+            changes, blamer, maintainer, changer)
+        expected = [
+            format_address_for_person(person)
+            for person in (blamer, maintainer)]
+        self.assertContentEqual(expected, recipients)
 
     def test_assemble_body_handles_no_preferred_email_for_changer(self):
         # If changer has no preferred email address,

=== modified file 'lib/lp/soyuz/doc/distroseriesqueue-dist-upgrader.txt'
--- lib/lp/soyuz/doc/distroseriesqueue-dist-upgrader.txt	2011-07-21 23:10:26 +0000
+++ lib/lp/soyuz/doc/distroseriesqueue-dist-upgrader.txt	2011-10-27 08:29:29 +0000
@@ -60,6 +60,7 @@
     DEBUG Creating queue entry
     DEBUG Setting it to ACCEPTED
     DEBUG Building recipients list.
+    ...
     DEBUG Adding recipient: 'Foo Bar <foo.bar@xxxxxxxxxxxxx>'
     DEBUG Sent a mail:
     ...
@@ -235,6 +236,7 @@
     DEBUG Creating queue entry
     DEBUG Setting it to ACCEPTED
     DEBUG Building recipients list.
+    ...
     DEBUG Adding recipient: 'Foo Bar <foo.bar@xxxxxxxxxxxxx>'
     DEBUG Sent a mail:
     ...

=== modified file 'lib/lp/soyuz/doc/distroseriesqueue-notify.txt'
--- lib/lp/soyuz/doc/distroseriesqueue-notify.txt	2011-06-01 04:57:27 +0000
+++ lib/lp/soyuz/doc/distroseriesqueue-notify.txt	2011-10-27 08:29:29 +0000
@@ -1,263 +1,291 @@
-= Queue Notify =
-
-PackageUpload has a notify() method to send emails.
-We need to be logged into the security model in order to get any further.
-
-  >>> login('foo.bar@xxxxxxxxxxxxx')
+Queue Notify
+============
+
+PackageUpload has a notify() method to send emails. We need to be logged
+into the security model in order to get any further.
+
+    >>> login('foo.bar@xxxxxxxxxxxxx')
 
 Get a packageupload object for netapplet, which has a relatively intact
-set of supporting sample data.  It has rows in distribution, distroseries, 
-sourcepackagerelease, person and a librarian entry for the changes file 
-which are all needed for successful operation of notify().
-
-  >>> from lp.soyuz.interfaces.queue import IPackageUploadSet
-  >>> netapplet_upload = getUtility(IPackageUploadSet)[3]
-  >>> print netapplet_upload.displayname
-  netapplet
-
-Set up some library files for the netapplet source package.  These are not
-already present in the sample data.
-
-  >>> from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet
-  >>> from lp.archiveuploader.tests import datadir
-  >>> import os
-  >>> netapplet_spr = netapplet_upload.sources[0].sourcepackagerelease
-  >>> librarian = getUtility(ILibraryFileAliasSet)
-  >>> files = ['netapplet_1.0-1.dsc', 'netapplet_1.0.orig.tar.gz',
-  ...     'netapplet_1.0-1.diff.gz']
-  >>> for file in files:
-  ...     filepath = datadir('suite/netapplet_1.0-1/%s' % file)
-  ...     fileobj = open(filepath, 'rb')
-  ...     filesize = os.stat(filepath).st_size
-  ...     lfa = librarian.create(file, filesize, fileobj, 'dummytype')
-  ...     sprf = netapplet_spr.addFile(lfa)
-  ...     fileobj.close()
-
-The notify() method generates one email here on this unsigned package.  It
-requires an announcement list email address, a "changes_file_object" that is
-just an open file object for the original changes file, and a special
-logger object that will extract tracebacks for the purposes of this doctest.
-
-  >>> changes_file_path = datadir(
-  ...     'suite/netapplet_1.0-1/netapplet_1.0-1_source.changes')
-  >>> changes_file = open(changes_file_path,'r')
-  >>> from lp.services.log.logger import FakeLogger
-  >>> netapplet_upload.notify(
-  ...     changes_file_object=changes_file, logger=FakeLogger())
-  DEBUG Building recipients list.
-  DEBUG Changes file is unsigned, adding changer as recipient
-  ...
-  DEBUG Sent a mail:
-  ...
-  DEBUG     Recipients: Daniel Silverstone <daniel.silverstone@xxxxxxxxxxxxx>
-  ...
-  DEBUG above if files already exist in other distroseries.
-  ...
-  DEBUG signer of the above package.
+set of supporting sample data.  It has rows in distribution,
+distroseries, sourcepackagerelease, person and a librarian entry for the
+changes file which are all needed for successful operation of notify().
+
+    >>> from lp.soyuz.interfaces.queue import IPackageUploadSet
+    >>> netapplet_upload = getUtility(IPackageUploadSet)[3]
+    >>> print netapplet_upload.displayname
+    netapplet
+
+Set up some library files for the netapplet source package.  These are
+not already present in the sample data.
+
+    >>> from canonical.launchpad.interfaces.librarian import (
+    ...     ILibraryFileAliasSet)
+    >>> from lp.archiveuploader.tests import datadir
+    >>> import os
+    >>> netapplet_spr = netapplet_upload.sources[0].sourcepackagerelease
+    >>> librarian = getUtility(ILibraryFileAliasSet)
+    >>> files = [
+    ...     'netapplet_1.0-1.dsc',
+    ...     'netapplet_1.0.orig.tar.gz',
+    ...     'netapplet_1.0-1.diff.gz',
+    ...     ]
+    >>> for file in files:
+    ...     filepath = datadir('suite/netapplet_1.0-1/%s' % file)
+    ...     fileobj = open(filepath, 'rb')
+    ...     filesize = os.stat(filepath).st_size
+    ...     lfa = librarian.create(file, filesize, fileobj, 'dummytype')
+    ...     sprf = netapplet_spr.addFile(lfa)
+    ...     fileobj.close()
+
+The notify() method generates one email here on this unsigned package.
+It requires an announcement list email address, a "changes_file_object"
+that is just an open file object for the original changes file, and a
+special logger object that will extract tracebacks for the purposes of
+this doctest.
+
+    >>> changes_file_path = datadir(
+    ...     'suite/netapplet_1.0-1/netapplet_1.0-1_source.changes')
+    >>> changes_file = open(changes_file_path,'r')
+    >>> from lp.services.log.logger import FakeLogger
+    >>> netapplet_upload.notify(
+    ...     changes_file_object=changes_file, logger=FakeLogger())
+    DEBUG Building recipients list.
+    DEBUG Changes file is unsigned; adding changer as recipient.
+    ...
+    DEBUG Sent a mail:
+    ...
+    DEBUG   Recipients: ... Silverstone ...
+    ...
+    DEBUG above if files already exist in other distroseries.
+    ...
+    DEBUG signer of the above package.
 
 Helper functions to examine emails that were sent:
 
-  >>> from lp.services.mail import stub
-  >>> from lp.testing.mail_helpers import pop_notifications
-  >>> def by_to_addrs(a, b):
-  ...     return cmp(a[1], b[1])
+    >>> from lp.services.mail import stub
+    >>> from lp.testing.mail_helpers import pop_notifications
 
 There's only one email generated from the preceding upload:
 
-  >>> [notification] = pop_notifications()
+    >>> [notification] = pop_notifications()
 
 The mail headers contain our To: as set on the notify() call.  The
-subject contains "Accepted", the package name, its version and whether it's
-source or binary.  The Bcc field also always contains the uploader's email
-address.
-
-  >>> notification['To']
-  'Daniel Silverstone <daniel.silverstone@xxxxxxxxxxxxx>'
-  >>> notification['Bcc']
-  'Root <root@localhost>'
-
-  >>> notification['Subject']
-  '[ubuntu/breezy-autotest] netapplet 0.99.6-1 (New)'
+subject contains "Accepted", the package name, its version and whether
+it's source or binary.  The Bcc field also always contains the
+uploader's email address.
+
+    >>> notification['To']
+    'Daniel Silverstone <daniel.silverstone@xxxxxxxxxxxxx>'
+
+    >>> notification['Bcc']
+    'Root <root@localhost>'
+
+    >>> notification['Subject']
+    '[ubuntu/breezy-autotest] netapplet 0.99.6-1 (New)'
 
 The mail body contains a list of files that were accepted:
 
-  >>> print notification.get_payload(0) # doctest: -NORMALIZE_WHITESPACE
-  From nobody ...
-  ...
-  NEW: netapplet_1.0-1.dsc
-  NEW: netapplet_1.0.orig.tar.gz
-  NEW: netapplet_1.0-1.diff.gz
-  <BLANKLINE>
-  ...
-  You may have gotten the distroseries wrong.  If so, you may get warnings
-  above if files already exist in other distroseries.
-  <BLANKLINE>
-  -- =
-  <BLANKLINE>
-  You are receiving this email because you are the uploader, maintainer or
-  signer of the above package.
-  <BLANKLINE>
-
-Now we will process a signed package.  Signed packages will potentially have
-a different recipient list to unsigned ones; recipients for signed package 
-uploads can be the signer, the maintainer and the changer, where these people 
-are different.  Unsigned packages only send notifications to the changer.
-
-  >>> from zope.security.proxy import removeSecurityProxy
-  >>> from lp.registry.interfaces.gpg import IGPGKeySet
-  >>> gpgkey = getUtility(IGPGKeySet).get(1)
-  >>> removeSecurityProxy(netapplet_upload).signing_key = gpgkey
+    >>> print notification.get_payload(0) # doctest: -NORMALIZE_WHITESPACE
+    From nobody ...
+    ...
+    NEW: netapplet_1.0-1.dsc
+    NEW: netapplet_1.0.orig.tar.gz
+    NEW: netapplet_1.0-1.diff.gz
+    <BLANKLINE>
+    ...
+    You may have gotten the distroseries wrong.  If so, you may get warnings
+    above if files already exist in other distroseries.
+    <BLANKLINE>
+    -- =
+    <BLANKLINE>
+    You are receiving this email because you are the uploader, maintainer or
+    signer of the above package.
+    <BLANKLINE>
+
+Now we will process a signed package.  Signed packages will potentially
+have a different recipient list to unsigned ones; recipients for signed
+package uploads can be the signer, the maintainer and the changer, where
+these people are different.  Unsigned packages only send notifications
+to the changer.
+
+    >>> from zope.security.proxy import removeSecurityProxy
+    >>> from lp.registry.interfaces.gpg import IGPGKeySet
+    >>> gpgkey = getUtility(IGPGKeySet).get(1)
+    >>> removeSecurityProxy(netapplet_upload).signing_key = gpgkey
 
 Now request the email:
 
-  >>> changes_file_path = datadir(
-  ...     'suite/netapplet_1.0-1-signed/netapplet_1.0-1_source.changes')
-  >>> changes_file = open(changes_file_path,'r')
-  >>> netapplet_upload.setAccepted()
-  >>> netapplet_upload.notify(
-  ...     changes_file_object=changes_file, logger=FakeLogger())
-  DEBUG Building recipients list.
-  ...
-  DEBUG Sent a mail:
-  ...
-  DEBUG     Recipients: Foo Bar <foo.bar@xxxxxxxxxxxxx>, Daniel Silverstone <daniel.silverstone@xxxxxxxxxxxxx>
-  ...
-  DEBUG Announcing to autotest_changes@xxxxxxxxxx
-  ...
-  DEBUG Sent a mail:
-  ...
-
-There are two emails, the upload acknowledgement and the announcement, because
-this upload is already accepted.
-
-  >>> msgs = pop_notifications()
-  >>> len(msgs)
-  2
-
-The mail 'To:' addresses contain the signer and the changer's email.  The
-announcement email contains the serieses changeslist.
-
-  >>> [msg['To'] for msg in msgs]
-  ['Foo Bar <foo.bar@xxxxxxxxxxxxx>,\n\tDaniel Silverstone <daniel.silverstone@xxxxxxxxxxxxx>', 'autotest_changes@xxxxxxxxxx']
-
-The mail 'Bcc:' address is the uploader.  The announcement has the uploader
-and the Debian derivatives address for the package uploaded.
-
-  >>> [msg['Bcc'] for msg in msgs]
-  ['Root <root@localhost>', 'Root <root@localhost>, netapplet_derivatives@xxxxxxxxxxxxxxxxxxxxxx']
+    >>> changes_file_path = datadir(
+    ...     'suite/netapplet_1.0-1-signed/netapplet_1.0-1_source.changes')
+    >>> changes_file = open(changes_file_path,'r')
+    >>> netapplet_upload.setAccepted()
+    >>> netapplet_upload.notify(
+    ...     changes_file_object=changes_file, logger=FakeLogger())
+    DEBUG Building recipients list.
+    ...
+    DEBUG Sent a mail:
+    ...
+    DEBUG     Recipients: ... Bar ...
+    ...
+    DEBUG Announcing to autotest_changes@xxxxxxxxxx
+    ...
+    DEBUG Sent a mail:
+    ...
+
+There are two emails, the upload acknowledgement and the announcement,
+because this upload is already accepted.
+
+    >>> msgs = pop_notifications()
+    >>> len(msgs)
+    2
+
+The mail 'To:' addresses contain the signer and the changer's email.
+The announcement email contains the serieses changeslist.
+
+    >>> def to_lower(address):
+    ...     """Return lower-case version of email address."""
+    ...     return address.lower()
+
+    >>> def extract_addresses(header_field):
+    ...     """Extract and sort addresses from an email header field."""
+    ...     return sorted(
+    ...         [addr.strip() for addr in header_field.split(',')],
+    ...         key=to_lower)
+
+    >>> for addr in extract_addresses(msgs[0]['To']):
+    ...     print addr
+    Daniel Silverstone <daniel.silverstone@xxxxxxxxxxxxx>
+    Foo Bar <foo.bar@xxxxxxxxxxxxx>
+
+    >>> print msgs[1]['To']
+    autotest_changes@xxxxxxxxxx
+
+The mail 'Bcc:' address is the uploader.  The announcement has the
+uploader and the Debian derivatives address for the package uploaded.
+
+    >>> for msg in msgs:
+    ...     print extract_addresses(msg['Bcc'])
+    ['Root <root@localhost>']
+    ['netapplet_derivatives@xxxxxxxxxxxxxxxxxxxxxx', 'Root <root@localhost>']
 
 The mail 'From:' addresses are the uploader and the changer.
 
-  >>> [msg['From'] for msg in msgs]
-  ['Root <root@localhost>', 'Daniel Silverstone <daniel.silverstone@xxxxxxxxxxxxx>']
+    >>> for msg in msgs:
+    ...     print msg['From']
+    Root <root@localhost>
+    Daniel Silverstone <daniel.silverstone@xxxxxxxxxxxxx>
 
-  >>> notification['Subject']
-  '[ubuntu/breezy-autotest] netapplet 0.99.6-1 (New)'
+    >>> print notification['Subject']
+    [ubuntu/breezy-autotest] netapplet 0.99.6-1 (New)
 
 The mail body contains the same list of files again:
 
-  >>> print notification.get_payload(0) # doctest: -NORMALIZE_WHITESPACE
-  From nobody ...
-  ...
-  NEW: netapplet_1.0-1.dsc
-  NEW: netapplet_1.0.orig.tar.gz
-  NEW: netapplet_1.0-1.diff.gz
-  <BLANKLINE>
-  ...
-  You may have gotten the distroseries wrong.  If so, you may get warnings
-  above if files already exist in other distroseries.
-  <BLANKLINE>
-  -- =
-  <BLANKLINE>
-  You are receiving this email because you are the uploader, maintainer or
-  signer of the above package.
-  <BLANKLINE>
-
-
-notify() will also work without passing the changes_file_object parameter
-provided that everything is already committed to the database (which is not
-the case when nascent upload runs).  This example demonstrates this usage:
-
-  >>> from canonical.librarian.testing.server import fillLibrarianFile
-  >>> changes_file = open(changes_file_path,"r")
-  >>> fillLibrarianFile(1, content=changes_file.read())
-  >>> changes_file.close()
-  >>> netapplet_upload.setNew()
-  >>> netapplet_upload.notify(logger=FakeLogger())
-  DEBUG Building recipients list.
-  ...
-  DEBUG Sent a mail:
-  ...
-  DEBUG     Recipients: Foo Bar <foo.bar@xxxxxxxxxxxxx>, Daniel Silverstone <daniel.silverstone@xxxxxxxxxxxxx>
-  ...
-  DEBUG above if files already exist in other distroseries.
-  ...
-  DEBUG signer of the above package.
+    >>> print notification.get_payload(0) # doctest: -NORMALIZE_WHITESPACE
+    From nobody ...
+    ...
+    NEW: netapplet_1.0-1.dsc
+    NEW: netapplet_1.0.orig.tar.gz
+    NEW: netapplet_1.0-1.diff.gz
+    <BLANKLINE>
+    ...
+    You may have gotten the distroseries wrong.  If so, you may get warnings
+    above if files already exist in other distroseries.
+    <BLANKLINE>
+    -- =
+    <BLANKLINE>
+    You are receiving this email because you are the uploader, maintainer or
+    signer of the above package.
+    <BLANKLINE>
+
+notify() will also work without passing the changes_file_object
+parameter provided that everything is already committed to the database
+(which is not the case when nascent upload runs).  This example
+demonstrates this usage:
+
+    >>> from canonical.librarian.testing.server import fillLibrarianFile
+    >>> changes_file = open(changes_file_path,"r")
+    >>> fillLibrarianFile(1, content=changes_file.read())
+    >>> changes_file.close()
+    >>> netapplet_upload.setNew()
+    >>> netapplet_upload.notify(logger=FakeLogger())
+    DEBUG Building recipients list.
+    ...
+    DEBUG Sent a mail:
+    ...
+    DEBUG     Recipients: ... Silverstone ...
+    ...
+    DEBUG above if files already exist in other distroseries.
+    ...
+    DEBUG signer of the above package.
 
 Only one email is generated:
 
-  >>> [notification] = pop_notifications()
+    >>> [notification] = pop_notifications()
 
 The mail headers are the same as before:
 
-  >>> notification['To']
-  'Foo Bar <foo.bar@xxxxxxxxxxxxx>,\n\tDaniel Silverstone <daniel.silverstone@xxxxxxxxxxxxx>'
-  >>> notification['Bcc']
-  'Root <root@localhost>'
-
-  >>> notification['Subject']
-  '[ubuntu/breezy-autotest] netapplet 0.99.6-1 (New)'
+    >>> for addr in extract_addresses(notification['To']):
+    ...     print addr
+    Daniel Silverstone <daniel.silverstone@xxxxxxxxxxxxx>
+    Foo Bar <foo.bar@xxxxxxxxxxxxx>
+
+    >>> print notification['Bcc']
+    Root <root@localhost>
+
+    >>> print notification['Subject']
+    [ubuntu/breezy-autotest] netapplet 0.99.6-1 (New)
 
 The mail body contains the same list of files again:
 
-  >>> print notification.get_payload(0) # doctest: -NORMALIZE_WHITESPACE
-  From nobody ...
-  ...
-  NEW: netapplet_1.0-1.dsc
-  NEW: netapplet_1.0.orig.tar.gz
-  NEW: netapplet_1.0-1.diff.gz
-  <BLANKLINE>
-  ...
-  You may have gotten the distroseries wrong.  If so, you may get warnings
-  above if files already exist in other distroseries.
-  <BLANKLINE>
-  -- =
-  <BLANKLINE>
-  You are receiving this email because you are the uploader, maintainer or
-  signer of the above package.
-  <BLANKLINE>
-
+    >>> print notification.get_payload(0) # doctest: -NORMALIZE_WHITESPACE
+    From nobody ...
+    ...
+    NEW: netapplet_1.0-1.dsc
+    NEW: netapplet_1.0.orig.tar.gz
+    NEW: netapplet_1.0-1.diff.gz
+    <BLANKLINE>
+    ...
+    You may have gotten the distroseries wrong.  If so, you may get warnings
+    above if files already exist in other distroseries.
+    <BLANKLINE>
+    -- =
+    <BLANKLINE>
+    You are receiving this email because you are the uploader, maintainer or
+    signer of the above package.
+    <BLANKLINE>
 
 notify() will also generate rejection notices if the upload failed.  The
-summary_text argument is text that is appended to any auto-generated text for
-the summary.  Rejections don't currently auto-generate anything.
+summary_text argument is text that is appended to any auto-generated
+text for the summary.  Rejections don't currently auto-generate
+anything.
 
-  >>> netapplet_upload.setRejected()
-  >>> netapplet_upload.notify(summary_text="Testing rejection message",
-  ...     logger=FakeLogger())
-  DEBUG Building recipients list.
-  ...
-  DEBUG Sending rejection email.
-  ...
-  DEBUG   Subject: [ubuntu/breezy-autotest] netapplet 0.99.6-1 (Rejected)
-  DEBUG   Sender: Root <root@localhost>
-  DEBUG   Recipients: Foo Bar <foo.bar@xxxxxxxxxxxxx>, Daniel Silverstone <daniel.silverstone@xxxxxxxxxxxxx>
-  DEBUG   Bcc: Root <root@localhost>
-  DEBUG   Body:
-  DEBUG Rejected:
-  DEBUG Testing rejection message
-  ...
-  DEBUG If you don't understand why your files were rejected, or if the
-  ...
-  DEBUG signer of the above package.
+    >>> netapplet_upload.setRejected()
+    >>> netapplet_upload.notify(
+    ...     summary_text="Testing rejection message", logger=FakeLogger())
+    DEBUG Building recipients list.
+    ...
+    DEBUG Sending rejection email.
+    ...
+    DEBUG   Subject: [ubuntu/breezy-autotest] netapplet 0.99.6-1 (Rejected)
+    DEBUG   Sender: Root <root@localhost>
+    DEBUG   Recipients: ... Foo Bar ...
+    DEBUG   Bcc: Root <root@localhost>
+    DEBUG   Body:
+    DEBUG Rejected:
+    DEBUG Testing rejection message
+    ...
+    DEBUG If you don't understand why your files were rejected, or if the
+    ...
+    DEBUG signer of the above package.
 
 Only one email is generated:
 
-  >>> transaction.commit()
-  >>> len(stub.test_emails)
-  1
+    >>> transaction.commit()
+    >>> len(stub.test_emails)
+    1
 
 Clean up, otherwise stuff is left lying around in /var/tmp.
 
-  >>> from canonical.testing.layers import LibrarianLayer
-  >>> LibrarianLayer.librarian_fixture.clear()
+    >>> from canonical.testing.layers import LibrarianLayer
+    >>> LibrarianLayer.librarian_fixture.clear()

=== modified file 'lib/lp/soyuz/model/queue.py'
--- lib/lp/soyuz/model/queue.py	2011-08-25 08:29:37 +0000
+++ lib/lp/soyuz/model/queue.py	2011-10-27 08:29:29 +0000
@@ -868,7 +868,7 @@
             signer, self.sourcepackagerelease, self.builds, self.customfiles,
             self.archive, self.distroseries, self.pocket, summary_text,
             changes, changesfile_content, changes_file_object,
-            status_action[self.status], dry_run, logger)
+            status_action[self.status], dry_run=dry_run, logger=logger)
 
     def _isPersonUploader(self, person):
         """Return True if person is an uploader to the package's distro."""

=== modified file 'lib/lp/soyuz/scripts/packagecopier.py'
--- lib/lp/soyuz/scripts/packagecopier.py	2011-09-26 06:30:07 +0000
+++ lib/lp/soyuz/scripts/packagecopier.py	2011-10-27 08:29:29 +0000
@@ -533,8 +533,8 @@
     Verifies if each copy can be performed using `CopyChecker` and
     raises `CannotCopy` if one or more copies could not be performed.
 
-    When `CannotCopy`is raised call sites are in charge to rollback the
-    transaction or performed copies will be commited.
+    When `CannotCopy` is raised, call sites are responsible for rolling
+    back the transaction.  Otherwise, performed copies will be commited.
 
     Wrapper for `do_direct_copy`.
 
@@ -611,8 +611,7 @@
             # In zopeless mode this email will be sent immediately.
             notify(
                 person, source.sourcepackagerelease, [], [], archive,
-                series, pocket, summary_text=error_text,
-                action='rejected')
+                series, pocket, summary_text=error_text, action='rejected')
         raise CannotCopy(error_text)
 
     overrides_index = 0
@@ -648,8 +647,7 @@
             if send_email:
                 notify(
                     person, source.sourcepackagerelease, [], [], archive,
-                    destination_series, pocket, changes=None,
-                    action='accepted',
+                    destination_series, pocket, action='accepted',
                     announce_from_person=announce_from_person,
                     previous_version=old_version)