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

Skip to content

Commit 8286951

Browse files
committed
merge 3.3
2 parents 49379c0 + 847bb76 commit 8286951

4 files changed

Lines changed: 87 additions & 26 deletions

File tree

Doc/whatsnew/3.3.rst

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1832,6 +1832,15 @@ Deprecated Python modules, functions and methods
18321832
* :class:`abc.abstractstaticmethod` has been deprecated, use
18331833
:class:`staticmethod` with :func:`abc.abstractmethod` instead.
18341834

1835+
* :mod:`imoprtlib` package:
1836+
1837+
* :meth:`importlib.abc.SourceLoader.path_mtime` is now deprecated in favour of
1838+
:meth:`importlib.abc.SourceLoader.path_stats` as bytecode files now store
1839+
both the modification time and size of the source file the bytecode file was
1840+
compiled from.
1841+
1842+
1843+
18351844

18361845

18371846
Deprecated functions and types of the C API
@@ -1963,11 +1972,6 @@ Porting Python code
19631972
:attr:`sys.path_importer_cache` where it repesents the use of implicit
19641973
finders, but semantically it should not change anything.
19651974

1966-
* :meth:`importlib.abc.SourceLoader.path_mtime` is now deprecated in favour of
1967-
:meth:`importlib.abc.SourceLoader.path_stats` as bytecode files now store
1968-
both the modification time and size of the source file the bytecode file was
1969-
compiled from.
1970-
19711975
* :class:`importlib.abc.Finder` no longer specifies a `find_module()` abstract
19721976
method that must be implemented. If you were relying on subclasses to
19731977
implement that method, make sure to check for the method's existence first.

Lib/mailbox.py

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ def _string_to_bytes(self, message):
208208
raise ValueError("String input must be ASCII-only; "
209209
"use bytes or a Message instead")
210210

