#27331: add policy keyword argument to all MIME subclasses.

Patch by Berker Peksag.
This commit is contained in:
R David Murray 2016-09-07 16:48:35 -04:00
parent 3788b85628
commit 56b1f1b4d5
11 changed files with 117 additions and 20 deletions

View 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.

View 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
---------

View 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)

View 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)

View 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'

View 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)

View 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

View 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

View 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)

View 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):

View File

@ -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.