]> granicus.if.org Git - python/commitdiff
#27331: add policy keyword argument to all MIME subclasses.
authorR David Murray <rdmurray@bitdance.com>
Wed, 7 Sep 2016 20:48:35 +0000 (16:48 -0400)
committerR David Murray <rdmurray@bitdance.com>
Wed, 7 Sep 2016 20:48:35 +0000 (16:48 -0400)
Patch by Berker Peksag.

Doc/library/email.mime.rst
Doc/whatsnew/3.6.rst
Lib/email/mime/application.py
Lib/email/mime/audio.py
Lib/email/mime/base.py
Lib/email/mime/image.py
Lib/email/mime/message.py
Lib/email/mime/multipart.py
Lib/email/mime/text.py
Lib/test/test_email/test_email.py
Misc/NEWS

index 8297deaf93aa160d2ab70833a5dc0195ef650781..165011de9c19b0ae87b83968b111b56f46b30acc 100644 (file)
@@ -25,7 +25,7 @@ Here are the classes:
 
 .. currentmodule:: email.mime.base
 
-.. class:: MIMEBase(_maintype, _subtype, **_params)
+.. class:: MIMEBase(_maintype, _subtype, *, policy=compat32, **_params)
 
    Module: :mod:`email.mime.base`
 
@@ -41,10 +41,17 @@ Here are the classes:
    key/value dictionary and is passed directly to :meth:`Message.add_header
    <email.message.Message.add_header>`.
 
+   If *policy* is specified, (defaults to the
+   :class:`compat32 <email.policy.Compat32>` policy) it will be passed to
+   :class:`~email.message.Message`.
+
    The :class:`MIMEBase` class always adds a :mailheader:`Content-Type` header
    (based on *_maintype*, *_subtype*, and *_params*), and a
    :mailheader:`MIME-Version` header (always set to ``1.0``).
 
+   .. versionchanged:: 3.6
+      Added *policy* keyword-only parameter.
+
 
 .. currentmodule:: email.mime.nonmultipart
 
@@ -62,7 +69,8 @@ Here are the classes:
 
 .. currentmodule:: email.mime.multipart
 
-.. class:: MIMEMultipart(_subtype='mixed', boundary=None, _subparts=None, **_params)
+.. class:: MIMEMultipart(_subtype='mixed', boundary=None, _subparts=None, \
+                         *, policy=compat32, **_params)
 
    Module: :mod:`email.mime.multipart`
 
@@ -82,14 +90,20 @@ Here are the classes:
    to the message by using the :meth:`Message.attach
    <email.message.Message.attach>` method.
 
+   Optional *policy* argument defaults to :class:`compat32 <email.policy.Compat32>`.
+
    Additional parameters for the :mailheader:`Content-Type` header are taken from
    the keyword arguments, or passed into the *_params* argument, which is a keyword
    dictionary.
 
+   .. versionchanged:: 3.6
+      Added *policy* keyword-only parameter.
 
 .. currentmodule:: email.mime.application
 
-.. class:: MIMEApplication(_data, _subtype='octet-stream', _encoder=email.encoders.encode_base64, **_params)
+.. class:: MIMEApplication(_data, _subtype='octet-stream', \
+                           _encoder=email.encoders.encode_base64, \
+                           *, policy=compat32, **_params)
 
    Module: :mod:`email.mime.application`
 
@@ -109,12 +123,18 @@ Here are the classes:
    object as necessary.  The default encoding is base64.  See the
    :mod:`email.encoders` module for a list of the built-in encoders.
 
+   Optional *policy* argument defaults to :class:`compat32 <email.policy.Compat32>`.
+
    *_params* are passed straight through to the base class constructor.
 
+   .. versionchanged:: 3.6
+      Added *policy* keyword-only parameter.
 
 .. currentmodule:: email.mime.audio
 
-.. class:: MIMEAudio(_audiodata, _subtype=None, _encoder=email.encoders.encode_base64, **_params)
+.. class:: MIMEAudio(_audiodata, _subtype=None, \
+                     _encoder=email.encoders.encode_base64, \
+                     *, policy=compat32, **_params)
 
    Module: :mod:`email.mime.audio`
 
@@ -137,12 +157,18 @@ Here are the classes:
    object as necessary.  The default encoding is base64.  See the
    :mod:`email.encoders` module for a list of the built-in encoders.
 
+   Optional *policy* argument defaults to :class:`compat32 <email.policy.Compat32>`.
+
    *_params* are passed straight through to the base class constructor.
 
