Merge ~cjwatson/launchpad:py3only-message-from-bytes into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 5baa1c0d154199d3b8524ce0e291be0ee9cb98bf
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:py3only-message-from-bytes
Merge into: launchpad:master
Diff against target: 1083 lines (+100/-118)
31 files modified
lib/lp/blueprints/doc/specification-notifications.txt (+6/-6)
lib/lp/bugs/doc/externalbugtracker-debbugs.txt (+3/-4)
lib/lp/bugs/doc/initial-bug-contacts.txt (+2/-2)
lib/lp/bugs/externalbugtracker/debbugs.py (+4/-4)
lib/lp/bugs/scripts/debbugs.py (+2/-4)
lib/lp/bugs/tests/bugs-emailinterface.txt (+10/-9)
lib/lp/bugs/utilities/filebugdataparser.py (+2/-2)
lib/lp/code/doc/codeimport.txt (+2/-2)
lib/lp/code/mail/tests/test_codeimport.py (+8/-8)
lib/lp/code/model/tests/test_gitrepository.py (+2/-2)
lib/lp/codehosting/scanner/tests/test_email.py (+5/-5)
lib/lp/registry/browser/tests/test_person.py (+2/-2)
lib/lp/registry/doc/distribution-mirror.txt (+2/-2)
lib/lp/registry/stories/gpg-coc/xx-gpg-coc.txt (+5/-5)
lib/lp/registry/stories/mailinglists/hosted-email-address.txt (+2/-2)
lib/lp/registry/stories/person/xx-add-sshkey.txt (+2/-2)
lib/lp/registry/tests/test_mailinglistapi.py (+4/-7)
lib/lp/services/compat.py (+0/-6)
lib/lp/services/mail/doc/emailauthentication.txt (+3/-6)
lib/lp/services/mail/doc/mailbox.txt (+3/-3)
lib/lp/services/mail/doc/sending-mail.txt (+13/-13)
lib/lp/services/mail/notification.py (+2/-2)
lib/lp/services/mail/signedmessage.py (+2/-2)
lib/lp/services/mail/stub.py (+3/-5)
lib/lp/services/mail/tests/incomingmail.txt (+1/-2)
lib/lp/services/mail/tests/test_stub.py (+1/-1)
lib/lp/services/messages/model/message.py (+2/-2)
lib/lp/services/verification/doc/logintoken.txt (+1/-1)
lib/lp/services/verification/tests/logintoken.py (+2/-3)
lib/lp/testing/mail_helpers.py (+2/-2)
lib/lp/translations/doc/translations-export-to-branch.txt (+2/-2)
Reviewer Review Type Date Requested Status
Cristian Gonzalez (community) Approve
Review via email: mp+408168@code.launchpad.net

Commit message

Use email.message_from_bytes directly

To post a comment you must log in.
Revision history for this message
Cristian Gonzalez (cristiangsp) wrote :

Looks good!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/lib/lp/blueprints/doc/specification-notifications.txt b/lib/lp/blueprints/doc/specification-notifications.txt
index 7eb04ab..63130a4 100644
--- a/lib/lp/blueprints/doc/specification-notifications.txt
+++ b/lib/lp/blueprints/doc/specification-notifications.txt
@@ -116,9 +116,9 @@ are now subscribed.
116116
117Now let's take a look at what the notification looks like:117Now let's take a look at what the notification looks like:
118118
119 >>> from lp.services.compat import message_from_bytes119 >>> import email
120 >>> notifications = [120 >>> notifications = [
121 ... message_from_bytes(raw_message)121 ... email.message_from_bytes(raw_message)
122 ... for from_addr, to_addrs, raw_message in sorted(stub.test_emails)]122 ... for from_addr, to_addrs, raw_message in sorted(stub.test_emails)]
123 >>> status_notification = notifications[0]123 >>> status_notification = notifications[0]
124 >>> status_notification['To']124 >>> status_notification['To']
@@ -158,7 +158,7 @@ Whiteboard change:
158 >>> transaction.commit()158 >>> transaction.commit()
159159
160 >>> notifications = [160 >>> notifications = [
161 ... message_from_bytes(raw_message)161 ... email.message_from_bytes(raw_message)
162 ... for from_addr, to_addrs, raw_message in sorted(stub.test_emails)]162 ... for from_addr, to_addrs, raw_message in sorted(stub.test_emails)]
163 >>> status_notification = notifications[0]163 >>> status_notification = notifications[0]
164 >>> status_notification['To']164 >>> status_notification['To']
@@ -198,7 +198,7 @@ Definition status and whiteboard change:
198 >>> transaction.commit()198 >>> transaction.commit()
199199
200 >>> notifications = [200 >>> notifications = [
201 ... message_from_bytes(raw_message)201 ... email.message_from_bytes(raw_message)
202 ... for from_addr, to_addrs, raw_message in sorted(stub.test_emails)]202 ... for from_addr, to_addrs, raw_message in sorted(stub.test_emails)]
203 >>> status_notification = notifications[0]203 >>> status_notification = notifications[0]
204 >>> status_notification['To']204 >>> status_notification['To']
@@ -238,7 +238,7 @@ Change priority:
238 >>> transaction.commit()238 >>> transaction.commit()
239239
240 >>> notifications = [240 >>> notifications = [
241 ... message_from_bytes(raw_message)241 ... email.message_from_bytes(raw_message)
242 ... for from_addr, to_addrs, raw_message in sorted(stub.test_emails)]242 ... for from_addr, to_addrs, raw_message in sorted(stub.test_emails)]
243 >>> status_notification = notifications[0]243 >>> status_notification = notifications[0]
244 >>> status_notification['To']244 >>> status_notification['To']
@@ -272,7 +272,7 @@ Change approver, assignee and drafter:
272 >>> transaction.commit()272 >>> transaction.commit()
273273
274 >>> notifications = [274 >>> notifications = [
275 ... message_from_bytes(raw_message)275 ... email.message_from_bytes(raw_message)
276 ... for from_addr, to_addrs, raw_message in sorted(stub.test_emails)]276 ... for from_addr, to_addrs, raw_message in sorted(stub.test_emails)]
277 >>> status_notification = notifications[0]277 >>> status_notification = notifications[0]
278 >>> status_notification['To']278 >>> status_notification['To']
diff --git a/lib/lp/bugs/doc/externalbugtracker-debbugs.txt b/lib/lp/bugs/doc/externalbugtracker-debbugs.txt
index 9dce3b5..aec439d 100644
--- a/lib/lp/bugs/doc/externalbugtracker-debbugs.txt
+++ b/lib/lp/bugs/doc/externalbugtracker-debbugs.txt
@@ -416,10 +416,9 @@ same comment.
416If we query the DebBugs database directly we'll see that there are two416If we query the DebBugs database directly we'll see that there are two
417copies of the same comment.417copies of the same comment.
418418
419 >>> from lp.services.compat import message_from_bytes
420 >>> debian_bug = external_debbugs._findBug(bug_watch.remotebug)419 >>> debian_bug = external_debbugs._findBug(bug_watch.remotebug)
421 >>> for comment in debian_bug.comments:420 >>> for comment in debian_bug.comments:
422 ... comment_email = message_from_bytes(comment)421 ... comment_email = email.message_from_bytes(comment)
423 ... print(comment_email['message-id'])422 ... print(comment_email['message-id'])
424 <20040309081430.98BF411EE67@tux>423 <20040309081430.98BF411EE67@tux>
425 <20040309081430.98BF411EE67@tux>424 <20040309081430.98BF411EE67@tux>
@@ -493,7 +492,7 @@ datecreated comes not from the Date header but from the Received header.
493492
494 >>> from lp.bugs.tests.externalbugtracker import (493 >>> from lp.bugs.tests.externalbugtracker import (
495 ... read_test_file)494 ... read_test_file)
496 >>> parsed_message = message_from_bytes(495 >>> parsed_message = email.message_from_bytes(
497 ... read_test_file('debbugs-comment-with-received-date.txt').encode(496 ... read_test_file('debbugs-comment-with-received-date.txt').encode(
498 ... 'UTF-8'))497 ... 'UTF-8'))
499498
@@ -519,7 +518,7 @@ default to using the Date header again.
519 >>> print(message.datecreated)518 >>> print(message.datecreated)
520 2007-12-14 18:54:30+00:00519 2007-12-14 18:54:30+00:00
521520
522 >>> parsed_message = message_from_bytes(521 >>> parsed_message = email.message_from_bytes(
523 ... read_test_file('debbugs-comment-with-received-date.txt').encode(522 ... read_test_file('debbugs-comment-with-received-date.txt').encode(
524 ... 'UTF-8'))523 ... 'UTF-8'))
525524
diff --git a/lib/lp/bugs/doc/initial-bug-contacts.txt b/lib/lp/bugs/doc/initial-bug-contacts.txt
index 46e8ed2..7161f17 100644
--- a/lib/lp/bugs/doc/initial-bug-contacts.txt
+++ b/lib/lp/bugs/doc/initial-bug-contacts.txt
@@ -167,8 +167,8 @@ notification). The email has the X-Launchpad-Message-Rationale header to
167track why daf received the email. The rational is repeated in the footer167track why daf received the email. The rational is repeated in the footer
168of the email with the bug title and URL.168of the email with the bug title and URL.
169169
170 >>> import email
170 >>> from operator import itemgetter171 >>> from operator import itemgetter
171 >>> from lp.services.compat import message_from_bytes
172172
173 >>> test_emails = list(stub.test_emails)173 >>> test_emails = list(stub.test_emails)
174 >>> test_emails.sort(key=itemgetter(1))174 >>> test_emails.sort(key=itemgetter(1))
@@ -183,7 +183,7 @@ of the email with the bug title and URL.
183 >>> print(to_addr)183 >>> print(to_addr)
184 ['daf@canonical.com']184 ['daf@canonical.com']
185185
186 >>> msg = message_from_bytes(raw_message)186 >>> msg = email.message_from_bytes(raw_message)
187 >>> msg['References'] == (187 >>> msg['References'] == (
188 ... bug_one_in_ubuntu_firefox.bug.initial_message.rfc822msgid)188 ... bug_one_in_ubuntu_firefox.bug.initial_message.rfc822msgid)
189 True189 True
diff --git a/lib/lp/bugs/externalbugtracker/debbugs.py b/lib/lp/bugs/externalbugtracker/debbugs.py
index 360c2b5..1bca0c7 100644
--- a/lib/lp/bugs/externalbugtracker/debbugs.py
+++ b/lib/lp/bugs/externalbugtracker/debbugs.py
@@ -10,6 +10,7 @@ __all__ = [
10 ]10 ]
1111
12from datetime import datetime12from datetime import datetime
13import email
13from email.utils import (14from email.utils import (
14 mktime_tz,15 mktime_tz,
15 parseaddr,16 parseaddr,
@@ -41,7 +42,6 @@ from lp.bugs.interfaces.externalbugtracker import (
41 UNKNOWN_REMOTE_IMPORTANCE,42 UNKNOWN_REMOTE_IMPORTANCE,
42 )43 )
43from lp.bugs.scripts import debbugs44from lp.bugs.scripts import debbugs
44from lp.services.compat import message_from_bytes
45from lp.services.config import config45from lp.services.config import config
46from lp.services.database.isolation import ensure_no_transaction46from lp.services.database.isolation import ensure_no_transaction
47from lp.services.mail.sendmail import simple_sendmail47from lp.services.mail.sendmail import simple_sendmail
@@ -242,7 +242,7 @@ class DebBugs(ExternalBugTracker):
242242
243 comment_ids = []243 comment_ids = []
244 for comment in debian_bug.comments:244 for comment in debian_bug.comments:
245 parsed_comment = message_from_bytes(comment)245 parsed_comment = email.message_from_bytes(comment)
246246
247 # It's possible for the same message to appear several times247 # It's possible for the same message to appear several times
248 # in a DebBugs comment log, since each control command in a248 # in a DebBugs comment log, since each control command in a
@@ -271,7 +271,7 @@ class DebBugs(ExternalBugTracker):
271 self._loadLog(debian_bug)271 self._loadLog(debian_bug)
272272
273 for comment in debian_bug.comments:273 for comment in debian_bug.comments:
274 parsed_comment = message_from_bytes(comment)274 parsed_comment = email.message_from_bytes(comment)
275 if parsed_comment['message-id'] == comment_id:275 if parsed_comment['message-id'] == comment_id:
276 return parseaddr(parsed_comment['from'])276 return parseaddr(parsed_comment['from'])
277277
@@ -324,7 +324,7 @@ class DebBugs(ExternalBugTracker):
324 self._loadLog(debian_bug)324 self._loadLog(debian_bug)
325325
326 for comment in debian_bug.comments:326 for comment in debian_bug.comments:
327 parsed_comment = message_from_bytes(comment)327 parsed_comment = email.message_from_bytes(comment)
328 if parsed_comment['message-id'] == comment_id:328 if parsed_comment['message-id'] == comment_id:
329 msg_date = self._getDateForComment(parsed_comment)329 msg_date = self._getDateForComment(parsed_comment)
330 message = getUtility(IMessageSet).fromEmail(comment, poster,330 message = getUtility(IMessageSet).fromEmail(comment, poster,
diff --git a/lib/lp/bugs/scripts/debbugs.py b/lib/lp/bugs/scripts/debbugs.py
index 81055c1..d693df1 100644
--- a/lib/lp/bugs/scripts/debbugs.py
+++ b/lib/lp/bugs/scripts/debbugs.py
@@ -11,8 +11,6 @@ import sys
1111
12import six12import six
1313
14from lp.services.compat import message_from_bytes
15
1614
17class Bug:15class Bug:
18 def __init__(self, db, id, package=None, date=None, status=None,16 def __init__(self, db, id, package=None, date=None, status=None,
@@ -59,7 +57,7 @@ class Bug:
59 if self._emails:57 if self._emails:
60 return self._emails58 return self._emails
61 for comment in self.comments:59 for comment in self.comments:
62 message = message_from_bytes(comment)60 message = email.message_from_bytes(comment)
63 self._emails.append(message)61 self._emails.append(message)
64 return self._emails62 return self._emails
6563
@@ -207,7 +205,7 @@ class Database:
207 bug.report = fd.read()205 bug.report = fd.read()
208 fd.close()206 fd.close()
209207
210 report_msg = message_from_bytes(bug.report)208 report_msg = email.message_from_bytes(bug.report)
211 charset = report_msg.get_content_charset('ascii')209 charset = report_msg.get_content_charset('ascii')
212 description = report_msg.get_payload(decode=True)210 description = report_msg.get_payload(decode=True)
213 bug.description = description.decode(charset)211 bug.description = description.decode(charset)
diff --git a/lib/lp/bugs/tests/bugs-emailinterface.txt b/lib/lp/bugs/tests/bugs-emailinterface.txt
index 452b6bf..94d9412 100644
--- a/lib/lp/bugs/tests/bugs-emailinterface.txt
+++ b/lib/lp/bugs/tests/bugs-emailinterface.txt
@@ -70,11 +70,11 @@ we'll have to authenticate the user manually:
70Now if we pass the message to the Malone handler, we can see that the70Now if we pass the message to the Malone handler, we can see that the
71bug got submitted correctly:71bug got submitted correctly:
7272
73 >>> import email
73 >>> from lp.bugs.mail.handler import MaloneHandler74 >>> from lp.bugs.mail.handler import MaloneHandler
74 >>> from lp.services.compat import message_from_bytes
75 >>> handler = MaloneHandler()75 >>> handler = MaloneHandler()
76 >>> def construct_email(raw_mail):76 >>> def construct_email(raw_mail):
77 ... msg = message_from_bytes(raw_mail, _class=MockSignedMessage)77 ... msg = email.message_from_bytes(raw_mail, _class=MockSignedMessage)
78 ... if 'Message-Id' not in msg:78 ... if 'Message-Id' not in msg:
79 ... msg['Message-Id'] = factory.makeUniqueRFC822MsgId()79 ... msg['Message-Id'] = factory.makeUniqueRFC822MsgId()
80 ... return msg80 ... return msg
@@ -356,7 +356,7 @@ And the person sending the email has received an error message.
356 ... if not stub.test_emails:356 ... if not stub.test_emails:
357 ... raise AssertionError("No emails queued!")357 ... raise AssertionError("No emails queued!")
358 ... from_addr, to_addrs, raw_message = stub.test_emails[-1]358 ... from_addr, to_addrs, raw_message = stub.test_emails[-1]
359 ... sent_msg = message_from_bytes(raw_message)359 ... sent_msg = email.message_from_bytes(raw_message)
360 ... error_mail, original_mail = sent_msg.get_payload()360 ... error_mail, original_mail = sent_msg.get_payload()
361 ... print("Subject: %s" % sent_msg['Subject'])361 ... print("Subject: %s" % sent_msg['Subject'])
362 ... print("To: %s" % ', '.join(to_addrs))362 ... print("To: %s" % ', '.join(to_addrs))
@@ -456,7 +456,8 @@ The same will happen if we send the same email without signing it:
456 >>> class MockUnsignedMessage(email.message.Message):456 >>> class MockUnsignedMessage(email.message.Message):
457 ... signedMessage = None457 ... signedMessage = None
458 ... signature = None458 ... signature = None
459 >>> msg = message_from_bytes(comment_mail, _class=MockUnsignedMessage)459 >>> msg = email.message_from_bytes(
460 ... comment_mail, _class=MockUnsignedMessage)
460 >>> handler.process(461 >>> handler.process(
461 ... msg, msg['To'],462 ... msg, msg['To'],
462 ... )463 ... )
@@ -2040,7 +2041,7 @@ Let's take a closer look at send_process_error_notification(), which is
2040used to send the error messages. It needs the message that caused the2041used to send the error messages. It needs the message that caused the
2041error, so let's create one.2042error, so let's create one.
20422043
2043 >>> test_msg = message_from_bytes(b"""From: foo.bar@canonical.com2044 >>> test_msg = email.message_from_bytes(b"""From: foo.bar@canonical.com
2044 ... To: bugs@launchpad.net2045 ... To: bugs@launchpad.net
2045 ... Message-Id: <original@msg>2046 ... Message-Id: <original@msg>
2046 ... Subject: Original Message Subject2047 ... Subject: Original Message Subject
@@ -2063,7 +2064,7 @@ The To and Subject headers got set to the values we provided:
20632064
2064 >>> transaction.commit()2065 >>> transaction.commit()
2065 >>> from_addr, to_addrs, raw_message = stub.test_emails[-1]2066 >>> from_addr, to_addrs, raw_message = stub.test_emails[-1]
2066 >>> sent_msg = message_from_bytes(raw_message)2067 >>> sent_msg = email.message_from_bytes(raw_message)
2067 >>> sent_msg['To']2068 >>> sent_msg['To']
2068 'test@canonical.com'2069 'test@canonical.com'
2069 >>> sent_msg['Subject']2070 >>> sent_msg['Subject']
@@ -2123,7 +2124,7 @@ the original message.
2123 ... max_return_size=max_return_size)2124 ... max_return_size=max_return_size)
2124 >>> transaction.commit()2125 >>> transaction.commit()
2125 >>> from_addr, to_addrs, raw_message = stub.test_emails[-1]2126 >>> from_addr, to_addrs, raw_message = stub.test_emails[-1]
2126 >>> sent_msg = message_from_bytes(raw_message)2127 >>> sent_msg = email.message_from_bytes(raw_message)
2127 >>> failure_msg, original_msg = sent_msg.get_payload()2128 >>> failure_msg, original_msg = sent_msg.get_payload()
2128 >>> msg = original_msg.get_payload()[0]2129 >>> msg = original_msg.get_payload()[0]
21292130
@@ -2220,7 +2221,7 @@ The 'subscribe' command failed, and the user is being notified of the
2220failure in an email.2221failure in an email.
22212222
2222 >>> from_addr, to_addrs, raw_message = stub.test_emails[-1]2223 >>> from_addr, to_addrs, raw_message = stub.test_emails[-1]
2223 >>> sent_msg = message_from_bytes(raw_message)2224 >>> sent_msg = email.message_from_bytes(raw_message)
2224 >>> failure_msg, original_msg = sent_msg.get_payload()2225 >>> failure_msg, original_msg = sent_msg.get_payload()
2225 >>> print(failure_msg.get_payload(decode=True).decode('UTF-8'))2226 >>> print(failure_msg.get_payload(decode=True).decode('UTF-8'))
2226 An error occurred while processing a mail you sent to Launchpad's email2227 An error occurred while processing a mail you sent to Launchpad's email
@@ -2268,7 +2269,7 @@ And the sender receives an email to let them know about the failing
2268'security' command.2269'security' command.
22692270
2270 >>> from_addr, to_addrs, raw_message = stub.test_emails[-1]2271 >>> from_addr, to_addrs, raw_message = stub.test_emails[-1]
2271 >>> sent_msg = message_from_bytes(raw_message)2272 >>> sent_msg = email.message_from_bytes(raw_message)
2272 >>> failure_msg, original_msg = sent_msg.get_payload()2273 >>> failure_msg, original_msg = sent_msg.get_payload()
2273 >>> print(failure_msg.get_payload(decode=True).decode('UTF-8'))2274 >>> print(failure_msg.get_payload(decode=True).decode('UTF-8'))
2274 An error occurred while processing a mail you sent to Launchpad's email2275 An error occurred while processing a mail you sent to Launchpad's email
diff --git a/lib/lp/bugs/utilities/filebugdataparser.py b/lib/lp/bugs/utilities/filebugdataparser.py
index 3b320be..4ffc0a1 100644
--- a/lib/lp/bugs/utilities/filebugdataparser.py
+++ b/lib/lp/bugs/utilities/filebugdataparser.py
@@ -9,12 +9,12 @@ __all__ = [
9 ]9 ]
1010
11import base6411import base64
12import email
12import tempfile13import tempfile
1314
14import six15import six
1516
16from lp.bugs.model.bug import FileBugData17from lp.bugs.model.bug import FileBugData
17from lp.services.compat import message_from_bytes
1818
1919
20class FileBugDataParser:20class FileBugDataParser:
@@ -62,7 +62,7 @@ class FileBugDataParser:
62 header_text = self._consumeBytes(b'\n\n')62 header_text = self._consumeBytes(b'\n\n')
63 # Use the email package to return a dict-like object of the63 # Use the email package to return a dict-like object of the
64 # headers, so we don't have to parse the text ourselves.64 # headers, so we don't have to parse the text ourselves.
65 return message_from_bytes(header_text)65 return email.message_from_bytes(header_text)
6666
67 def readLine(self):67 def readLine(self):
68 """Read a line of the message."""68 """Read a line of the message."""
diff --git a/lib/lp/code/doc/codeimport.txt b/lib/lp/code/doc/codeimport.txt
index 3856ed3..d5da426 100644
--- a/lib/lp/code/doc/codeimport.txt
+++ b/lib/lp/code/doc/codeimport.txt
@@ -103,8 +103,8 @@ three members of the vcs-imports team.
103 >>> vcs_imports = getUtility(ILaunchpadCelebrities).vcs_imports103 >>> vcs_imports = getUtility(ILaunchpadCelebrities).vcs_imports
104 >>> len(get_contact_email_addresses(vcs_imports))104 >>> len(get_contact_email_addresses(vcs_imports))
105 3105 3
106 >>> from lp.services.compat import message_from_bytes106 >>> import email
107 >>> message = message_from_bytes(stub.test_emails[0][2])107 >>> message = email.message_from_bytes(stub.test_emails[0][2])
108 >>> print(message['subject'])108 >>> print(message['subject'])
109 New code import: ~import-person/widget/trunk-cvs109 New code import: ~import-person/widget/trunk-cvs
110 >>> print(message['X-Launchpad-Message-Rationale'])110 >>> print(message['X-Launchpad-Message-Rationale'])
diff --git a/lib/lp/code/mail/tests/test_codeimport.py b/lib/lp/code/mail/tests/test_codeimport.py
index 18ecb59..ee62761 100644
--- a/lib/lp/code/mail/tests/test_codeimport.py
+++ b/lib/lp/code/mail/tests/test_codeimport.py
@@ -3,6 +3,7 @@
33
4"""Tests for code import related mailings"""4"""Tests for code import related mailings"""
55
6import email
6import textwrap7import textwrap
78
8import six9import six
@@ -14,7 +15,6 @@ from lp.code.enums import (
14 TargetRevisionControlSystems,15 TargetRevisionControlSystems,
15 )16 )
16from lp.code.tests.helpers import GitHostingFixture17from lp.code.tests.helpers import GitHostingFixture
17from lp.services.compat import message_from_bytes
18from lp.services.mail import stub18from lp.services.mail import stub
19from lp.testing import (19from lp.testing import (
20 login_celebrity,20 login_celebrity,
@@ -40,7 +40,7 @@ class TestNewCodeImports(TestCaseWithFactory):
40 cvs_module='a_module', branch_name='import',40 cvs_module='a_module', branch_name='import',
41 product=fooix, registrant=eric)41 product=fooix, registrant=eric)
42 transaction.commit()42 transaction.commit()
43 msg = message_from_bytes(stub.test_emails[0][2])43 msg = email.message_from_bytes(stub.test_emails[0][2])
44 self.assertEqual('code-import', msg['X-Launchpad-Notification-Type'])44 self.assertEqual('code-import', msg['X-Launchpad-Notification-Type'])
45 self.assertEqual('~eric/fooix/import', msg['X-Launchpad-Branch'])45 self.assertEqual('~eric/fooix/import', msg['X-Launchpad-Branch'])
46 self.assertEqual(46 self.assertEqual(
@@ -64,7 +64,7 @@ class TestNewCodeImports(TestCaseWithFactory):
64 branch_name='trunk', product=fooix, registrant=eric,64 branch_name='trunk', product=fooix, registrant=eric,
65 rcs_type=RevisionControlSystems.BZR_SVN)65 rcs_type=RevisionControlSystems.BZR_SVN)
66 transaction.commit()66 transaction.commit()
67 msg = message_from_bytes(stub.test_emails[0][2])67 msg = email.message_from_bytes(stub.test_emails[0][2])
68 self.assertEqual('code-import', msg['X-Launchpad-Notification-Type'])68 self.assertEqual('code-import', msg['X-Launchpad-Notification-Type'])
69 self.assertEqual('~eric/fooix/trunk', msg['X-Launchpad-Branch'])69 self.assertEqual('~eric/fooix/trunk', msg['X-Launchpad-Branch'])
70 self.assertEqual(70 self.assertEqual(
@@ -87,7 +87,7 @@ class TestNewCodeImports(TestCaseWithFactory):
87 git_repo_url='git://git.example.com/fooix.git',87 git_repo_url='git://git.example.com/fooix.git',
88 branch_name='master', product=fooix, registrant=eric)88 branch_name='master', product=fooix, registrant=eric)
89 transaction.commit()89 transaction.commit()
90 msg = message_from_bytes(stub.test_emails[0][2])90 msg = email.message_from_bytes(stub.test_emails[0][2])
91 self.assertEqual('code-import', msg['X-Launchpad-Notification-Type'])91 self.assertEqual('code-import', msg['X-Launchpad-Notification-Type'])
92 self.assertEqual('~eric/fooix/master', msg['X-Launchpad-Branch'])92 self.assertEqual('~eric/fooix/master', msg['X-Launchpad-Branch'])
93 self.assertEqual(93 self.assertEqual(
@@ -113,7 +113,7 @@ class TestNewCodeImports(TestCaseWithFactory):
113 branch_name=u'master', product=fooix, registrant=eric,113 branch_name=u'master', product=fooix, registrant=eric,
114 target_rcs_type=TargetRevisionControlSystems.GIT)114 target_rcs_type=TargetRevisionControlSystems.GIT)
115 transaction.commit()115 transaction.commit()
116 msg = message_from_bytes(stub.test_emails[0][2])116 msg = email.message_from_bytes(stub.test_emails[0][2])
117 self.assertEqual('code-import', msg['X-Launchpad-Notification-Type'])117 self.assertEqual('code-import', msg['X-Launchpad-Notification-Type'])
118 self.assertEqual('~eric/fooix/+git/master', msg['X-Launchpad-Branch'])118 self.assertEqual('~eric/fooix/+git/master', msg['X-Launchpad-Branch'])
119 self.assertEqual(119 self.assertEqual(
@@ -141,7 +141,7 @@ class TestNewCodeImports(TestCaseWithFactory):
141 git_repo_url='git://git.example.com/fooix.git',141 git_repo_url='git://git.example.com/fooix.git',
142 branch_name='master', sourcepackage=fooix, registrant=eric)142 branch_name='master', sourcepackage=fooix, registrant=eric)
143 transaction.commit()143 transaction.commit()
144 msg = message_from_bytes(stub.test_emails[0][2])144 msg = email.message_from_bytes(stub.test_emails[0][2])
145 self.assertEqual('code-import', msg['X-Launchpad-Notification-Type'])145 self.assertEqual('code-import', msg['X-Launchpad-Notification-Type'])
146 self.assertEqual(146 self.assertEqual(
147 '~eric/foobuntu/manic/fooix/master', msg['X-Launchpad-Branch'])147 '~eric/foobuntu/manic/fooix/master', msg['X-Launchpad-Branch'])
@@ -164,7 +164,7 @@ class TestUpdatedCodeImports(TestCaseWithFactory):
164 layer = DatabaseFunctionalLayer164 layer = DatabaseFunctionalLayer
165165
166 def assertSameDetailsEmail(self, details, unique_name):166 def assertSameDetailsEmail(self, details, unique_name):
167 msg = message_from_bytes(stub.test_emails[0][2])167 msg = email.message_from_bytes(stub.test_emails[0][2])
168 self.assertEqual(168 self.assertEqual(
169 'code-import-updated', msg['X-Launchpad-Notification-Type'])169 'code-import-updated', msg['X-Launchpad-Notification-Type'])
170 self.assertEqual(unique_name, msg['X-Launchpad-Branch'])170 self.assertEqual(unique_name, msg['X-Launchpad-Branch'])
@@ -183,7 +183,7 @@ class TestUpdatedCodeImports(TestCaseWithFactory):
183183
184 def assertDifferentDetailsEmail(self, old_details, new_details,184 def assertDifferentDetailsEmail(self, old_details, new_details,
185 unique_name):185 unique_name):
186 msg = message_from_bytes(stub.test_emails[0][2])186 msg = email.message_from_bytes(stub.test_emails[0][2])
187 self.assertEqual(187 self.assertEqual(
188 'code-import-updated', msg['X-Launchpad-Notification-Type'])188 'code-import-updated', msg['X-Launchpad-Notification-Type'])
189 self.assertEqual(unique_name, msg['X-Launchpad-Branch'])189 self.assertEqual(unique_name, msg['X-Launchpad-Branch'])
diff --git a/lib/lp/code/model/tests/test_gitrepository.py b/lib/lp/code/model/tests/test_gitrepository.py
index de296a7..6edeb1e 100644
--- a/lib/lp/code/model/tests/test_gitrepository.py
+++ b/lib/lp/code/model/tests/test_gitrepository.py
@@ -13,6 +13,7 @@ from datetime import (
13 datetime,13 datetime,
14 timedelta,14 timedelta,
15 )15 )
16import email
16from functools import partial17from functools import partial
17import hashlib18import hashlib
18import json19import json
@@ -145,7 +146,6 @@ from lp.registry.interfaces.personociproject import IPersonOCIProjectFactory
145from lp.registry.interfaces.personproduct import IPersonProductFactory146from lp.registry.interfaces.personproduct import IPersonProductFactory
146from lp.registry.tests.test_accesspolicy import get_policies_for_artifact147from lp.registry.tests.test_accesspolicy import get_policies_for_artifact
147from lp.services.authserver.xmlrpc import AuthServerAPIView148from lp.services.authserver.xmlrpc import AuthServerAPIView
148from lp.services.compat import message_from_bytes
149from lp.services.config import config149from lp.services.config import config
150from lp.services.database.constants import UTC_NOW150from lp.services.database.constants import UTC_NOW
151from lp.services.database.interfaces import IStore151from lp.services.database.interfaces import IStore
@@ -1326,7 +1326,7 @@ class TestGitRepositoryModificationNotifications(TestCaseWithFactory):
1326 getUtility(IGitRepositoryModifiedMailJobSource)).runAll()1326 getUtility(IGitRepositoryModifiedMailJobSource)).runAll()
1327 bodies_by_recipient = {}1327 bodies_by_recipient = {}
1328 for from_addr, to_addrs, message in stub.test_emails:1328 for from_addr, to_addrs, message in stub.test_emails:
1329 body = message_from_bytes(message).get_payload(decode=True)1329 body = email.message_from_bytes(message).get_payload(decode=True)
1330 for to_addr in to_addrs:1330 for to_addr in to_addrs:
1331 bodies_by_recipient[to_addr] = six.ensure_text(body)1331 bodies_by_recipient[to_addr] = six.ensure_text(body)
1332 # Both the owner and the unprivileged subscriber receive email.1332 # Both the owner and the unprivileged subscriber receive email.
diff --git a/lib/lp/codehosting/scanner/tests/test_email.py b/lib/lp/codehosting/scanner/tests/test_email.py
index 3072ec2..34af406 100644
--- a/lib/lp/codehosting/scanner/tests/test_email.py
+++ b/lib/lp/codehosting/scanner/tests/test_email.py
@@ -5,6 +5,7 @@
55
6__metaclass__ = type6__metaclass__ = type
77
8import email
8import os9import os
910
10from breezy.uncommit import uncommit11from breezy.uncommit import uncommit
@@ -25,7 +26,6 @@ from lp.codehosting.scanner import events
25from lp.codehosting.scanner.bzrsync import BzrSync26from lp.codehosting.scanner.bzrsync import BzrSync
26from lp.codehosting.scanner.tests.test_bzrsync import BzrSyncTestCase27from lp.codehosting.scanner.tests.test_bzrsync import BzrSyncTestCase
27from lp.registry.interfaces.person import IPersonSet28from lp.registry.interfaces.person import IPersonSet
28from lp.services.compat import message_from_bytes
29from lp.services.config import config29from lp.services.config import config
30from lp.services.features.testing import FeatureFixture30from lp.services.features.testing import FeatureFixture
31from lp.services.job.runner import JobRunner31from lp.services.job.runner import JobRunner
@@ -72,7 +72,7 @@ class TestBzrSyncEmail(BzrSyncTestCase):
72 self.assertEqual(len(stub.test_emails), 1)72 self.assertEqual(len(stub.test_emails), 1)
73 [initial_email] = stub.test_emails73 [initial_email] = stub.test_emails
74 expected = 'First scan of the branch detected 0 revisions'74 expected = 'First scan of the branch detected 0 revisions'
75 message = message_from_bytes(initial_email[2])75 message = email.message_from_bytes(initial_email[2])
76 email_body = message.get_payload()76 email_body = message.get_payload()
77 self.assertIn(expected, email_body)77 self.assertIn(expected, email_body)
78 self.assertEmailHeadersEqual(78 self.assertEmailHeadersEqual(
@@ -87,7 +87,7 @@ class TestBzrSyncEmail(BzrSyncTestCase):
87 [initial_email] = stub.test_emails87 [initial_email] = stub.test_emails
88 expected = ('First scan of the branch detected 1 revision'88 expected = ('First scan of the branch detected 1 revision'
89 ' in the revision history of the=\n branch.')89 ' in the revision history of the=\n branch.')
90 message = message_from_bytes(initial_email[2])90 message = email.message_from_bytes(initial_email[2])
91 email_body = message.get_payload()91 email_body = message.get_payload()
92 self.assertIn(expected, email_body)92 self.assertIn(expected, email_body)
93 self.assertEmailHeadersEqual(93 self.assertEmailHeadersEqual(
@@ -105,7 +105,7 @@ class TestBzrSyncEmail(BzrSyncTestCase):
105 self.assertEqual(len(stub.test_emails), 1)105 self.assertEqual(len(stub.test_emails), 1)
106 [uncommit_email] = stub.test_emails106 [uncommit_email] = stub.test_emails
107 expected = '1 revision was removed from the branch.'107 expected = '1 revision was removed from the branch.'
108 message = message_from_bytes(uncommit_email[2])108 message = email.message_from_bytes(uncommit_email[2])
109 email_body = message.get_payload()109 email_body = message.get_payload()
110 self.assertIn(expected, email_body)110 self.assertIn(expected, email_body)
111 self.assertEmailHeadersEqual(111 self.assertEmailHeadersEqual(
@@ -137,7 +137,7 @@ class TestBzrSyncEmail(BzrSyncTestCase):
137 'Subject: [Branch %s] Test branch' % self.db_branch.unique_name)137 'Subject: [Branch %s] Test branch' % self.db_branch.unique_name)
138 self.assertIn(expected, uncommit_email_body)138 self.assertIn(expected, uncommit_email_body)
139139
140 recommit_email_msg = message_from_bytes(recommit_email[2])140 recommit_email_msg = email.message_from_bytes(recommit_email[2])
141 recommit_email_body = recommit_email_msg.get_payload()[0].get_payload(141 recommit_email_body = recommit_email_msg.get_payload()[0].get_payload(
142 decode=True)142 decode=True)
143 subject = '[Branch %s] Rev 1: second' % self.db_branch.unique_name143 subject = '[Branch %s] Rev 1: second' % self.db_branch.unique_name
diff --git a/lib/lp/registry/browser/tests/test_person.py b/lib/lp/registry/browser/tests/test_person.py
index 2b015ce..b21e38e 100644
--- a/lib/lp/registry/browser/tests/test_person.py
+++ b/lib/lp/registry/browser/tests/test_person.py
@@ -5,6 +5,7 @@
5__metaclass__ = type5__metaclass__ = type
66
7import doctest7import doctest
8import email
8from operator import attrgetter9from operator import attrgetter
9import re10import re
10from textwrap import dedent11from textwrap import dedent
@@ -67,7 +68,6 @@ from lp.registry.interfaces.teammembership import (
67from lp.registry.model.karma import KarmaCategory68from lp.registry.model.karma import KarmaCategory
68from lp.registry.model.milestone import milestone_sort_key69from lp.registry.model.milestone import milestone_sort_key
69from lp.scripts.garbo import PopulateLatestPersonSourcePackageReleaseCache70from lp.scripts.garbo import PopulateLatestPersonSourcePackageReleaseCache
70from lp.services.compat import message_from_bytes
71from lp.services.config import config71from lp.services.config import config
72from lp.services.database.interfaces import IStore72from lp.services.database.interfaces import IStore
73from lp.services.features.testing import FeatureFixture73from lp.services.features.testing import FeatureFixture
@@ -938,7 +938,7 @@ class TestPersonEditView(TestPersonRenameFormMixin, TestCaseWithFactory):
938 messages = [msg for from_addr, to_addr, msg in stub.test_emails]938 messages = [msg for from_addr, to_addr, msg in stub.test_emails]
939 raw_msg = None939 raw_msg = None
940 for orig_msg in messages:940 for orig_msg in messages:
941 msg = message_from_bytes(orig_msg)941 msg = email.message_from_bytes(orig_msg)
942 if msg.get('to') == added_email:942 if msg.get('to') == added_email:
943 raw_msg = orig_msg943 raw_msg = orig_msg
944 token_url = get_token_url_from_email(raw_msg)944 token_url = get_token_url_from_email(raw_msg)
diff --git a/lib/lp/registry/doc/distribution-mirror.txt b/lib/lp/registry/doc/distribution-mirror.txt
index 29c35dc..8939a83 100644
--- a/lib/lp/registry/doc/distribution-mirror.txt
+++ b/lib/lp/registry/doc/distribution-mirror.txt
@@ -364,7 +364,7 @@ up on the public mirror listings.
364 >>> import transaction364 >>> import transaction
365 >>> transaction.commit()365 >>> transaction.commit()
366366
367 >>> from lp.services.compat import message_from_bytes367 >>> import email
368 >>> from lp.services.mail import stub368 >>> from lp.services.mail import stub
369 >>> len(stub.test_emails)369 >>> len(stub.test_emails)
370 2370 2
@@ -377,7 +377,7 @@ up on the public mirror listings.
377 ['mark@example.com']377 ['mark@example.com']
378 >>> valid_mirror.enabled378 >>> valid_mirror.enabled
379 False379 False
380 >>> msg = message_from_bytes(raw_message)380 >>> msg = email.message_from_bytes(raw_message)
381 >>> print(msg.get_payload())381 >>> print(msg.get_payload())
382 Dear mirror administrator,382 Dear mirror administrator,
383 <BLANKLINE>383 <BLANKLINE>
diff --git a/lib/lp/registry/stories/gpg-coc/xx-gpg-coc.txt b/lib/lp/registry/stories/gpg-coc/xx-gpg-coc.txt
index 09342e8..1b81c27 100644
--- a/lib/lp/registry/stories/gpg-coc/xx-gpg-coc.txt
+++ b/lib/lp/registry/stories/gpg-coc/xx-gpg-coc.txt
@@ -5,9 +5,9 @@ Claiming GPG Keys
5Setup5Setup
6-----6-----
77
8 >>> import email
8 >>> from zope.component import getUtility9 >>> from zope.component import getUtility
9 >>> from lp.registry.interfaces.person import IPersonSet10 >>> from lp.registry.interfaces.person import IPersonSet
10 >>> from lp.services.compat import message_from_bytes
11 >>> from lp.services.mail import stub11 >>> from lp.services.mail import stub
12 >>> from lp.testing.keyserver import KeyServerTac12 >>> from lp.testing.keyserver import KeyServerTac
13 >>> from lp.testing.pages import setupBrowserFreshLogin13 >>> from lp.testing.pages import setupBrowserFreshLogin
@@ -56,7 +56,7 @@ text part that provides useful information to users who -- for whatever reason
56-- cannot decrypt the token url. Start by grabbing the confirmation message.56-- cannot decrypt the token url. Start by grabbing the confirmation message.
5757
58 >>> from_addr, to_addrs, raw_msg = stub.test_emails.pop()58 >>> from_addr, to_addrs, raw_msg = stub.test_emails.pop()
59 >>> msg = message_from_bytes(raw_msg)59 >>> msg = email.message_from_bytes(raw_msg)
60 >>> msg.get_content_type()60 >>> msg.get_content_type()
61 'text/plain'61 'text/plain'
6262
@@ -178,7 +178,7 @@ their Launchpad account.
178Sample Person checks their email.178Sample Person checks their email.
179179
180 >>> from_addr, to_addrs, raw_msg = stub.test_emails.pop()180 >>> from_addr, to_addrs, raw_msg = stub.test_emails.pop()
181 >>> msg = message_from_bytes(raw_msg)181 >>> msg = email.message_from_bytes(raw_msg)
182 >>> msg.get_content_type()182 >>> msg.get_content_type()
183 'text/plain'183 'text/plain'
184 >>> body = msg.get_payload(decode=True)184 >>> body = msg.get_payload(decode=True)
@@ -512,7 +512,7 @@ Get the token from the body of the email sent.
512 >>> import re512 >>> import re
513 >>> from lp.services.mail import stub513 >>> from lp.services.mail import stub
514 >>> from_addr, to_addrs, raw_msg = stub.test_emails.pop()514 >>> from_addr, to_addrs, raw_msg = stub.test_emails.pop()
515 >>> msg = message_from_bytes(raw_msg)515 >>> msg = email.message_from_bytes(raw_msg)
516 >>> cipher_body = msg.get_payload(decode=1)516 >>> cipher_body = msg.get_payload(decode=1)
517 >>> body = decrypt_content(cipher_body, six.ensure_str('test'))517 >>> body = decrypt_content(cipher_body, six.ensure_str('test'))
518 >>> link = get_token_url_from_bytes(body)518 >>> link = get_token_url_from_bytes(body)
@@ -628,7 +628,7 @@ Test if the advertisement email was sent:
628628
629 >>> from lp.services.mail import stub629 >>> from lp.services.mail import stub
630 >>> from_addr, to_addrs, raw_msg = stub.test_emails.pop()630 >>> from_addr, to_addrs, raw_msg = stub.test_emails.pop()
631 >>> msg = message_from_bytes(raw_msg)631 >>> msg = email.message_from_bytes(raw_msg)
632 >>> print(six.ensure_text(msg.get_payload(decode=True)))632 >>> print(six.ensure_text(msg.get_payload(decode=True)))
633 <BLANKLINE>633 <BLANKLINE>
634 ...634 ...
diff --git a/lib/lp/registry/stories/mailinglists/hosted-email-address.txt b/lib/lp/registry/stories/mailinglists/hosted-email-address.txt
index 9952c67..3853b24 100644
--- a/lib/lp/registry/stories/mailinglists/hosted-email-address.txt
+++ b/lib/lp/registry/stories/mailinglists/hosted-email-address.txt
@@ -46,8 +46,8 @@ Launchpad sends that address a confirmation message.
46 >>> from_addr, to_addrs, raw_msg = stub.test_emails.pop()46 >>> from_addr, to_addrs, raw_msg = stub.test_emails.pop()
47 >>> stub.test_emails47 >>> stub.test_emails
48 []48 []
49 >>> from lp.services.compat import message_from_bytes49 >>> import email
50 >>> msg = message_from_bytes(raw_msg)50 >>> msg = email.message_from_bytes(raw_msg)
51 >>> print(msg['From'])51 >>> print(msg['From'])
52 Launchpad Email Validator <noreply@launchpad.net>52 Launchpad Email Validator <noreply@launchpad.net>
53 >>> print(msg['Subject'])53 >>> print(msg['Subject'])
diff --git a/lib/lp/registry/stories/person/xx-add-sshkey.txt b/lib/lp/registry/stories/person/xx-add-sshkey.txt
index c58405f..8c1095d 100644
--- a/lib/lp/registry/stories/person/xx-add-sshkey.txt
+++ b/lib/lp/registry/stories/person/xx-add-sshkey.txt
@@ -225,7 +225,7 @@ this case we'll raise an UnexpectedFormData.
225If he removes a key then he will get a security warning email notification225If he removes a key then he will get a security warning email notification
226that the key has been removed.226that the key has been removed.
227227
228 >>> from lp.services.compat import message_from_bytes228 >>> import email
229 >>> from lp.services.mail import stub229 >>> from lp.services.mail import stub
230 >>> stub.test_emails = []230 >>> stub.test_emails = []
231 >>> browser.open('http://launchpad.test/~salgado/+editsshkeys')231 >>> browser.open('http://launchpad.test/~salgado/+editsshkeys')
@@ -233,7 +233,7 @@ that the key has been removed.
233 >>> from_addr, to_addr, msg = stub.test_emails.pop()233 >>> from_addr, to_addr, msg = stub.test_emails.pop()
234 >>> to_addr234 >>> to_addr
235 ['guilherme.salgado@canonical.com']235 ['guilherme.salgado@canonical.com']
236 >>> payload = message_from_bytes(msg).get_payload()236 >>> payload = email.message_from_bytes(msg).get_payload()
237 >>> assert payload.startswith('The SSH Key')237 >>> assert payload.startswith('The SSH Key')
238238
239239
diff --git a/lib/lp/registry/tests/test_mailinglistapi.py b/lib/lp/registry/tests/test_mailinglistapi.py
index 256bbfa..ec689eb 100644
--- a/lib/lp/registry/tests/test_mailinglistapi.py
+++ b/lib/lp/registry/tests/test_mailinglistapi.py
@@ -6,7 +6,7 @@
6__metaclass__ = type6__metaclass__ = type
7__all__ = []7__all__ = []
88
9from email import message_from_string9import email
10from textwrap import dedent10from textwrap import dedent
1111
12from six.moves import xmlrpc_client12from six.moves import xmlrpc_client
@@ -36,10 +36,7 @@ from lp.registry.xmlrpc.mailinglist import (
36 ENABLED,36 ENABLED,
37 MailingListAPIView,37 MailingListAPIView,
38 )38 )
39from lp.services.compat import (39from lp.services.compat import message_as_bytes
40 message_as_bytes,
41 message_from_bytes,
42 )
43from lp.services.config import config40from lp.services.config import config
44from lp.services.identity.interfaces.account import AccountStatus41from lp.services.identity.interfaces.account import AccountStatus
45from lp.services.identity.interfaces.emailaddress import (42from lp.services.identity.interfaces.emailaddress import (
@@ -449,7 +446,7 @@ class MailingListAPIMessageTestCase(TestCaseWithFactory):
449 self.factory.makeMailingList(team, owner)446 self.factory.makeMailingList(team, owner)
450 sender = self.factory.makePerson(email='me@eg.dom')447 sender = self.factory.makePerson(email='me@eg.dom')
451 with person_logged_in(sender):448 with person_logged_in(sender):
452 message = message_from_string(dedent("""\449 message = email.message_from_string(dedent("""\
453 From: me@eg.dom450 From: me@eg.dom
454 To: team@lists.launchpad.test451 To: team@lists.launchpad.test
455 Subject: A question452 Subject: A question
@@ -493,7 +490,7 @@ class MailingListAPIMessageTestCase(TestCaseWithFactory):
493 # Non-ascii messages headers are re-encoded for moderators.490 # Non-ascii messages headers are re-encoded for moderators.
494 team, sender, message = self.makeMailingListAndHeldMessage()491 team, sender, message = self.makeMailingListAndHeldMessage()
495 with person_logged_in(sender):492 with person_logged_in(sender):
496 message = message_from_bytes(dedent("""\493 message = email.message_from_bytes(dedent("""\
497 From: \xa9 me <me@eg.dom>494 From: \xa9 me <me@eg.dom>
498 To: team@lists.launchpad.test495 To: team@lists.launchpad.test
499 Subject: \xa9 gremlins496 Subject: \xa9 gremlins
diff --git a/lib/lp/services/compat.py b/lib/lp/services/compat.py
index 88df437..835b123 100644
--- a/lib/lp/services/compat.py
+++ b/lib/lp/services/compat.py
@@ -10,15 +10,9 @@ __metaclass__ = type
10__all__ = [10__all__ = [
11 'escape',11 'escape',
12 'message_as_bytes',12 'message_as_bytes',
13 'message_from_bytes',
14 ]13 ]
1514
16try:15try:
17 from email import message_from_bytes
18except ImportError:
19 from email import message_from_string as message_from_bytes
20
21try:
22 from html import escape16 from html import escape
23except ImportError:17except ImportError:
24 from cgi import escape18 from cgi import escape
diff --git a/lib/lp/services/mail/doc/emailauthentication.txt b/lib/lp/services/mail/doc/emailauthentication.txt
index efcdce9..8cd8a8b 100644
--- a/lib/lp/services/mail/doc/emailauthentication.txt
+++ b/lib/lp/services/mail/doc/emailauthentication.txt
@@ -108,14 +108,11 @@ be canonicalised to \r\n. In order to ensure that the line endings in
108signed_canonicalised.txt are not already '\r\n', we recreate the test108signed_canonicalised.txt are not already '\r\n', we recreate the test
109message.109message.
110110
111 >>> from lp.services.compat import (111 >>> from lp.services.compat import message_as_bytes
112 ... message_as_bytes,
113 ... message_from_bytes,
114 ... )
115 >>> from lp.services.mail.signedmessage import SignedMessage112 >>> from lp.services.mail.signedmessage import SignedMessage
116 >>> msg = read_test_message('signed_canonicalised.txt')113 >>> msg = read_test_message('signed_canonicalised.txt')
117 >>> msg_lines = message_as_bytes(msg).splitlines()114 >>> msg_lines = message_as_bytes(msg).splitlines()
118 >>> msg = message_from_bytes(115 >>> msg = email.message_from_bytes(
119 ... b'\n'.join(msg_lines), _class=SignedMessage)116 ... b'\n'.join(msg_lines), _class=SignedMessage)
120 >>> msg.parsed_bytes = message_as_bytes(msg)117 >>> msg.parsed_bytes = message_as_bytes(msg)
121118
@@ -135,7 +132,7 @@ authenticateEmail() doesn't have any problems verifying the signature:
135132
136 >>> from lp.registry.interfaces.person import IPerson133 >>> from lp.registry.interfaces.person import IPerson
137 >>> for line_ending in b'\n', b'\r\n':134 >>> for line_ending in b'\n', b'\r\n':
138 ... msg = message_from_bytes(135 ... msg = email.message_from_bytes(
139 ... line_ending.join(msg_lines), _class=SignedMessage)136 ... line_ending.join(msg_lines), _class=SignedMessage)
140 ... msg.parsed_bytes = message_as_bytes(msg)137 ... msg.parsed_bytes = message_as_bytes(msg)
141 ... principal = authenticateEmail(msg, accept_any_timestamp)138 ... principal = authenticateEmail(msg, accept_any_timestamp)
diff --git a/lib/lp/services/mail/doc/mailbox.txt b/lib/lp/services/mail/doc/mailbox.txt
index aa9ac88..6424f2d 100644
--- a/lib/lp/services/mail/doc/mailbox.txt
+++ b/lib/lp/services/mail/doc/mailbox.txt
@@ -74,8 +74,8 @@ before:
74 >>> len(mails)74 >>> len(mails)
75 275 2
76 >>> id, raw_mail = mails[0]76 >>> id, raw_mail = mails[0]
77 >>> from lp.services.compat import message_from_bytes77 >>> import email
78 >>> mail = message_from_bytes(raw_mail)78 >>> mail = email.message_from_bytes(raw_mail)
79 >>> print(mail['Message-ID'])79 >>> print(mail['Message-ID'])
80 <test1>80 <test1>
8181
@@ -90,7 +90,7 @@ Since we didn't delete the mail, it's still in there:
90 >>> len(mails)90 >>> len(mails)
91 291 2
92 >>> id, raw_mail = mails[0]92 >>> id, raw_mail = mails[0]
93 >>> mail = message_from_bytes(raw_mail)93 >>> mail = email.message_from_bytes(raw_mail)
94 >>> print(mail['Message-ID'])94 >>> print(mail['Message-ID'])
95 <test1>95 <test1>
9696
diff --git a/lib/lp/services/mail/doc/sending-mail.txt b/lib/lp/services/mail/doc/sending-mail.txt
index de0483f..b520983 100644
--- a/lib/lp/services/mail/doc/sending-mail.txt
+++ b/lib/lp/services/mail/doc/sending-mail.txt
@@ -17,10 +17,10 @@ The mail get sent when the transaction gets commited:
1717
18Now let's look at the sent email:18Now let's look at the sent email:
1919
20 >>> from lp.services.compat import message_from_bytes20 >>> import email
21 >>> from lp.services.mail import stub21 >>> from lp.services.mail import stub
22 >>> from_addr, to_addr, raw_message = stub.test_emails.pop()22 >>> from_addr, to_addr, raw_message = stub.test_emails.pop()
23 >>> msg = message_from_bytes(raw_message)23 >>> msg = email.message_from_bytes(raw_message)
24 >>> msg['To']24 >>> msg['To']
25 'test@canonical.com'25 'test@canonical.com'
26 >>> msg['From']26 >>> msg['From']
@@ -52,7 +52,7 @@ the person's name is encoded properly.
5252
53 >>> transaction.commit()53 >>> transaction.commit()
54 >>> from_addr, to_addr, raw_message = stub.test_emails.pop()54 >>> from_addr, to_addr, raw_message = stub.test_emails.pop()
55 >>> msg = message_from_bytes(raw_message)55 >>> msg = email.message_from_bytes(raw_message)
56 >>> msg['To']56 >>> msg['To']
57 'test@canonical.com'57 'test@canonical.com'
58 >>> msg['From']58 >>> msg['From']
@@ -86,7 +86,7 @@ simple_sendmail_from_person uses the Person's preferred email address:
86 >>> transaction.commit()86 >>> transaction.commit()
87 >>> found = False87 >>> found = False
88 >>> for from_addr, to_addr, raw_message in stub.test_emails:88 >>> for from_addr, to_addr, raw_message in stub.test_emails:
89 ... msg = message_from_bytes(raw_message)89 ... msg = email.message_from_bytes(raw_message)
90 ... if msg['From'] == 'Sample Person <testing@canonical.com>':90 ... if msg['From'] == 'Sample Person <testing@canonical.com>':
91 ... found = True91 ... found = True
92 >>> assert found92 >>> assert found
@@ -109,7 +109,7 @@ the header will appear more than once in the output message.
109 >>> transaction.commit()109 >>> transaction.commit()
110110
111 >>> from_addr, to_addr, raw_message = stub.test_emails.pop()111 >>> from_addr, to_addr, raw_message = stub.test_emails.pop()
112 >>> msg = message_from_bytes(raw_message)112 >>> msg = email.message_from_bytes(raw_message)
113 >>> msg["X-Foo"]113 >>> msg["X-Foo"]
114 'test'114 'test'
115 >>> msg.get_all("X-Bar")115 >>> msg.get_all("X-Bar")
@@ -129,7 +129,7 @@ only.
129Now let's look at the sent email again.129Now let's look at the sent email again.
130130
131 >>> from_addr, to_addr, raw_message = stub.test_emails.pop()131 >>> from_addr, to_addr, raw_message = stub.test_emails.pop()
132 >>> msg = message_from_bytes(raw_message)132 >>> msg = email.message_from_bytes(raw_message)
133133
134 >>> from email.header import decode_header134 >>> from email.header import decode_header
135 >>> subject_str, charset = decode_header(msg['Subject'])[0]135 >>> subject_str, charset = decode_header(msg['Subject'])[0]
@@ -154,7 +154,7 @@ contain non-ASCII characters:
154 >>> transaction.commit()154 >>> transaction.commit()
155155
156 >>> from_addr, to_addr, raw_message = stub.test_emails.pop()156 >>> from_addr, to_addr, raw_message = stub.test_emails.pop()
157 >>> msg = message_from_bytes(raw_message)157 >>> msg = email.message_from_bytes(raw_message)
158158
159 >>> from email.utils import parseaddr159 >>> from email.utils import parseaddr
160 >>> from_name_encoded, from_addr = parseaddr(msg['From'])160 >>> from_name_encoded, from_addr = parseaddr(msg['From'])
@@ -185,7 +185,7 @@ surrounded by quotes and quoted if necessary:
185 >>> transaction.commit()185 >>> transaction.commit()
186186
187 >>> from_addr, to_addr, raw_message = stub.test_emails.pop()187 >>> from_addr, to_addr, raw_message = stub.test_emails.pop()
188 >>> msg = message_from_bytes(raw_message)188 >>> msg = email.message_from_bytes(raw_message)
189 >>> parseaddr(msg['From'])189 >>> parseaddr(msg['From'])
190 ('Foo [Baz] " Bar', 'foo.bar@canonical.com')190 ('Foo [Baz] " Bar', 'foo.bar@canonical.com')
191191
@@ -244,7 +244,7 @@ The message is the same as the one from the simple_sendmail test except
244that the precedence header was not added.244that the precedence header was not added.
245245
246 >>> from_addr, to_addr, raw_message = stub.test_emails.pop()246 >>> from_addr, to_addr, raw_message = stub.test_emails.pop()
247 >>> msg = message_from_bytes(raw_message)247 >>> msg = email.message_from_bytes(raw_message)
248 >>> msg['To']248 >>> msg['To']
249 'test@canonical.com'249 'test@canonical.com'
250 >>> msg['From']250 >>> msg['From']
@@ -282,7 +282,7 @@ provide better bounce handling.
282282
283 >>> from lp.services.config import config283 >>> from lp.services.config import config
284 >>> from_addr, to_add, raw_message = stub.test_emails.pop()284 >>> from_addr, to_add, raw_message = stub.test_emails.pop()
285 >>> sent_msg = message_from_bytes(raw_message)285 >>> sent_msg = email.message_from_bytes(raw_message)
286 >>> sent_msg['Return-Path'] == config.canonical.bounce_address286 >>> sent_msg['Return-Path'] == config.canonical.bounce_address
287 True287 True
288 >>> sent_msg['Errors-To'] == config.canonical.bounce_address288 >>> sent_msg['Errors-To'] == config.canonical.bounce_address
@@ -301,7 +301,7 @@ It's possible to set Return-Path manually if needed.
301 >>> transaction.commit()301 >>> transaction.commit()
302302
303 >>> from_addr, to_add, raw_message = stub.test_emails.pop()303 >>> from_addr, to_add, raw_message = stub.test_emails.pop()
304 >>> sent_msg = message_from_bytes(raw_message)304 >>> sent_msg = email.message_from_bytes(raw_message)
305 >>> sent_msg['Return-Path']305 >>> sent_msg['Return-Path']
306 '<>'306 '<>'
307307
@@ -322,7 +322,7 @@ are ignored.
322 ... print(to_addr)322 ... print(to_addr)
323 no-priv@canonical.com323 no-priv@canonical.com
324324
325 >>> sent_msg = message_from_bytes(raw_message)325 >>> sent_msg = email.message_from_bytes(raw_message)
326 >>> sent_msg['To']326 >>> sent_msg['To']
327 'test@canonical.com'327 'test@canonical.com'
328 >>> sent_msg['CC']328 >>> sent_msg['CC']
@@ -332,7 +332,7 @@ Since sendmail() gets the addresses to send to from the email header,
332it needs to take care of unfolding the headers, so that they don't332it needs to take care of unfolding the headers, so that they don't
333contain any line breaks.333contain any line breaks.
334334
335 >>> folded_message = message_from_bytes(b"""Subject: required335 >>> folded_message = email.message_from_bytes(b"""Subject: required
336 ... From: Not used336 ... From: Not used
337 ... <from.address@example.com>337 ... <from.address@example.com>
338 ... To: To Address338 ... To: To Address
diff --git a/lib/lp/services/mail/notification.py b/lib/lp/services/mail/notification.py
index 1d55202..9704416 100644
--- a/lib/lp/services/mail/notification.py
+++ b/lib/lp/services/mail/notification.py
@@ -10,13 +10,13 @@ __all__ = [
1010
1111
12from difflib import unified_diff12from difflib import unified_diff
13import email
13from email.mime.message import MIMEMessage14from email.mime.message import MIMEMessage
14from email.mime.multipart import MIMEMultipart15from email.mime.multipart import MIMEMultipart
15from email.mime.text import MIMEText16from email.mime.text import MIMEText
16import re17import re
1718
18from lp.bugs.mail.bugnotificationbuilder import get_bugmail_error_address19from lp.bugs.mail.bugnotificationbuilder import get_bugmail_error_address
19from lp.services.compat import message_from_bytes
20from lp.services.config import config20from lp.services.config import config
21from lp.services.mail.helpers import get_email_template21from lp.services.mail.helpers import get_email_template
22from lp.services.mail.mailwrapper import MailWrapper22from lp.services.mail.mailwrapper import MailWrapper
@@ -72,7 +72,7 @@ def send_process_error_notification(to_address, subject, error_msg,
72 original_msg_str = bytes(original_msg)72 original_msg_str = bytes(original_msg)
73 if len(original_msg_str) > max_return_size:73 if len(original_msg_str) > max_return_size:
74 truncated_msg_str = original_msg_str[:max_return_size]74 truncated_msg_str = original_msg_str[:max_return_size]
75 original_msg = message_from_bytes(truncated_msg_str)75 original_msg = email.message_from_bytes(truncated_msg_str)
76 msg.attach(MIMEMessage(original_msg))76 msg.attach(MIMEMessage(original_msg))
77 sendmail(msg)77 sendmail(msg)
7878
diff --git a/lib/lp/services/mail/signedmessage.py b/lib/lp/services/mail/signedmessage.py
index 02eeb90..732a397 100644
--- a/lib/lp/services/mail/signedmessage.py
+++ b/lib/lp/services/mail/signedmessage.py
@@ -11,12 +11,12 @@ __all__ = [
11 'strip_pgp_signature',11 'strip_pgp_signature',
12 ]12 ]
1313
14import email
14from email.message import Message15from email.message import Message
15import re16import re
1617
17from zope.interface import implementer18from zope.interface import implementer
1819
19from lp.services.compat import message_from_bytes
20from lp.services.mail.interfaces import ISignedMessage20from lp.services.mail.interfaces import ISignedMessage
2121
2222
@@ -42,7 +42,7 @@ def signed_message_from_bytes(buf):
42 It makes sure that the SignedMessage instance has access to the42 It makes sure that the SignedMessage instance has access to the
43 parsed bytes.43 parsed bytes.
44 """44 """
45 msg = message_from_bytes(buf, _class=SignedMessage)45 msg = email.message_from_bytes(buf, _class=SignedMessage)
46 msg.parsed_bytes = buf46 msg.parsed_bytes = buf
47 return msg47 return msg
4848
diff --git a/lib/lp/services/mail/stub.py b/lib/lp/services/mail/stub.py
index b63265c..c63d2fd 100644
--- a/lib/lp/services/mail/stub.py
+++ b/lib/lp/services/mail/stub.py
@@ -5,16 +5,14 @@
55
6__metaclass__ = type6__metaclass__ = type
77
8import email
8from logging import getLogger9from logging import getLogger
910
10from zope.component import getUtility11from zope.component import getUtility
11from zope.interface import implementer12from zope.interface import implementer
12from zope.sendmail.interfaces import IMailer13from zope.sendmail.interfaces import IMailer
1314
14from lp.services.compat import (15from lp.services.compat import message_as_bytes
15 message_as_bytes,
16 message_from_bytes,
17 )
1816
1917
20@implementer(IMailer)18@implementer(IMailer)
@@ -41,7 +39,7 @@ class StubMailer:
41 # headers that determine the actual To: address. However, this might39 # headers that determine the actual To: address. However, this might
42 # be required to bypass some spam filters.40 # be required to bypass some spam filters.
43 if self.rewrite:41 if self.rewrite:
44 message = message_from_bytes(message)42 message = email.message_from_bytes(message)
45 message['X-Orig-To'] = message['To']43 message['X-Orig-To'] = message['To']
46 message['X-Orig-Cc'] = message['Cc']44 message['X-Orig-Cc'] = message['Cc']
47 message['X-Orig-From'] = message['From']45 message['X-Orig-From'] = message['From']
diff --git a/lib/lp/services/mail/tests/incomingmail.txt b/lib/lp/services/mail/tests/incomingmail.txt
index 6fa33ca..bd77caa 100644
--- a/lib/lp/services/mail/tests/incomingmail.txt
+++ b/lib/lp/services/mail/tests/incomingmail.txt
@@ -301,8 +301,7 @@ to the user, citing the OOPS ID, with the original message attached.
301301
302OOPS notifications work even if the From: address isn't properly MIME-encoded.302OOPS notifications work even if the From: address isn't properly MIME-encoded.
303303
304 >>> from lp.services.compat import message_from_bytes304 >>> msg = email.message_from_bytes(
305 >>> msg = message_from_bytes(
306 ... u"""From: \u05D1 <bet@canonical.com>305 ... u"""From: \u05D1 <bet@canonical.com>
307 ... To: launchpad@oops.com306 ... To: launchpad@oops.com
308 ... X-Launchpad-Original-To: launchpad@oops.com307 ... X-Launchpad-Original-To: launchpad@oops.com
diff --git a/lib/lp/services/mail/tests/test_stub.py b/lib/lp/services/mail/tests/test_stub.py
index 85a549f..e1af47a 100644
--- a/lib/lp/services/mail/tests/test_stub.py
+++ b/lib/lp/services/mail/tests/test_stub.py
@@ -15,9 +15,9 @@ def test_simple_sendmail():
15 r"""15 r"""
16 Send an email (faked by TestMailer - no actual email is sent)16 Send an email (faked by TestMailer - no actual email is sent)
1717
18 >>> from email import message_from_bytes
18 >>> from email.mime.text import MIMEText19 >>> from email.mime.text import MIMEText
19 >>> import transaction20 >>> import transaction
20 >>> from lp.services.compat import message_from_bytes
21 >>> from lp.services.mail import stub21 >>> from lp.services.mail import stub
22 >>> from lp.services.mail.sendmail import simple_sendmail22 >>> from lp.services.mail.sendmail import simple_sendmail
2323
diff --git a/lib/lp/services/messages/model/message.py b/lib/lp/services/messages/model/message.py
index f6638b2..a2943ea 100644
--- a/lib/lp/services/messages/model/message.py
+++ b/lib/lp/services/messages/model/message.py
@@ -11,6 +11,7 @@ __all__ = [
11 ]11 ]
1212
13from datetime import datetime13from datetime import datetime
14import email
14from email.header import (15from email.header import (
15 decode_header,16 decode_header,
16 make_header,17 make_header,
@@ -57,7 +58,6 @@ from lp.registry.interfaces.person import (
57 PersonCreationRationale,58 PersonCreationRationale,
58 validate_public_person,59 validate_public_person,
59 )60 )
60from lp.services.compat import message_from_bytes
61from lp.services.config import config61from lp.services.config import config
62from lp.services.database.constants import UTC_NOW62from lp.services.database.constants import UTC_NOW
63from lp.services.database.datetimecol import UtcDateTimeCol63from lp.services.database.datetimecol import UtcDateTimeCol
@@ -376,7 +376,7 @@ class MessageSet:
376 # Parse the raw message into an email.message.Message instance,376 # Parse the raw message into an email.message.Message instance,
377 # if we haven't been given one already.377 # if we haven't been given one already.
378 if parsed_message is None:378 if parsed_message is None:
379 parsed_message = message_from_bytes(email_message)379 parsed_message = email.message_from_bytes(email_message)
380380
381 # We could easily generate a default, but a missing message-id381 # We could easily generate a default, but a missing message-id
382 # almost certainly means a developer is using this method when382 # almost certainly means a developer is using this method when
diff --git a/lib/lp/services/verification/doc/logintoken.txt b/lib/lp/services/verification/doc/logintoken.txt
index 9a8587c..201c15a 100644
--- a/lib/lp/services/verification/doc/logintoken.txt
+++ b/lib/lp/services/verification/doc/logintoken.txt
@@ -49,7 +49,7 @@ Let's create a new LoginToken to confirm an email address for foobar.
49The email does not have a precedence header because the user implicitly49The email does not have a precedence header because the user implicitly
50requested it to complete their task.50requested it to complete their task.
5151
52 >>> from lp.services.compat import message_from_bytes52 >>> from email import message_from_bytes
5353
54 >>> msg = message_from_bytes(found_msg)54 >>> msg = message_from_bytes(found_msg)
55 >>> print(msg['Precedence'])55 >>> print(msg['Precedence'])
diff --git a/lib/lp/services/verification/tests/logintoken.py b/lib/lp/services/verification/tests/logintoken.py
index 4f7d3ee..fda3d6a 100644
--- a/lib/lp/services/verification/tests/logintoken.py
+++ b/lib/lp/services/verification/tests/logintoken.py
@@ -3,16 +3,15 @@
33
4"""Helper functions for logintoken-related tests."""4"""Helper functions for logintoken-related tests."""
55
6import email
6import re7import re
78
8import six9import six
910
10from lp.services.compat import message_from_bytes
11
1211
13def get_token_url_from_email(email_msg):12def get_token_url_from_email(email_msg):
14 """Return the logintoken URL contained in the given email message."""13 """Return the logintoken URL contained in the given email message."""
15 msg = message_from_bytes(email_msg)14 msg = email.message_from_bytes(email_msg)
16 return get_token_url_from_bytes(msg.get_payload(decode=True))15 return get_token_url_from_bytes(msg.get_payload(decode=True))
1716
1817
diff --git a/lib/lp/testing/mail_helpers.py b/lib/lp/testing/mail_helpers.py
index 16bdcc2..23112fe 100644
--- a/lib/lp/testing/mail_helpers.py
+++ b/lib/lp/testing/mail_helpers.py
@@ -5,6 +5,7 @@
55
6__metaclass__ = type6__metaclass__ = type
77
8import email
8import operator9import operator
910
10import six11import six
@@ -18,7 +19,6 @@ from lp.registry.interfaces.persontransferjob import (
18 ITeamInvitationNotificationJobSource,19 ITeamInvitationNotificationJobSource,
19 ITeamJoinNotificationJobSource,20 ITeamJoinNotificationJobSource,
20 )21 )
21from lp.services.compat import message_from_bytes
22from lp.services.config import config22from lp.services.config import config
23from lp.services.job.runner import JobRunner23from lp.services.job.runner import JobRunner
24from lp.services.log.logger import DevNullLogger24from lp.services.log.logger import DevNullLogger
@@ -44,7 +44,7 @@ def pop_notifications(sort_key=None, commit=True):
4444
45 notifications = []45 notifications = []
46 for fromaddr, toaddrs, raw_message in stub.test_emails:46 for fromaddr, toaddrs, raw_message in stub.test_emails:
47 notification = message_from_bytes(raw_message)47 notification = email.message_from_bytes(raw_message)
48 notification['X-Envelope-To'] = ', '.join(toaddrs)48 notification['X-Envelope-To'] = ', '.join(toaddrs)
49 notification['X-Envelope-From'] = fromaddr49 notification['X-Envelope-From'] = fromaddr
50 notifications.append(notification)50 notifications.append(notification)
diff --git a/lib/lp/translations/doc/translations-export-to-branch.txt b/lib/lp/translations/doc/translations-export-to-branch.txt
index c8b74b0..76b63db 100644
--- a/lib/lp/translations/doc/translations-export-to-branch.txt
+++ b/lib/lp/translations/doc/translations-export-to-branch.txt
@@ -223,8 +223,8 @@ The Launchpad UI allows users to register branches in the Launchpad
223database without populating them in bzr. Exporting to such a branch223database without populating them in bzr. Exporting to such a branch
224won't work, so we email a notification to the branch owner.224won't work, so we email a notification to the branch owner.
225225
226 >>> import email
226 >>> from lp.codehosting.vfs import get_rw_server227 >>> from lp.codehosting.vfs import get_rw_server
227 >>> from lp.services.compat import message_from_bytes
228 >>> from lp.services.mail import stub228 >>> from lp.services.mail import stub
229 >>> from lp.testing.factory import (229 >>> from lp.testing.factory import (
230 ... remove_security_proxy_and_shout_at_engineer)230 ... remove_security_proxy_and_shout_at_engineer)
@@ -255,7 +255,7 @@ won't work, so we email a notification to the branch owner.
255 >>> transaction.commit()255 >>> transaction.commit()
256256
257 >>> sender, recipients, body = stub.test_emails.pop()257 >>> sender, recipients, body = stub.test_emails.pop()
258 >>> message = message_from_bytes(body)258 >>> message = email.message_from_bytes(body)
259 >>> print(message['Subject'])259 >>> print(message['Subject'])
260 Launchpad: translations branch has not been set up.260 Launchpad: translations branch has not been set up.
261261

Subscribers

People subscribed via source and target branches

to status/vote changes: