Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bb17d2b

Browse files
committed
#18600: add policy to add_string, and as_bytes and __bytes__ methods.
This was triggered by wanting to make the doctest in email.policy.rst pass; as_bytes and __bytes__ are clearly useful now that we have BytesGenerator. Also updated the Message docs to document the policy keyword that was added in 3.3.
1 parent 3f58277 commit bb17d2b

6 files changed

Lines changed: 160 additions & 21 deletions

File tree

Doc/library/email.message.rst

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,32 @@ parameters, and for recursively walking over the object tree.
3131
Here are the methods of the :class:`Message` class:
3232

3333

34-
.. class:: Message()
34+
.. class:: Message(policy=compat32)
3535

36-
The constructor takes no arguments.
36+
The *policy* argument determiens the :mod:`~email.policy` that will be used
37+
to update the message model. The default value, :class:`compat32
38+
<email.policy.Compat32>` maintains backward compatibility with the
39+
Python 3.2 version of the email package. For more information see the
40+
:mod:`~email.policy` documentation.
3741

42+
.. versionchanged:: 3.3 The *policy* keyword argument was added.
3843

39-
.. method:: as_string(unixfrom=False, maxheaderlen=0)
44+
45+
.. method:: as_string(unixfrom=False, maxheaderlen=0, policy=None)
4046

4147
Return the entire message flattened as a string. When optional *unixfrom*
42-
is ``True``, the envelope header is included in the returned string.
43-
*unixfrom* defaults to ``False``. Flattening the message may trigger
44-
changes to the :class:`Message` if defaults need to be filled in to
45-
complete the transformation to a string (for example, MIME boundaries may
46-
be generated or modified).
48+
is true, the envelope header is included in the returned string.
49+
*unixfrom* defaults to ``False``. For backward compabitility reasons,
50+
*maxheaderlen* defaults to ``0``, so if you want a different value you
51+
must override it explicitly (the value specified for *max_line_length* in
52+
the policy will be ignored by this method). The *policy* argument may be
53+
used to override the default policy obtained from the message instance.
54+
This can be used to control some of the formatting produced by the
55+
method, since the specified *policy* will be passed to the ``Generator``.
56+
57+
Flattening the message may trigger changes to the :class:`Message` if
58+
defaults need to be filled in to complete the transformation to a string
59+
(for example, MIME boundaries may be generated or modified).
4760

4861
Note that this method is provided as a convenience and may not always
4962
format the message the way you want. For example, by default it does
@@ -59,10 +72,57 @@ Here are the methods of the :class:`Message` class:
5972
g.flatten(msg)
6073
text = fp.getvalue()
6174

75+
If the message object contains binary data that is not encoded according
76+
to RFC standards, the non-compliant data will be replaced by unicode
77+
"unknown character" code points. (See also :meth:`.as_bytes` and
78+
:class:`~email.generator.BytesGenerator`.)
79+
80+
.. versionchanged:: 3.4 the *policy* keyword argument was added.
81+
6282

6383
.. method:: __str__()
6484

65-
Equivalent to ``as_string(unixfrom=True)``.
85+
Equivalent to :meth:`.as_string()`. Allows ``str(msg)`` to produce a
86+
string containing the formatted message.
87+
88+
89+
.. method:: as_bytes(unixfrom=False, policy=None)
90+
91+
Return the entire message flattened as a bytes object. When optional
92+
*unixfrom* is true, the envelope header is included in the returned
93+
string. *unixfrom* defaults to ``False``. The *policy* argument may be
94+
used to override the default policy obtained from the message instance.
95+
This can be used to control some of the formatting produced by the
96+
method, since the specified *policy* will be passed to the
97+
``BytesGenerator``.
98+
99+
Flattening the message may trigger changes to the :class:`Message` if
100+
defaults need to be filled in to complete the transformation to a string
101+
(for example, MIME boundaries may be generated or modified).
102+
103+
Note that this method is provided as a convenience and may not always
104+
format the message the way you want. For example, by default it does
105+
not do the mangling of lines that begin with ``From`` that is
106+
required by the unix mbox format. For more flexibility, instantiate a
107+
:class:`~email.generator.BytesGenerator` instance and use its
108+
:meth:`flatten` method directly. For example::
109+
110+
from io import BytesIO
111+
from email.generator import BytesGenerator
112+
fp = BytesIO()
113+
g = BytesGenerator(fp, mangle_from_=True, maxheaderlen=60)
114+
g.flatten(msg)
115+
text = fp.getvalue()
116+
117+
.. versionadded:: 3.4
118+
119+
120+
.. method:: __bytes__()
121+
122+
Equivalent to :meth:`.as_bytes()`. Allows ``bytes(msg)`` to produce a
123+
bytes object containing the formatted message.
124+
125+
.. versionadded:: 3.4
66126

67127

68128
.. method:: is_multipart()

Doc/library/email.policy.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ separators for the platform on which it is running::
105105

106106
>>> import os
107107
>>> with open('converted.txt', 'wb') as f:
108-
... f.write(msg.as_string(policy=msg.policy.clone(linesep=os.linesep)))
108+
... f.write(msg.as_bytes(policy=msg.policy.clone(linesep=os.linesep)))
109+
17
109110

110111
Policy objects can also be combined using the addition operator, producing a
111112
policy object whose settings are a combination of the non-default values of the

Doc/whatsnew/3.4.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,26 @@ The :meth:`~aifc.getparams` method now returns a namedtuple rather than a
195195
plain tuple. (Contributed by Claudiu Popa in :issue:`17818`.)
196196

197197

198+
email
199+
-----
200+
201+
:meth:`~email.message.Message.as_string` now accepts a *policy* argument to
202+
override the default policy of the message when generating a string
203+
representation of it. This means that ``as_string`` can now be used in more
204+
circumstances, instead of having to create and use a :mod:`~email.generator` in
205+
order to pass formatting parameters to its ``flatten`` method.
206+
207+
New method :meth:`~email.message.Message.as_bytes` added to produce a bytes
208+
representation of the message in a fashion similar to how ``as_string``
209+
produces a string representation. It does not accept the *maxheaderlen*
210+
argument, but does accept the *unixfrom* and *policy* arguments. The
211+
:class:`~email.message.Message` :meth:`~email.message.Message.__bytes__` method
212+
calls it, meaning that ``bytes(mymsg)`` will now produce the intuitive
213+
result: a bytes object containing the fully formatted message.
214+
215+
(Contributed by R. David Murray in :issue:`18600`.)
216+
217+
198218
functools
199219
---------
200220

Lib/email/message.py

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -132,22 +132,50 @@ def __init__(self, policy=compat32):
132132

133133
def __str__(self):
134134
"""Return the entire formatted message as a string.
135-
This includes the headers, body, and envelope header.
136135
"""
137136
return self.as_string()
138137

139-
def as_string(self, unixfrom=False, maxheaderlen=0):
138+
def as_string(self, unixfrom=False, maxheaderlen=0, policy=None):
140139
"""Return the entire formatted message as a string.
141-
Optional `unixfrom' when True, means include the Unix From_ envelope
142-
header.
143140
144-
This is a convenience method and may not generate the message exactly
145-
as you intend. For more flexibility, use the flatten() method of a
146-
Generator instance.
141+
Optional 'unixfrom', when true, means include the Unix From_ envelope
142+
header. For backward compatibility reasons, if maxheaderlen is
143+
not specified it defaults to 0, so you must override it explicitly
144+
if you want a different maxheaderlen. 'policy' is passed to the
145+
Generator instance used to serialize the mesasge; if it is not
146+
specified the policy associated with the message instance is used.
147+
148+
If the message object contains binary data that is not encoded
149+
according to RFC standards, the non-compliant data will be replaced by
150+
unicode "unknown character" code points.
147151
"""
148152
from email.generator import Generator
153+
policy = self.policy if policy is None else policy
149154
fp = StringIO()
150-
g = Generator(fp, mangle_from_=False, maxheaderlen=maxheaderlen)
155+
g = Generator(fp,
156+
mangle_from_=False,
157+
maxheaderlen=maxheaderlen,
158+
policy=policy)
159+
g.flatten(self, unixfrom=unixfrom)
160+
return fp.getvalue()
161+
162+
def __bytes__(self):
163+
"""Return the entire formatted message as a bytes object.
164+
"""
165+
return self.as_bytes()
166+
167+
def as_bytes(self, unixfrom=False, policy=None):
168+
"""Return the entire formatted message as a bytes object.
169+
170+
Optional 'unixfrom', when true, means include the Unix From_ envelope
171+
header. 'policy' is passed to the BytesGenerator instance used to
172+
serialize the message; if not specified the policy associated with
173+
the message instance is used.
174+
"""
175+
from email.generator import BytesGenerator
176+
policy = self.policy if policy is None else policy
177+
fp = BytesIO()
178+
g = BytesGenerator(fp, mangle_from_=False, policy=policy)
151179
g.flatten(self, unixfrom=unixfrom)
152180
return fp.getvalue()
153181

Lib/test/test_email/test_email.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,15 +249,42 @@ def test__contains__(self):
249249
self.assertTrue('TO' in msg)
250250

251251
def test_as_string(self):
252-
eq = self.ndiffAssertEqual
253252
msg = self._msgobj('msg_01.txt')
254253
with openfile('msg_01.txt') as fp:
255254
text = fp.read()
256-
eq(text, str(msg))
255+
self.assertEqual(text, str(msg))
257256
fullrepr = msg.as_string(unixfrom=True)
258257
lines = fullrepr.split('\n')
259258
self.assertTrue(lines[0].startswith('From '))
260-
eq(text, NL.join(lines[1:]))
259+
self.assertEqual(text, NL.join(lines[1:]))
260+
261+
def test_as_string_policy(self):
262+
msg = self._msgobj('msg_01.txt')
263+
newpolicy = msg.policy.clone(linesep='\r\n')
264+
fullrepr = msg.as_string(policy=newpolicy)
265+
s = StringIO()
266+
g = Generator(s, policy=newpolicy)
267+
g.flatten(msg)
268+
self.assertEqual(fullrepr, s.getvalue())
269+
270+
def test_as_bytes(self):
271+
msg = self._msgobj('msg_01.txt')
272+
with openfile('msg_01.txt', 'rb') as fp:
273+
data = fp.read()
274+
self.assertEqual(data, bytes(msg))
275+
fullrepr = msg.as_bytes(unixfrom=True)
276+
lines = fullrepr.split(b'\n')
277+
self.assertTrue(lines[0].startswith(b'From '))
278+
self.assertEqual(data, b'\n'.join(lines[1:]))
279+
280+
def test_as_bytes_policy(self):
281+
msg = self._msgobj('msg_01.txt')
282+
newpolicy = msg.policy.clone(linesep='\r\n')
283+
fullrepr = msg.as_bytes(policy=newpolicy)
284+
s = BytesIO()
285+
g = BytesGenerator(s,policy=newpolicy)
286+
g.flatten(msg)
287+
self.assertEqual(fullrepr, s.getvalue())
261288

262289
# test_headerregistry.TestContentTypeHeader.bad_params
263290
def test_bad_param(self):

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ Core and Builtins
2222
Library
2323
-------
2424

25+
- Issue #18600: Added policy argument to email.message.Message.as_string,
26+
and as_bytes and __bytes__ methods to Message.
27+
2528
- Issue #18671: Output more information when logging exceptions occur.
2629

2730
- Issue #18621: Prevent the site module's patched builtins from keeping

0 commit comments

Comments
 (0)