← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad/basemailer-refactor into lp:launchpad

 

Colin Watson has proposed merging lp:~cjwatson/launchpad/basemailer-refactor into lp:launchpad.

Commit message:
Refactor BaseMailer a little, making argument order more consistent and adding _getFromAddress and sendOne methods.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/basemailer-refactor/+merge/268858

Refactor BaseMailer a little.  This makes argument order more consistent (no more "recipient, email" in some methods and "email, recipient" in others) and adds _getFromAddress and sendOne methods.  This is in preparation for introducing PackageUploadMailer, for which it will be useful to construct a single mailer that can send some mails with a different From address (-changes announcements) and that can intercept the loop body of sendAll to make the dry-run case work.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/basemailer-refactor into lp:launchpad.
=== modified file 'lib/lp/code/mail/branch.py'
--- lib/lp/code/mail/branch.py	2015-04-16 14:35:17 +0000
+++ lib/lp/code/mail/branch.py	2015-08-23 23:35:32 +0000
@@ -244,8 +244,8 @@
             revision_id=revision_id,
             notification_type='branch-revision', full_subject=subject)
 
-    def _getHeaders(self, email):
-        headers = BaseMailer._getHeaders(self, email)
+    def _getHeaders(self, email, recipient):
+        headers = BaseMailer._getHeaders(self, email, recipient)
         reason, rationale = self._recipients.getReason(email)
         headers['X-Launchpad-Branch'] = reason.branch.unique_name
         if IGitRef.providedBy(reason.branch):

=== modified file 'lib/lp/code/mail/branchmergeproposal.py'
--- lib/lp/code/mail/branchmergeproposal.py	2015-04-16 04:00:33 +0000
+++ lib/lp/code/mail/branchmergeproposal.py	2015-08-23 23:35:32 +0000
@@ -97,7 +97,7 @@
         """Return the address to use for the reply-to header."""
         return self.merge_proposal.address
 
-    def _getToAddresses(self, recipient, email):
+    def _getToAddresses(self, email, recipient):
         """Return the addresses to use for the to header.
 
         If the email is being sent directly to the recipient, their email
@@ -105,7 +105,7 @@
         reviewers are returned.
         """
         if self.direct_email:
-            return BaseMailer._getToAddresses(self, recipient, email)
+            return BaseMailer._getToAddresses(self, email, recipient)
         to_addrs = [self.merge_proposal.address]
         for vote in self.merge_proposal.votes:
             if vote.reviewer == vote.registrant:
@@ -117,9 +117,9 @@
             to_addrs.append(self._format_user_address(vote.reviewer))
         return to_addrs
 
-    def _getHeaders(self, email):
+    def _getHeaders(self, email, recipient):
         """Return the mail headers to use."""
-        headers = BranchMailer._getHeaders(self, email)
+        headers = BranchMailer._getHeaders(self, email, recipient)
         if self.merge_proposal.root_message_id is not None:
             headers['In-Reply-To'] = self.merge_proposal.root_message_id
         return headers

=== modified file 'lib/lp/code/mail/codereviewcomment.py'
--- lib/lp/code/mail/codereviewcomment.py	2015-08-04 09:28:32 +0000
+++ lib/lp/code/mail/codereviewcomment.py	2015-08-23 23:35:32 +0000
@@ -131,15 +131,15 @@
         return ''.join((
             self.body_prefix, append_footer(self.body_main, footer)))
 
-    def _getHeaders(self, email):
+    def _getHeaders(self, email, recipient):
         """Return the mail headers to use."""
-        headers = BMPMailer._getHeaders(self, email)
+        headers = BMPMailer._getHeaders(self, email, recipient)
         headers['Message-Id'] = self.message.rfc822msgid
         if self.message.parent is not None:
             headers['In-Reply-To'] = self.message.parent.rfc822msgid
         return headers
 
-    def _getToAddresses(self, recipient, email):
+    def _getToAddresses(self, email, recipient):
         """Provide to addresses as if this were a mailing list.
 
         CodeReviewComments which are not replies shall list the merge proposer

=== modified file 'lib/lp/code/mail/sourcepackagerecipebuild.py'
--- lib/lp/code/mail/sourcepackagerecipebuild.py	2014-07-09 03:40:14 +0000
+++ lib/lp/code/mail/sourcepackagerecipebuild.py	2015-08-23 23:35:32 +0000
@@ -42,10 +42,10 @@
             notification_type='recipe-build-status')
         self.build = build
 
-    def _getHeaders(self, email):
+    def _getHeaders(self, email, recipient):
         """See `BaseMailer`"""
         headers = super(
-            SourcePackageRecipeBuildMailer, self)._getHeaders(email)
+            SourcePackageRecipeBuildMailer, self)._getHeaders(email, recipient)
         headers.update({
             'X-Launchpad-Build-State': self.build.status.name,
             })
@@ -81,7 +81,7 @@
             params['upload_log_url'] = self.build.upload_log_url
         return params
 
-    def _getFooter(self, params):
+    def _getFooter(self, email, recipient, params):
         """See `BaseMailer`"""
         return ('%(build_url)s\n'
                 '%(reason)s\n' % params)

=== modified file 'lib/lp/code/mail/tests/test_codereviewcomment.py'
--- lib/lp/code/mail/tests/test_codereviewcomment.py	2015-08-04 09:28:32 +0000
+++ lib/lp/code/mail/tests/test_codereviewcomment.py	2015-08-23 23:35:32 +0000
@@ -351,11 +351,10 @@
         """To address for a comment with no parent should be the proposer."""
         comment = self.makeCommentAndParticipants()
         mailer = CodeReviewCommentMailer.forCreation(comment)
-        to = mailer._getToAddresses(
-            comment.message.owner, 'comment@xxxxxxxxx')
+        to = mailer._getToAddresses('comment@xxxxxxxxx', comment.message.owner)
         self.assertEqual(['Proposer <proposer@xxxxxxxxx>'], to)
         to = mailer._getToAddresses(
-            comment.branch_merge_proposal.registrant, 'propose@xxxxxxxxx')
+            'propose@xxxxxxxxx', comment.branch_merge_proposal.registrant)
         self.assertEqual(['Proposer <propose@xxxxxxxxx>'], to)
 
     def test_generateEmail_addresses(self):
@@ -379,10 +378,9 @@
         reply = comment.branch_merge_proposal.createComment(
             second_commenter, 'hello2', parent=comment)
         mailer = CodeReviewCommentMailer.forCreation(reply)
-        to = mailer._getToAddresses(second_commenter, 'comment2@xxxxxxxxx')
+        to = mailer._getToAddresses('comment2@xxxxxxxxx', second_commenter)
         self.assertEqual(['Commenter <commenter@xxxxxxxxx>'], to)
-        to = mailer._getToAddresses(
-            comment.message.owner, 'comment@xxxxxxxxx')
+        to = mailer._getToAddresses('comment@xxxxxxxxx', comment.message.owner)
         self.assertEqual(['Commenter <comment@xxxxxxxxx>'], to)
 
     def test_getToAddresses_with_hidden_address(self):
@@ -394,7 +392,7 @@
         reply = comment.branch_merge_proposal.createComment(
             second_commenter, 'hello2', parent=comment)
         mailer = CodeReviewCommentMailer.forCreation(reply)
-        to = mailer._getToAddresses(second_commenter, 'comment2@xxxxxxxxx')
+        to = mailer._getToAddresses('comment2@xxxxxxxxx', second_commenter)
         self.assertEqual([mailer.merge_proposal.address], to)
 
 

=== modified file 'lib/lp/services/mail/basemailer.py'
--- lib/lp/services/mail/basemailer.py	2015-08-06 12:09:59 +0000
+++ lib/lp/services/mail/basemailer.py	2015-08-23 23:35:32 +0000
@@ -71,7 +71,10 @@
         self._mail_controller_class = mail_controller_class
         self.request = request
 
-    def _getToAddresses(self, recipient, email):
+    def _getFromAddress(self, email, recipient):
+        return self.from_address
+
+    def _getToAddresses(self, email, recipient):
         return [format_address(recipient.displayname, email)]
 
     def generateEmail(self, email, recipient, force_no_attachments=False):
@@ -81,15 +84,16 @@
         :param recipient: The Person to send to.
         :return: (headers, subject, body) of the email.
         """
