← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:py3-stubmailer-bytes into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:py3-stubmailer-bytes into launchpad:master.

Commit message:
Treat email bodies in StubMailer as bytes

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

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

Other bits of email infrastructure, as well as very many tests, still need to be updated to use bytes here for Python 3 compatibility.  This mainly just establishes the basic compatibility infrastructure.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:py3-stubmailer-bytes into launchpad:master.
diff --git a/lib/lp/bugs/scripts/debbugs.py b/lib/lp/bugs/scripts/debbugs.py
index f15de06..0d3c3c8 100644
--- a/lib/lp/bugs/scripts/debbugs.py
+++ b/lib/lp/bugs/scripts/debbugs.py
@@ -11,13 +11,10 @@ import re
 import subprocess
 import sys
 
-if sys.version_info[:2] >= (3, 5):
-    from email import message_from_bytes
-else:
-    from email import message_from_string as message_from_bytes
-
 import six
 
+from lp.services.compat import message_from_bytes
+
 
 class Bug:
     def __init__(self, db, id, package=None, date=None, status=None,
diff --git a/lib/lp/services/compat.py b/lib/lp/services/compat.py
index 50935f9..b985631 100644
--- a/lib/lp/services/compat.py
+++ b/lib/lp/services/compat.py
@@ -11,6 +11,8 @@ from __future__ import absolute_import, print_function, unicode_literals
 __metaclass__ = type
 __all__ = [
     'lzma',
+    'message_as_bytes',
+    'message_from_bytes',
     'mock',
     'SafeConfigParser',
     ]
@@ -21,6 +23,13 @@ except ImportError:
     from ConfigParser import SafeConfigParser
 
 try:
+    from email import message_from_bytes
+except ImportError:
+    from email import message_from_string as message_from_bytes
+
+import io
+
+try:
     import lzma
 except ImportError:
     from backports import lzma
@@ -29,3 +38,20 @@ try:
     import mock
 except ImportError:
     from unittest import mock
+
+import six
+
+
+if six.PY3:
+    def message_as_bytes(message):
+        from email.generator import BytesGenerator
+        from email.policy import compat32
+
+        fp = io.BytesIO()
+        g = BytesGenerator(
+            fp, mangle_from_=False, maxheaderlen=0, policy=compat32)
+        g.flatten(message)
+        return fp.getvalue()
+else:
+    def message_as_bytes(message):
+        return message.as_string()
diff --git a/lib/lp/services/mail/stub.py b/lib/lp/services/mail/stub.py
index 1492057..b63265c 100644
--- a/lib/lp/services/mail/stub.py
+++ b/lib/lp/services/mail/stub.py
@@ -5,13 +5,17 @@
 
 __metaclass__ = type
 
-import email
 from logging import getLogger
 
 from zope.component import getUtility
 from zope.interface import implementer
 from zope.sendmail.interfaces import IMailer
 
+from lp.services.compat import (
+    message_as_bytes,
+    message_from_bytes,
+    )
+
 
 @implementer(IMailer)
 class StubMailer:
@@ -37,7 +41,7 @@ class StubMailer:
         # headers that determine the actual To: address. However, this might
         # be required to bypass some spam filters.
         if self.rewrite:
-            message = email.message_from_string(message)
+            message = message_from_bytes(message)
             message['X-Orig-To'] = message['To']
             message['X-Orig-Cc'] = message['Cc']
             message['X-Orig-From'] = message['From']
@@ -47,7 +51,7 @@ class StubMailer:
             del message['Reply-To']
             message['To'] = ', '.join(self.to_addrs)
             message['From'] = self.from_addr
-            message = message.as_string()
+            message = message_as_bytes(message)
 
         sendmail = getUtility(IMailer, self.mailer)
         sendmail.send(self.from_addr, self.to_addrs, message)
diff --git a/lib/lp/testing/mail_helpers.py b/lib/lp/testing/mail_helpers.py
index 3ac740a..1b405e3 100644
--- a/lib/lp/testing/mail_helpers.py
+++ b/lib/lp/testing/mail_helpers.py
@@ -7,7 +7,6 @@ from __future__ import absolute_import, print_function
 
 __metaclass__ = type
 
-import email
 import operator
 
 import transaction
@@ -20,6 +19,7 @@ from lp.registry.interfaces.persontransferjob import (
     ITeamInvitationNotificationJobSource,
     ITeamJoinNotificationJobSource,
     )
+from lp.services.compat import message_from_bytes
 from lp.services.config import config
 from lp.services.job.runner import JobRunner
 from lp.services.log.logger import DevNullLogger
@@ -45,7 +45,7 @@ def pop_notifications(sort_key=None, commit=True):
 
     notifications = []
     for fromaddr, toaddrs, raw_message in stub.test_emails:
-        notification = email.message_from_string(raw_message)
+        notification = message_from_bytes(raw_message)
         notification['X-Envelope-To'] = ', '.join(toaddrs)
         notification['X-Envelope-From'] = fromaddr
         notifications.append(notification)