+   .. versionchanged:: 3.6
+      Added *policy* keyword-only parameter.
 
 .. currentmodule:: email.mime.image
 
-.. class:: MIMEImage(_imagedata, _subtype=None, _encoder=email.encoders.encode_base64, **_params)
+.. class:: MIMEImage(_imagedata, _subtype=None, \
+                     _encoder=email.encoders.encode_base64, \
+                    *, policy=compat32, **_params)
 
    Module: :mod:`email.mime.image`
 
@@ -165,13 +191,17 @@ Here are the classes:
    object as necessary.  The default encoding is base64.  See the
    :mod:`email.encoders` module for a list of the built-in encoders.
 
+   Optional *policy* argument defaults to :class:`compat32 <email.policy.Compat32>`.
+
    *_params* are passed straight through to the :class:`~email.mime.base.MIMEBase`
    constructor.
 
+   .. versionchanged:: 3.6
+      Added *policy* keyword-only parameter.
 
 .. currentmodule:: email.mime.message
 
-.. class:: MIMEMessage(_msg, _subtype='rfc822')
+.. class:: MIMEMessage(_msg, _subtype='rfc822', *, policy=compat32)
 
    Module: :mod:`email.mime.message`
 
@@ -184,10 +214,14 @@ Here are the classes:
    Optional *_subtype* sets the subtype of the message; it defaults to
    :mimetype:`rfc822`.
 
+   Optional *policy* argument defaults to :class:`compat32 <email.policy.Compat32>`.
+
+   .. versionchanged:: 3.6
+      Added *policy* keyword-only parameter.
 
 .. currentmodule:: email.mime.text
 
-.. class:: MIMEText(_text, _subtype='plain', _charset=None)
+.. class:: MIMEText(_text, _subtype='plain', _charset=None, *, policy=compat32)
 
    Module: :mod:`email.mime.text`
 
@@ -211,5 +245,10 @@ Here are the classes:
    will automatically encode the new payload (and add a new
    :mailheader:`Content-Transfer-Encoding` header).
 
+   Optional *policy* argument defaults to :class:`compat32 <email.policy.Compat32>`.
+
    .. versionchanged:: 3.5
       *_charset* also accepts :class:`~email.charset.Charset` instances.
+
+   .. versionchanged:: 3.6
+      Added *policy* keyword-only parameter.
index 7ab4c97915f4aabce1b5eee7bcf6616ddf25214e..ebb142c0b1046fdb0c894bc861ca81ff82433e7b 100644 (file)
@@ -455,6 +455,13 @@ Any code relying on the presence of ``default_format`` may
 need to be adapted. See :issue:`27819` for more details.
 
 
+email
+-----
+
+The :mod:`email.mime` classes now all accept an optional *policy* keyword.
+(Contributed by Berker Peksag in :issue:`27331`.)
+
+
 encodings
 ---------
 
index f5c5905564f690dc36a5d8afef5958bc0283281f..6877e554e1027171e34cb2336daa0b7677258a26 100644 (file)
@@ -14,7 +14,7 @@ class MIMEApplication(MIMENonMultipart):
     """Class for generating application/* MIME documents."""
 
     def __init__(self, _data, _subtype='octet-stream',
-                 _encoder=encoders.encode_base64, **_params):
+                 _encoder=encoders.encode_base64, *, policy=None, **_params):
         """Create an application/* type MIME document.
 
         _data is a string containing the raw application data.
@@ -31,6 +31,7 @@ class MIMEApplication(MIMENonMultipart):
         """
         if _subtype is None:
             raise TypeError('Invalid application MIME subtype')
-        MIMENonMultipart.__init__(self, 'application', _subtype, **_params)
+        MIMENonMultipart.__init__(self, 'application', _subtype, policy=policy,
+                                  **_params)
         self.set_payload(_data)
         _encoder(self)