-        to_addresses = self._getToAddresses(recipient, email)
-        headers = self._getHeaders(email)
+        from_address = self._getFromAddress(email, recipient)
+        to_addresses = self._getToAddresses(email, recipient)
+        headers = self._getHeaders(email, recipient)
         subject = self._getSubject(email, recipient)
         body = self._getBody(email, recipient)
         expanded_footer = self._getExpandedFooter(headers, recipient)
         if expanded_footer:
             body = append_footer(body, expanded_footer)
         ctrl = self._mail_controller_class(
-            self.from_address, to_addresses, subject, body, headers,
+            from_address, to_addresses, subject, body, headers,
             envelope_to=[email])
         if force_no_attachments:
             ctrl.addAttachment(
@@ -108,7 +112,7 @@
         """Return the address to use for the reply-to header."""
         return None
 
-    def _getHeaders(self, email):
+    def _getHeaders(self, email, recipient):
         """Return the mail headers to use."""
         reason, rationale = self._recipients.getReason(email)
         headers = OrderedDict()
@@ -149,12 +153,12 @@
         template = get_email_template(self._template_name, app=self.app)
         params = self._getTemplateParams(email, recipient)
         body = template % params
-        footer = self._getFooter(params)
+        footer = self._getFooter(email, recipient, params)
         if footer is not None:
             body = append_footer(body, footer)
         return body
 
-    def _getFooter(self, params):
+    def _getFooter(self, email, recipient, params):
         """Provide a footer to attach to the body, or None."""
         return None
 
@@ -168,33 +172,35 @@
                 lines.append('%s: %s\n' % (key[2:], value))
         return ''.join(lines)
 
-    def sendAll(self):
-        """Send notifications to all recipients."""
+    def sendOne(self, email, recipient):
+        """Send notification to one recipient."""
         # We never want SMTP errors to propagate from this function.
-        for email, recipient in self._recipients.getRecipientPersons():
-            ctrl = self.generateEmail(email, recipient)
+        ctrl = self.generateEmail(email, recipient)
+        try:
+            ctrl.send()
+        except SMTPException:
+            # If the initial sending failed, try again without
+            # attachments.
+            ctrl = self.generateEmail(
+                email, recipient, force_no_attachments=True)
             try:
                 ctrl.send()
             except SMTPException:
-                # If the initial sending failed, try again without
-                # attachments.
-                ctrl = self.generateEmail(
-                    email, recipient, force_no_attachments=True)
-                try:
-                    ctrl.send()
-                except SMTPException:
-                    error_utility = getUtility(IErrorReportingUtility)
-                    oops_vars = {
-                        "message_id": ctrl.headers.get("Message-Id"),
-                        "notification_type": self.notification_type,
-                        "recipient": ", ".join(ctrl.to_addrs),
-                        "subject": ctrl.subject,
-                        }
-                    with error_utility.oopsMessage(oops_vars):
-                        oops = error_utility.raising(
-                            sys.exc_info(), self.request)
-                    self.logger.info(
-                        "Mail resulted in OOPS: %s" % oops.get("id"))
+                error_utility = getUtility(IErrorReportingUtility)
+                oops_vars = {
+                    "message_id": ctrl.headers.get("Message-Id"),
+                    "notification_type": self.notification_type,
+                    "recipient": ", ".join(ctrl.to_addrs),
+                    "subject": ctrl.subject,
+                    }
+                with error_utility.oopsMessage(oops_vars):
+                    oops = error_utility.raising(sys.exc_info(), self.request)
+                self.logger.info("Mail resulted in OOPS: %s" % oops.get("id"))
+
+    def sendAll(self):
+        """Send notifications to all recipients."""
+        for email, recipient in sorted(self._recipients.getRecipientPersons()):
+            self.sendOne(email, recipient)
 
 
 class RecipientReason:

=== modified file 'lib/lp/services/mail/tests/test_basemailer.py'
--- lib/lp/services/mail/tests/test_basemailer.py	2015-08-06 12:09:59 +0000
+++ lib/lp/services/mail/tests/test_basemailer.py	2015-08-23 23:35:32 +0000
@@ -32,10 +32,17 @@
         return 'body'
 
 
+class FromAddressUpper(BaseMailerSubclass):
+    """Subclass of BaseMailer providing an example getFromAddress."""
+
+    def _getFromAddress(self, email, recipient):
+        return self.from_address.upper()
+
+
 class ToAddressesUpper(BaseMailerSubclass):
     """Subclass of BaseMailer providing an example getToAddresses."""
 
-    def _getToAddresses(self, recipient, email):
+    def _getToAddresses(self, email, recipient):
         return email.upper()
 
 
@@ -95,6 +102,19 @@
         self.assertEqual(['to@xxxxxxxxxxx'], ctrl.envelope_to)
         self.assertEqual(['Example To <to@xxxxxxxxxxx>'], ctrl.to_addrs)
 
+    def test_generateEmail_uses_getFromAddress(self):
+        """BaseMailer.generateEmail uses getFromAddress.
+
+        We verify this by using a subclass that provides getFromAddress
+        returning the uppercased email address.
+        """
+        fake_to = self.factory.makePerson(email='to@xxxxxxxxxxx')
+        recipients = {fake_to: FakeSubscription()}
+        mailer = FromAddressUpper(
+            'subject', None, recipients, 'from@xxxxxxxxxxx')
+        ctrl = mailer.generateEmail('to@xxxxxxxxxxx', fake_to)
+        self.assertEqual('FROM@xxxxxxxxxxx', ctrl.from_addr)
+
     def test_generateEmail_uses_getToAddresses(self):
         """BaseMailer.generateEmail uses getToAddresses.
 

=== modified file 'lib/lp/snappy/mail/snapbuild.py'
--- lib/lp/snappy/mail/snapbuild.py	2015-07-23 16:55:43 +0000
+++ lib/lp/snappy/mail/snapbuild.py	2015-08-23 23:35:32 +0000
@@ -39,9 +39,9 @@
             notification_type="snap-build-status")
         self.build = build
 
-    def _getHeaders(self, email):
+    def _getHeaders(self, email, recipient):
         """See `BaseMailer`."""
-        headers = super(SnapBuildMailer, self)._getHeaders(email)
+        headers = super(SnapBuildMailer, self)._getHeaders(email, recipient)
         headers["X-Launchpad-Build-State"] = self.build.status.name
         return headers
 
@@ -76,7 +76,7 @@
             params["builder_url"] = canonical_url(build.builder)
         return params
 
-    def _getFooter(self, params):
+    def _getFooter(self, email, recipient, params):
         """See `BaseMailer`."""
         return ("%(build_url)s\n"
                 "%(reason)s\n" % params)

=== modified file 'lib/lp/soyuz/mail/binarypackagebuild.py'
--- lib/lp/soyuz/mail/binarypackagebuild.py	2015-07-14 10:57:46 +0000
+++ lib/lp/soyuz/mail/binarypackagebuild.py	2015-08-23 23:35:32 +0000
@@ -150,9 +150,10 @@
         self.build = build
         self.extra_info = extra_info
 
-    def _getHeaders(self, email):
+    def _getHeaders(self, email, recipient):
         """See `BaseMailer`."""
-        headers = super(BinaryPackageBuildMailer, self)._getHeaders(email)
+        headers = super(BinaryPackageBuildMailer, self)._getHeaders(
+            email, recipient)
         build = self.build
         headers.update({
             "X-Launchpad-Archive": build.archive.reference,
@@ -234,7 +235,7 @@
             })
         return params
 
-    def _getFooter(self, params):
+    def _getFooter(self, email, recipient, params):
         """See `BaseMailer`."""
         return ("%(build_title)s\n"
                 "%(build_url)s\n\n"

=== modified file 'lib/lp/soyuz/mail/livefsbuild.py'
--- lib/lp/soyuz/mail/livefsbuild.py	2014-07-09 06:25:19 +0000
+++ lib/lp/soyuz/mail/livefsbuild.py	2015-08-23 23:35:32 +0000
@@ -39,9 +39,9 @@
             notification_type="livefs-build-status")
         self.build = build
 
-    def _getHeaders(self, email):
+    def _getHeaders(self, email, recipient):
         """See `BaseMailer`."""
-        headers = super(LiveFSBuildMailer, self)._getHeaders(email)
+        headers = super(LiveFSBuildMailer, self)._getHeaders(email, recipient)
         headers["X-Launchpad-Build-State"] = self.build.status.name
         return headers
 
@@ -77,7 +77,7 @@
             params["builder_url"] = canonical_url(build.builder)
         return params
 
-    def _getFooter(self, params):
+    def _getFooter(self, email, recipient, params):
         """See `BaseMailer`."""
         return ("%(build_url)s\n"
                 "%(reason)s\n" % params)


Follow ups