211+
# Whether each message must end in a newline
212+
_append_newline = False
213+
211214
def _dump_message(self, message, target, mangle_from_=False):
212215
# This assumes the target file is open in binary mode.
213216
"""Dump message contents to target file."""
@@ -219,6 +222,9 @@ def _dump_message(self, message, target, mangle_from_=False):
219222
data = buffer.read()
220223
data = data.replace(b'\n', linesep)
221224
target.write(data)
225+
if self._append_newline and not data.endswith(linesep):
226+
# Make sure the message ends with a newline
227+
target.write(linesep)
222228
elif isinstance(message, (str, bytes, io.StringIO)):
223229
if isinstance(message, io.StringIO):
224230
warnings.warn("Use of StringIO input is deprecated, "
@@ -230,11 +236,15 @@ def _dump_message(self, message, target, mangle_from_=False):
230236
message = message.replace(b'\nFrom ', b'\n>From ')
231237
message = message.replace(b'\n', linesep)
232238
target.write(message)
239+
if self._append_newline and not message.endswith(linesep):
240+
# Make sure the message ends with a newline
241+
target.write(linesep)
233242
elif hasattr(message, 'read'):
234243
if hasattr(message, 'buffer'):
235244
warnings.warn("Use of text mode files is deprecated, "
236245
"use a binary mode file instead", DeprecationWarning, 3)
237246
message = message.buffer
247+
lastline = None
238248
while True:
239249
line = message.readline()
240250
# Universal newline support.
@@ -248,6 +258,10 @@ def _dump_message(self, message, target, mangle_from_=False):
248258
line = b'>From ' + line[5:]
249259
line = line.replace(b'\n', linesep)
250260
target.write(line)
261+
lastline = line
262+
if self._append_newline and lastline and not lastline.endswith(linesep):
263+
# Make sure the message ends with a newline
264+
target.write(linesep)
251265
else:
252266
raise TypeError('Invalid message type: %s' % type(message))
253267

@@ -833,30 +847,48 @@ class mbox(_mboxMMDF):
833847

834848
_mangle_from_ = True
835849

850+
# All messages must end in a newline character, and
851+
# _post_message_hooks outputs an empty line between messages.
852+
_append_newline = True
853+
836854
def __init__(self, path, factory=None, create=True):
837855
"""Initialize an mbox mailbox."""
838856
self._message_factory = mboxMessage
839857
_mboxMMDF.__init__(self, path, factory, create)
840858

841-
def _pre_message_hook(self, f):
842-
"""Called before writing each message to file f."""
843-
if f.tell() != 0:
844-
f.write(linesep)
859+
def _post_message_hook(self, f):
860+
"""Called after writing each message to file f."""
861+
f.write(linesep)
845862

846863
def _generate_toc(self):
847864
"""Generate key-to-(start, stop) table of contents."""
848865
starts, stops = [], []
866+
last_was_empty = False
849867
self._file.seek(0)
850868
while True:
851869
line_pos = self._file.tell()
852870
line = self._file.readline()
853871
if line.startswith(b'From '):
854872
if len(stops) < len(starts):
855-
stops.append(line_pos - len(linesep))
873+
if last_was_empty:
874+
stops.append(line_pos - len(linesep))
875+
else:
876+
# The last line before the "From " line wasn't
877+
# blank, but we consider it a start of a
878+
# message anyway.
879+
stops.append(line_pos)
856880
starts.append(line_pos)
881+
last_was_empty = False
857882
elif not line:
858-
stops.append(line_pos)
883+
if last_was_empty:
884+
stops.append(line_pos - len(linesep))
885+
else:
886+
stops.append(line_pos)
859887
break
888+
elif line == linesep:
889+
last_was_empty = True
890+
else:
891+
last_was_empty = False
860892
self._toc = dict(enumerate(zip(starts, stops)))
861893
self._next_key = len(self._toc)
862894
self._file_length = self._file.tell()

Lib/test/test_mailbox.py

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class TestMailbox(TestBase):
5353
maxDiff = None
5454

5555
_factory = None # Overridden by subclasses to reuse tests
56-
_template = 'From: foo\n\n%s'
56+
_template = 'From: foo\n\n%s\n'
5757

5858
def setUp(self):
5959
self._path = support.TESTFN
@@ -232,22 +232,22 @@ def test_get(self):
232232
key0 = self._box.add(self._template % 0)
233233
msg = self._box.get(key0)
234234
self.assertEqual(msg['from'], 'foo')
235-
self.assertEqual(msg.get_payload(), '0')
235+
self.assertEqual(msg.get_payload(), '0\n')
236236
self.assertIs(self._box.get('foo'), None)
237237
self.assertIs(self._box.get('foo', False), False)
238238
self._box.close()
239239
self._box = self._factory(self._path)
240240
key1 = self._box.add(self._template % 1)
241241
msg = self._box.get(key1)
242242
self.assertEqual(msg['from'], 'foo')
243-
self.assertEqual(msg.get_payload(), '1')
243+
self.assertEqual(msg.get_payload(), '1\n')
244244

245245
def test_getitem(self):
246246
# Retrieve message using __getitem__()
247247
key0 = self._box.add(self._template % 0)
248248
msg = self._box[key0]
249249
self.assertEqual(msg['from'], 'foo')
250-
self.assertEqual(msg.get_payload(), '0')
250+
self.assertEqual(msg.get_payload(), '0\n')
251251
self.assertRaises(KeyError, lambda: self._box['foo'])
252252
self._box.discard(key0)
253253
self.assertRaises(KeyError, lambda: self._box[key0])
@@ -259,7 +259,7 @@ def test_get_message(self):
259259
msg0 = self._box.get_message(key0)
260260
self.assertIsInstance(msg0, mailbox.Message)
261261
self.assertEqual(msg0['from'], 'foo')
262-
self.assertEqual(msg0.get_payload(), '0')
262+
self.assertEqual(msg0.get_payload(), '0\n')
263263
self._check_sample(self._box.get_message(key1))
264264

265265
def test_get_bytes(self):
@@ -432,15 +432,15 @@ def test_pop(self):
432432
self.assertIn(key0, self._box)
433433
key1 = self._box.add(self._template % 1)
434434
self.assertIn(key1, self._box)
435-
self.assertEqual(self._box.pop(key0).get_payload(), '0')
435+
self.assertEqual(self._box.pop(key0).get_payload(), '0\n')
436436
self.assertNotIn(key0, self._box)
437437
self.assertIn(key1, self._box)
438438
key2 = self._box.add(self._template % 2)
439439
self.assertIn(key2, self._box)
440-
self.assertEqual(self._box.pop(key2).get_payload(), '2')
440+
self.assertEqual(self._box.pop(key2).get_payload(), '2\n')
441441
self.assertNotIn(key2, self._box)
442442
self.assertIn(key1, self._box)
443-
self.assertEqual(self._box.pop(key1).get_payload(), '1')
443+
self.assertEqual(self._box.pop(key1).get_payload(), '1\n')
444444
self.assertNotIn(key1, self._box)
445445
self.assertEqual(len(self._box), 0)
446446

@@ -635,15 +635,15 @@ def test_set_MM(self):
635635
msg_returned = self._box.get_message(key)
636636
self.assertEqual(msg_returned.get_subdir(), 'new')
637637
self.assertEqual(msg_returned.get_flags(), '')
638-
self.assertEqual(msg_returned.get_payload(), '1')
638+
self.assertEqual(msg_returned.get_payload(), '1\n')
639639
msg2 = mailbox.MaildirMessage(self._template % 2)
640640
msg2.set_info('2,S')
641641
self._box[key] = msg2
642642
self._box[key] = self._template % 3
643643
msg_returned = self._box.get_message(key)
644644
self.assertEqual(msg_returned.get_subdir(), 'new')
645645
self.assertEqual(msg_returned.get_flags(), 'S')
646-
self.assertEqual(msg_returned.get_payload(), '3')
646+
self.assertEqual(msg_returned.get_payload(), '3\n')
647647

648648
def test_consistent_factory(self):
649649
# Add a message.
@@ -996,20 +996,20 @@ def assertMailboxEmpty(self):
996996

997997
def test_add_from_string(self):
998998
# Add a string starting with 'From ' to the mailbox
999-
key = self._box.add('From foo@bar blah\nFrom: foo\n\n0')
999+
key = self._box.add('From foo@bar blah\nFrom: foo\n\n0\n')
10001000
self.assertEqual(self._box[key].get_from(), 'foo@bar blah')
1001-
self.assertEqual(self._box[key].get_payload(), '0')
1001+
self.assertEqual(self._box[key].get_payload(), '0\n')
10021002

10031003
def test_add_from_bytes(self):
10041004
# Add a byte string starting with 'From ' to the mailbox
1005-
key = self._box.add(b'From foo@bar blah\nFrom: foo\n\n0')
1005+
key = self._box.add(b'From foo@bar blah\nFrom: foo\n\n0\n')
10061006
self.assertEqual(self._box[key].get_from(), 'foo@bar blah')
1007-
self.assertEqual(self._box[key].get_payload(), '0')
1007+
self.assertEqual(self._box[key].get_payload(), '0\n')
10081008

10091009
def test_add_mbox_or_mmdf_message(self):
10101010
# Add an mboxMessage or MMDFMessage
10111011
for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage):
1012-
msg = class_('From foo@bar blah\nFrom: foo\n\n0')
1012+
msg = class_('From foo@bar blah\nFrom: foo\n\n0\n')
10131013
key = self._box.add(msg)
10141014

10151015
def test_open_close_open(self):
@@ -1116,6 +1116,29 @@ def test_file_perms(self):
11161116
perms = st.st_mode
11171117
self.assertFalse((perms & 0o111)) # Execute bits should all be off.
11181118

1119+
def test_terminating_newline(self):
1120+
message = email.message.Message()
1121+
message['From'] = '[email protected]'
1122+
message.set_payload('No newline at the end')
1123+
i = self._box.add(message)
1124+
1125+
# A newline should have been appended to the payload
1126+
message = self._box.get(i)
1127+
self.assertEqual(message.get_payload(), 'No newline at the end\n')
1128+
1129+
def test_message_separator(self):
1130+
# Check there's always a single blank line after each message
1131+
self._box.add('From: foo\n\n0') # No newline at the end
1132+
with open(self._path) as f:
1133+
data = f.read()
1134+
self.assertEqual(data[-3:], '0\n\n')
1135+
1136+
self._box.add('From: foo\n\n0\n') # Newline at the end
1137+
with open(self._path) as f:
1138+
data = f.read()
1139+
self.assertEqual(data[-3:], '0\n\n')
1140+
1141+
11191142
class TestMMDF(_TestMboxMMDF, unittest.TestCase):
11201143

11211144
_factory = lambda self, path, factory=None: mailbox.MMDF(path, factory)

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ Core and Builtins
3333
Library
3434
-------
3535

36+
- Issue #15222: Insert blank line after each message in mbox mailboxes
37+
3638
- Issue #16013: Fix CSV Reader parsing issue with ending quote characters.
3739
Patch by Serhiy Storchaka.
3840

0 commit comments

Comments
 (0)