(original) (raw)
changeset: 103244:481d14cb7595 user: R David Murray rdmurray@bitdance.com date: Wed Sep 07 16:48:35 2016 -0400 files: 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 description: #27331: add policy keyword argument to all MIME subclasses. Patch by Berker Peksag. diff -r be272696991e -r 481d14cb7595 Doc/library/email.mime.rst --- a/Doc/library/email.mime.rst Wed Sep 07 12:51:08 2016 -0700 +++ b/Doc/library/email.mime.rst Wed Sep 07 16:48:35 2016 -0400 @@ -25,7 +25,7 @@ .. 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 @@ key/value dictionary and is passed directly to :meth:`Message.add_header`. + If *policy* is specified, (defaults to the + :class:`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 @@ .. 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 @@ to the message by using the :meth:`Message.attach` method. + Optional *policy* argument defaults to :class:`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 @@ 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 `. + *_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 @@ 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 `. + *_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 @@ 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 `. + *_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 @@ Optional *_subtype* sets the subtype of the message; it defaults to :mimetype:`rfc822`. + Optional *policy* argument defaults to :class:`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 @@ will automatically encode the new payload (and add a new :mailheader:`Content-Transfer-Encoding` header). + Optional *policy* argument defaults to :class:`compat32 `. + .. versionchanged:: 3.5 *_charset* also accepts :class:`~email.charset.Charset` instances. + + .. versionchanged:: 3.6 + Added *policy* keyword-only parameter. diff -r be272696991e -r 481d14cb7595 Doc/whatsnew/3.6.rst --- a/Doc/whatsnew/3.6.rst Wed Sep 07 12:51:08 2016 -0700 +++ b/Doc/whatsnew/3.6.rst Wed Sep 07 16:48:35 2016 -0400 @@ -455,6 +455,13 @@ 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 --------- diff -r be272696991e -r 481d14cb7595 Lib/email/mime/application.py --- a/Lib/email/mime/application.py Wed Sep 07 12:51:08 2016 -0700 +++ b/Lib/email/mime/application.py Wed Sep 07 16:48:35 2016 -0400 @@ -14,7 +14,7 @@ """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 @@ """ 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) diff -r be272696991e -r 481d14cb7595 Lib/email/mime/audio.py --- a/Lib/email/mime/audio.py Wed Sep 07 12:51:08 2016 -0700 +++ b/Lib/email/mime/audio.py Wed Sep 07 16:48:35 2016 -0400 @@ -43,7 +43,7 @@ """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 @@ _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) diff -r be272696991e -r 481d14cb7595 Lib/email/mime/base.py --- a/Lib/email/mime/base.py Wed Sep 07 12:51:08 2016 -0700 +++ b/Lib/email/mime/base.py Wed Sep 07 16:48:35 2016 -0400 @@ -6,6 +6,8 @@ __all__ = ['MIMEBase'] +import email.policy + from email import message @@ -13,14 +15,16 @@ 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' diff -r be272696991e -r 481d14cb7595 Lib/email/mime/image.py --- a/Lib/email/mime/image.py Wed Sep 07 12:51:08 2016 -0700 +++ b/Lib/email/mime/image.py Wed Sep 07 16:48:35 2016 -0400 @@ -17,7 +17,7 @@ """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 @@ _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) diff -r be272696991e -r 481d14cb7595 Lib/email/mime/message.py --- a/Lib/email/mime/message.py Wed Sep 07 12:51:08 2016 -0700 +++ b/Lib/email/mime/message.py Wed Sep 07 16:48:35 2016 -0400 @@ -14,7 +14,7 @@ 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 @@ 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 diff -r be272696991e -r 481d14cb7595 Lib/email/mime/multipart.py --- a/Lib/email/mime/multipart.py Wed Sep 07 12:51:08 2016 -0700 +++ b/Lib/email/mime/multipart.py Wed Sep 07 16:48:35 2016 -0400 @@ -14,6 +14,7 @@ """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 @@ 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 diff -r be272696991e -r 481d14cb7595 Lib/email/mime/text.py --- a/Lib/email/mime/text.py Wed Sep 07 12:51:08 2016 -0700 +++ b/Lib/email/mime/text.py Wed Sep 07 16:48:35 2016 -0400 @@ -14,7 +14,7 @@ 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 @@ 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) diff -r be272696991e -r 481d14cb7595 Lib/test/test_email/test_email.py --- a/Lib/test/test_email/test_email.py Wed Sep 07 12:51:08 2016 -0700 +++ b/Lib/test/test_email/test_email.py Wed Sep 07 16:48:35 2016 -0400 @@ -31,6 +31,7 @@ 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 @@ --===============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 @@ 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 @@ 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): diff -r be272696991e -r 481d14cb7595 Misc/NEWS --- a/Misc/NEWS Wed Sep 07 12:51:08 2016 -0700 +++ b/Misc/NEWS Wed Sep 07 16:48:35 2016 -0400 @@ -91,6 +91,8 @@ 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./rdmurray@bitdance.com