index fbc118951aea75b27ba96015e7c219e4290dd115..4bcd7b224a862a7e977cb31f1079cf038bbbee75 100644 (file)
@@ -43,7 +43,7 @@ class MIMEAudio(MIMENonMultipart):
     """Class for generating audio/* MIME documents."""
 
     def __init__(self, _audiodata, _subtype=None,
-                 _encoder=encoders.encode_base64, **_params):
+                 _encoder=encoders.encode_base64, *, policy=None, **_params):
         """Create an audio/* type MIME document.
 
         _audiodata is a string containing the raw audio data.  If this data
@@ -68,6 +68,7 @@ class MIMEAudio(MIMENonMultipart):
             _subtype = _whatsnd(_audiodata)
         if _subtype is None:
             raise TypeError('Could not find audio MIME subtype')
-        MIMENonMultipart.__init__(self, 'audio', _subtype, **_params)
+        MIMENonMultipart.__init__(self, 'audio', _subtype, policy=policy,
+                                  **_params)
         self.set_payload(_audiodata)
         _encoder(self)
index ac919258b15ba8e678fde8a979ba2f93828930db..1a3f9b51f6c04567671dbdd24ac1835526ff0aef 100644 (file)
@@ -6,6 +6,8 @@
 
 __all__ = ['MIMEBase']
 
+import email.policy
+
 from email import message
 
 
@@ -13,14 +15,16 @@ from email import message
 class MIMEBase(message.Message):
     """Base class for MIME specializations."""
 
-    def __init__(self, _maintype, _subtype, **_params):
+    def __init__(self, _maintype, _subtype, *, policy=None, **_params):
         """This constructor adds a Content-Type: and a MIME-Version: header.
 
         The Content-Type: header is taken from the _maintype and _subtype
         arguments.  Additional parameters for this header are taken from the
         keyword arguments.
         """
-        message.Message.__init__(self)
+        if policy is None:
+            policy = email.policy.compat32
+        message.Message.__init__(self, policy=policy)
         ctype = '%s/%s' % (_maintype, _subtype)
         self.add_header('Content-Type', ctype, **_params)
         self['MIME-Version'] = '1.0'
index 5563823239b8be88893d6d03a39fb1a206c65285..92724643cdeeac4dba730d7b6b93226db5710bda 100644 (file)
@@ -17,7 +17,7 @@ class MIMEImage(MIMENonMultipart):
     """Class for generating image/* type MIME documents."""
 
     def __init__(self, _imagedata, _subtype=None,
-                 _encoder=encoders.encode_base64, **_params):
+                 _encoder=encoders.encode_base64, *, policy=None, **_params):
         """Create an image/* type MIME document.
 
         _imagedata is a string containing the raw image data.  If this data
@@ -41,6 +41,7 @@ class MIMEImage(MIMENonMultipart):
             _subtype = imghdr.what(None, _imagedata)
         if _subtype is None:
             raise TypeError('Could not guess image MIME subtype')
-        MIMENonMultipart.__init__(self, 'image', _subtype, **_params)
+        MIMENonMultipart.__init__(self, 'image', _subtype, policy=policy,
+                                  **_params)
         self.set_payload(_imagedata)
         _encoder(self)
index 275dbfd088652abedbf062ef99521261510141b4..07e4f2d11961516b634417be86be87fe60471854 100644 (file)
@@ -14,7 +14,7 @@ from email.mime.nonmultipart import MIMENonMultipart
 class MIMEMessage(MIMENonMultipart):
     """Class representing message/* MIME documents."""
 
-    def __init__(self, _msg, _subtype='rfc822'):
+    def __init__(self, _msg, _subtype='rfc822', *, policy=None):
         """Create a message/* type MIME document.
 
         _msg is a message object and must be an instance of Message, or a
@@ -24,7 +24,7 @@ class MIMEMessage(MIMENonMultipart):
         default is "rfc822" (this is defined by the MIME standard, even though
         the term "rfc822" is technically outdated by RFC 2822).
         """
-        MIMENonMultipart.__init__(self, 'message', _subtype)
+        MIMENonMultipart.__init__(self, 'message', _subtype, policy=policy)
         if not isinstance(_msg, message.Message):
             raise TypeError('Argument is not an instance of Message')
         # It's convenient to use this base class method.  We need to do it
index 96618650c519c5cdf38cd2dbfe6ddba8d78ec63f..2d3f288810dd9198ce3cf80edd5cd3e0acc4aec0 100644 (file)
@@ -14,6 +14,7 @@ class MIMEMultipart(MIMEBase):
     """Base class for MIME multipart/* type messages."""
 
     def __init__(self, _subtype='mixed', boundary=None, _subparts=None,
+                 *, policy=None,
                  **_params):
         """Creates a multipart/* type message.
 
@@ -33,7 +34,7 @@ class MIMEMultipart(MIMEBase):
         Additional parameters for the Content-Type header are taken from the
         keyword arguments (or passed into the _params argument).
         """
-        MIMEBase.__init__(self, 'multipart', _subtype, **_params)
+        MIMEBase.__init__(self, 'multipart', _subtype, policy=policy, **_params)
 
         # Initialise _payload to an empty list as the Message superclass's
         # implementation of is_multipart assumes that _payload is a list for
index 479928ec945d77f092879deb4d70392fa4091f93..87de8d235f9019fdd9dacd8927981dbfb59be53f 100644 (file)
@@ -14,7 +14,7 @@ from email.mime.nonmultipart import MIMENonMultipart
 class MIMEText(MIMENonMultipart):
     """Class for generating text/* type MIME documents."""
 
-    def __init__(self, _text, _subtype='plain', _charset=None):
+    def __init__(self, _text, _subtype='plain', _charset=None, *, policy=None):
         """Create a text/* type MIME document.
 
         _text is the string for this message object.
@@ -38,7 +38,7 @@ class MIMEText(MIMENonMultipart):
         if isinstance(_charset, Charset):
             _charset = str(_charset)
 
-        MIMENonMultipart.__init__(self, 'text', _subtype,
+        MIMENonMultipart.__init__(self, 'text', _subtype, policy=policy,
                                   **{'charset': _charset})
 
         self.set_payload(_text, _charset)
index 8aaca01dbacb0753a3649eb666fc7766cd924218..85fccf954875f5d8e5f39a810a1dc1812c8f374e 100644 (file)
@@ -31,6 +31,7 @@ from email.mime.image import MIMEImage
 from email.mime.base import MIMEBase
 from email.mime.message import MIMEMessage
 from email.mime.multipart import MIMEMultipart
+from email.mime.nonmultipart import MIMENonMultipart
 from email import utils
 from email import errors
 from email import encoders
@@ -2062,7 +2063,13 @@ YXNkZg==
 --===============0012394164==--""")
         self.assertEqual(m.get_payload(0).get_payload(), 'YXNkZg==')
 
+    def test_mimebase_default_policy(self):
+        m = MIMEBase('multipart', 'mixed')
+        self.assertIs(m.policy, email.policy.compat32)
 
+    def test_mimebase_custom_policy(self):
+        m = MIMEBase('multipart', 'mixed', policy=email.policy.default)
+        self.assertIs(m.policy, email.policy.default)
 
 # Test some badly formatted messages
 class TestNonConformant(TestEmailBase):
@@ -2664,6 +2671,19 @@ message 2
         msg = MIMEMultipart()
         self.assertTrue(msg.is_multipart())
 
+    def test_multipart_default_policy(self):
+        msg = MIMEMultipart()
+        msg['To'] = 'a@b.com'
+        msg['To'] = 'c@d.com'
+        self.assertEqual(msg.get_all('to'), ['a@b.com', 'c@d.com'])
+
+    def test_multipart_custom_policy(self):
+        msg = MIMEMultipart(policy=email.policy.default)
+        msg['To'] = 'a@b.com'
+        with self.assertRaises(ValueError) as cm:
+            msg['To'] = 'c@d.com'
+        self.assertEqual(str(cm.exception),
+                         'There may be at most 1 To headers in a message')
 
 # A general test of parser->model->generator idempotency.  IOW, read a message
 # in, parse it into a message object tree, then without touching the tree,
@@ -3313,6 +3333,27 @@ multipart/report
         g.flatten(msg, linesep='\r\n')
         self.assertEqual(s.getvalue(), msgtxt)
 
+    def test_mime_classes_policy_argument(self):
+        with openfile('audiotest.au', 'rb') as fp:
+            audiodata = fp.read()
+        with openfile('PyBanner048.gif', 'rb') as fp:
+            bindata = fp.read()
+        classes = [
+            (MIMEApplication, ('',)),
+            (MIMEAudio, (audiodata,)),
+            (MIMEImage, (bindata,)),
+            (MIMEMessage, (Message(),)),
+            (MIMENonMultipart, ('multipart', 'mixed')),
+            (MIMEText, ('',)),
+        ]
+        for cls, constructor in classes:
+            with self.subTest(cls=cls.__name__, policy='compat32'):
+                m = cls(*constructor)
+                self.assertIs(m.policy, email.policy.compat32)
+            with self.subTest(cls=cls.__name__, policy='default'):
+                m = cls(*constructor, policy=email.policy.default)
+                self.assertIs(m.policy, email.policy.default)
+
 
 # Test the iterator/generators
 class TestIterators(TestEmailBase):
index 3dc9693585a6b3ecaa5f8c29b3df367c69ee4714..772cb7a93b75c692f670169d113f2144d0a36b7b 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -91,6 +91,8 @@ Core and Builtins
 Library
 -------
 
+- Issue 27331: The email.mime classes now all accept an optional policy keyword.
+
 - Issue 27988: Fix email iter_attachments incorrect mutation of payload list.
 
 - Issue #16113: Add SHA-3 and SHAKE support to hashlib module.