mirror of
https://github.com/python/cpython.git
synced 2025-08-23 18:24:46 +00:00
#15222: Merge 3.2
This commit is contained in:
commit
f6b30385cc
3 changed files with 78 additions and 21 deletions
|
@ -208,6 +208,9 @@ class Mailbox:
|
||||||
raise ValueError("String input must be ASCII-only; "
|
raise ValueError("String input must be ASCII-only; "
|
||||||
"use bytes or a Message instead")
|
"use bytes or a Message instead")
|
||||||
|
|
||||||
|
# Whether each message must end in a newline
|
||||||
|
_append_newline = False
|
||||||
|
|
||||||
def _dump_message(self, message, target, mangle_from_=False):
|
def _dump_message(self, message, target, mangle_from_=False):
|
||||||
# This assumes the target file is open in binary mode.
|
# This assumes the target file is open in binary mode.
|
||||||
"""Dump message contents to target file."""
|
"""Dump message contents to target file."""
|
||||||
|
@ -219,6 +222,9 @@ class Mailbox:
|
||||||
data = buffer.read()
|
data = buffer.read()
|
||||||
data = data.replace(b'\n', linesep)
|
data = data.replace(b'\n', linesep)
|
||||||
target.write(data)
|
target.write(data)
|
||||||
|
if self._append_newline and not data.endswith(linesep):
|
||||||
|
# Make sure the message ends with a newline
|
||||||
|
target.write(linesep)
|
||||||
elif isinstance(message, (str, bytes, io.StringIO)):
|
elif isinstance(message, (str, bytes, io.StringIO)):
|
||||||
if isinstance(message, io.StringIO):
|
if isinstance(message, io.StringIO):
|
||||||
warnings.warn("Use of StringIO input is deprecated, "
|
warnings.warn("Use of StringIO input is deprecated, "
|
||||||
|
@ -230,11 +236,15 @@ class Mailbox:
|
||||||
message = message.replace(b'\nFrom ', b'\n>From ')
|
message = message.replace(b'\nFrom ', b'\n>From ')
|
||||||
message = message.replace(b'\n', linesep)
|
message = message.replace(b'\n', linesep)
|
||||||
target.write(message)
|
target.write(message)
|
||||||
|
if self._append_newline and not message.endswith(linesep):
|
||||||
|
# Make sure the message ends with a newline
|
||||||
|
target.write(linesep)
|
||||||
elif hasattr(message, 'read'):
|
elif hasattr(message, 'read'):
|
||||||
if hasattr(message, 'buffer'):
|
if hasattr(message, 'buffer'):
|
||||||
warnings.warn("Use of text mode files is deprecated, "
|
warnings.warn("Use of text mode files is deprecated, "
|
||||||
"use a binary mode file instead", DeprecationWarning, 3)
|
"use a binary mode file instead", DeprecationWarning, 3)
|
||||||
message = message.buffer
|
message = message.buffer
|
||||||
|
lastline = None
|
||||||
while True:
|
while True:
|
||||||
line = message.readline()
|
line = message.readline()
|
||||||
# Universal newline support.
|
# Universal newline support.
|
||||||
|
@ -248,6 +258,10 @@ class Mailbox:
|
||||||
line = b'>From ' + line[5:]
|
line = b'>From ' + line[5:]
|
||||||
line = line.replace(b'\n', linesep)
|
line = line.replace(b'\n', linesep)
|
||||||
target.write(line)
|
target.write(line)
|
||||||
|
lastline = line
|
||||||
|
if self._append_newline and lastline and not lastline.endswith(linesep):
|
||||||
|
# Make sure the message ends with a newline
|
||||||
|
target.write(linesep)
|
||||||
else:
|
else:
|
||||||
raise TypeError('Invalid message type: %s' % type(message))
|
raise TypeError('Invalid message type: %s' % type(message))
|
||||||
|
|
||||||
|
@ -833,30 +847,48 @@ class mbox(_mboxMMDF):
|
||||||
|
|
||||||
_mangle_from_ = True
|
_mangle_from_ = True
|
||||||
|
|
||||||
|
# All messages must end in a newline character, and
|
||||||
|
# _post_message_hooks outputs an empty line between messages.
|
||||||
|
_append_newline = True
|
||||||
|
|
||||||
def __init__(self, path, factory=None, create=True):
|
def __init__(self, path, factory=None, create=True):
|
||||||
"""Initialize an mbox mailbox."""
|
"""Initialize an mbox mailbox."""
|
||||||
self._message_factory = mboxMessage
|
self._message_factory = mboxMessage
|
||||||
_mboxMMDF.__init__(self, path, factory, create)
|
_mboxMMDF.__init__(self, path, factory, create)
|
||||||
|
|
||||||
def _pre_message_hook(self, f):
|
def _post_message_hook(self, f):
|
||||||
"""Called before writing each message to file f."""
|
"""Called after writing each message to file f."""
|
||||||
if f.tell() != 0:
|
f.write(linesep)
|
||||||
f.write(linesep)
|
|
||||||
|
|
||||||
def _generate_toc(self):
|
def _generate_toc(self):
|
||||||
"""Generate key-to-(start, stop) table of contents."""
|
"""Generate key-to-(start, stop) table of contents."""
|
||||||
starts, stops = [], []
|
starts, stops = [], []
|
||||||
|
last_was_empty = False
|
||||||
self._file.seek(0)
|
self._file.seek(0)
|
||||||
while True:
|
while True:
|
||||||
line_pos = self._file.tell()
|
line_pos = self._file.tell()
|
||||||
line = self._file.readline()
|
line = self._file.readline()
|
||||||
if line.startswith(b'From '):
|
if line.startswith(b'From '):
|
||||||
if len(stops) < len(starts):
|
if len(stops) < len(starts):
|
||||||
stops.append(line_pos - len(linesep))
|
if last_was_empty:
|
||||||
|
stops.append(line_pos - len(linesep))
|
||||||
|
else:
|
||||||
|
# The last line before the "From " line wasn't
|
||||||
|
# blank, but we consider it a start of a
|
||||||
|
# message anyway.
|
||||||
|
stops.append(line_pos)
|
||||||
starts.append(line_pos)
|
starts.append(line_pos)
|
||||||
|
last_was_empty = False
|
||||||
elif not line:
|
elif not line:
|
||||||
stops.append(line_pos)
|
if last_was_empty:
|
||||||
|
stops.append(line_pos - len(linesep))
|
||||||
|
else:
|
||||||
|
stops.append(line_pos)
|
||||||
break
|
break
|
||||||
|
elif line == linesep:
|
||||||
|
last_was_empty = True
|
||||||
|
else:
|
||||||
|
last_was_empty = False
|
||||||
self._toc = dict(enumerate(zip(starts, stops)))
|
self._toc = dict(enumerate(zip(starts, stops)))
|
||||||
self._next_key = len(self._toc)
|
self._next_key = len(self._toc)
|
||||||
self._file_length = self._file.tell()
|
self._file_length = self._file.tell()
|
||||||
|
|
|
@ -53,7 +53,7 @@ class TestMailbox(TestBase):
|
||||||
maxDiff = None
|
maxDiff = None
|
||||||
|
|
||||||
_factory = None # Overridden by subclasses to reuse tests
|
_factory = None # Overridden by subclasses to reuse tests
|
||||||
_template = 'From: foo\n\n%s'
|
_template = 'From: foo\n\n%s\n'
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self._path = support.TESTFN
|
self._path = support.TESTFN
|
||||||
|
@ -232,7 +232,7 @@ class TestMailbox(TestBase):
|
||||||
key0 = self._box.add(self._template % 0)
|
key0 = self._box.add(self._template % 0)
|
||||||
msg = self._box.get(key0)
|
msg = self._box.get(key0)
|
||||||
self.assertEqual(msg['from'], 'foo')
|
self.assertEqual(msg['from'], 'foo')
|
||||||
self.assertEqual(msg.get_payload(), '0')
|
self.assertEqual(msg.get_payload(), '0\n')
|
||||||
self.assertIs(self._box.get('foo'), None)
|
self.assertIs(self._box.get('foo'), None)
|
||||||
self.assertIs(self._box.get('foo', False), False)
|
self.assertIs(self._box.get('foo', False), False)
|
||||||
self._box.close()
|
self._box.close()
|
||||||
|
@ -240,14 +240,14 @@ class TestMailbox(TestBase):
|
||||||
key1 = self._box.add(self._template % 1)
|
key1 = self._box.add(self._template % 1)
|
||||||
msg = self._box.get(key1)
|
msg = self._box.get(key1)
|
||||||
self.assertEqual(msg['from'], 'foo')
|
self.assertEqual(msg['from'], 'foo')
|
||||||
self.assertEqual(msg.get_payload(), '1')
|
self.assertEqual(msg.get_payload(), '1\n')
|
||||||
|
|
||||||
def test_getitem(self):
|
def test_getitem(self):
|
||||||
# Retrieve message using __getitem__()
|
# Retrieve message using __getitem__()
|
||||||
key0 = self._box.add(self._template % 0)
|
key0 = self._box.add(self._template % 0)
|
||||||
msg = self._box[key0]
|
msg = self._box[key0]
|
||||||
self.assertEqual(msg['from'], 'foo')
|
self.assertEqual(msg['from'], 'foo')
|
||||||
self.assertEqual(msg.get_payload(), '0')
|
self.assertEqual(msg.get_payload(), '0\n')
|
||||||
self.assertRaises(KeyError, lambda: self._box['foo'])
|
self.assertRaises(KeyError, lambda: self._box['foo'])
|
||||||
self._box.discard(key0)
|
self._box.discard(key0)
|
||||||
self.assertRaises(KeyError, lambda: self._box[key0])
|
self.assertRaises(KeyError, lambda: self._box[key0])
|
||||||
|
@ -259,7 +259,7 @@ class TestMailbox(TestBase):
|
||||||
msg0 = self._box.get_message(key0)
|
msg0 = self._box.get_message(key0)
|
||||||
self.assertIsInstance(msg0, mailbox.Message)
|
self.assertIsInstance(msg0, mailbox.Message)
|
||||||
self.assertEqual(msg0['from'], 'foo')
|
self.assertEqual(msg0['from'], 'foo')
|
||||||
self.assertEqual(msg0.get_payload(), '0')
|
self.assertEqual(msg0.get_payload(), '0\n')
|
||||||
self._check_sample(self._box.get_message(key1))
|
self._check_sample(self._box.get_message(key1))
|
||||||
|
|
||||||
def test_get_bytes(self):
|
def test_get_bytes(self):
|
||||||
|
@ -432,15 +432,15 @@ class TestMailbox(TestBase):
|
||||||
self.assertIn(key0, self._box)
|
self.assertIn(key0, self._box)
|
||||||
key1 = self._box.add(self._template % 1)
|
key1 = self._box.add(self._template % 1)
|
||||||
self.assertIn(key1, self._box)
|
self.assertIn(key1, self._box)
|
||||||
self.assertEqual(self._box.pop(key0).get_payload(), '0')
|
self.assertEqual(self._box.pop(key0).get_payload(), '0\n')
|
||||||
self.assertNotIn(key0, self._box)
|
self.assertNotIn(key0, self._box)
|
||||||
self.assertIn(key1, self._box)
|
self.assertIn(key1, self._box)
|
||||||
key2 = self._box.add(self._template % 2)
|
key2 = self._box.add(self._template % 2)
|
||||||
self.assertIn(key2, self._box)
|
self.assertIn(key2, self._box)
|
||||||
self.assertEqual(self._box.pop(key2).get_payload(), '2')
|
self.assertEqual(self._box.pop(key2).get_payload(), '2\n')
|
||||||
self.assertNotIn(key2, self._box)
|
self.assertNotIn(key2, self._box)
|
||||||
self.assertIn(key1, self._box)
|
self.assertIn(key1, self._box)
|
||||||
self.assertEqual(self._box.pop(key1).get_payload(), '1')
|
self.assertEqual(self._box.pop(key1).get_payload(), '1\n')
|
||||||
self.assertNotIn(key1, self._box)
|
self.assertNotIn(key1, self._box)
|
||||||
self.assertEqual(len(self._box), 0)
|
self.assertEqual(len(self._box), 0)
|
||||||
|
|
||||||
|
@ -635,7 +635,7 @@ class TestMaildir(TestMailbox, unittest.TestCase):
|
||||||
msg_returned = self._box.get_message(key)
|
msg_returned = self._box.get_message(key)
|
||||||
self.assertEqual(msg_returned.get_subdir(), 'new')
|
self.assertEqual(msg_returned.get_subdir(), 'new')
|
||||||
self.assertEqual(msg_returned.get_flags(), '')
|
self.assertEqual(msg_returned.get_flags(), '')
|
||||||
self.assertEqual(msg_returned.get_payload(), '1')
|
self.assertEqual(msg_returned.get_payload(), '1\n')
|
||||||
msg2 = mailbox.MaildirMessage(self._template % 2)
|
msg2 = mailbox.MaildirMessage(self._template % 2)
|
||||||
msg2.set_info('2,S')
|
msg2.set_info('2,S')
|
||||||
self._box[key] = msg2
|
self._box[key] = msg2
|
||||||
|
@ -643,7 +643,7 @@ class TestMaildir(TestMailbox, unittest.TestCase):
|
||||||
msg_returned = self._box.get_message(key)
|
msg_returned = self._box.get_message(key)
|
||||||
self.assertEqual(msg_returned.get_subdir(), 'new')
|
self.assertEqual(msg_returned.get_subdir(), 'new')
|
||||||
self.assertEqual(msg_returned.get_flags(), 'S')
|
self.assertEqual(msg_returned.get_flags(), 'S')
|
||||||
self.assertEqual(msg_returned.get_payload(), '3')
|
self.assertEqual(msg_returned.get_payload(), '3\n')
|
||||||
|
|
||||||
def test_consistent_factory(self):
|
def test_consistent_factory(self):
|
||||||
# Add a message.
|
# Add a message.
|
||||||
|
@ -996,20 +996,20 @@ class _TestMboxMMDF(_TestSingleFile):
|
||||||
|
|
||||||
def test_add_from_string(self):
|
def test_add_from_string(self):
|
||||||
# Add a string starting with 'From ' to the mailbox
|
# Add a string starting with 'From ' to the mailbox
|
||||||
key = self._box.add('From foo@bar blah\nFrom: foo\n\n0')
|
key = self._box.add('From foo@bar blah\nFrom: foo\n\n0\n')
|
||||||
self.assertEqual(self._box[key].get_from(), 'foo@bar blah')
|
self.assertEqual(self._box[key].get_from(), 'foo@bar blah')
|
||||||
self.assertEqual(self._box[key].get_payload(), '0')
|
self.assertEqual(self._box[key].get_payload(), '0\n')
|
||||||
|
|
||||||
def test_add_from_bytes(self):
|
def test_add_from_bytes(self):
|
||||||
# Add a byte string starting with 'From ' to the mailbox
|
# Add a byte string starting with 'From ' to the mailbox
|
||||||
key = self._box.add(b'From foo@bar blah\nFrom: foo\n\n0')
|
key = self._box.add(b'From foo@bar blah\nFrom: foo\n\n0\n')
|
||||||
self.assertEqual(self._box[key].get_from(), 'foo@bar blah')
|
self.assertEqual(self._box[key].get_from(), 'foo@bar blah')
|
||||||
self.assertEqual(self._box[key].get_payload(), '0')
|
self.assertEqual(self._box[key].get_payload(), '0\n')
|
||||||
|
|
||||||
def test_add_mbox_or_mmdf_message(self):
|
def test_add_mbox_or_mmdf_message(self):
|
||||||
# Add an mboxMessage or MMDFMessage
|
# Add an mboxMessage or MMDFMessage
|
||||||
for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage):
|
for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage):
|
||||||
msg = class_('From foo@bar blah\nFrom: foo\n\n0')
|
msg = class_('From foo@bar blah\nFrom: foo\n\n0\n')
|
||||||
key = self._box.add(msg)
|
key = self._box.add(msg)
|
||||||
|
|
||||||
def test_open_close_open(self):
|
def test_open_close_open(self):
|
||||||
|
@ -1116,6 +1116,29 @@ class TestMbox(_TestMboxMMDF, unittest.TestCase):
|
||||||
perms = st.st_mode
|
perms = st.st_mode
|
||||||
self.assertFalse((perms & 0o111)) # Execute bits should all be off.
|
self.assertFalse((perms & 0o111)) # Execute bits should all be off.
|
||||||
|
|
||||||
|
def test_terminating_newline(self):
|
||||||
|
message = email.message.Message()
|
||||||
|
message['From'] = 'john@example.com'
|
||||||
|
message.set_payload('No newline at the end')
|
||||||
|
i = self._box.add(message)
|
||||||
|
|
||||||
|
# A newline should have been appended to the payload
|
||||||
|
message = self._box.get(i)
|
||||||
|
self.assertEqual(message.get_payload(), 'No newline at the end\n')
|
||||||
|
|
||||||
|
def test_message_separator(self):
|
||||||
|
# Check there's always a single blank line after each message
|
||||||
|
self._box.add('From: foo\n\n0') # No newline at the end
|
||||||
|
with open(self._path) as f:
|
||||||
|
data = f.read()
|
||||||
|
self.assertEqual(data[-3:], '0\n\n')
|
||||||
|
|
||||||
|
self._box.add('From: foo\n\n0\n') # Newline at the end
|
||||||
|
with open(self._path) as f:
|
||||||
|
data = f.read()
|
||||||
|
self.assertEqual(data[-3:], '0\n\n')
|
||||||
|
|
||||||
|
|
||||||
class TestMMDF(_TestMboxMMDF, unittest.TestCase):
|
class TestMMDF(_TestMboxMMDF, unittest.TestCase):
|
||||||
|
|
||||||
_factory = lambda self, path, factory=None: mailbox.MMDF(path, factory)
|
_factory = lambda self, path, factory=None: mailbox.MMDF(path, factory)
|
||||||
|
|
|
@ -33,6 +33,8 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #15222: Insert blank line after each message in mbox mailboxes
|
||||||
|
|
||||||
- Issue #16013: Fix CSV Reader parsing issue with ending quote characters.
|
- Issue #16013: Fix CSV Reader parsing issue with ending quote characters.
|
||||||
Patch by Serhiy Storchaka.
|
Patch by Serhiy Storchaka.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue