diff options
author | Giampaolo Rodola' <g.rodola@gmail.com> | 2012-09-25 19:34:54 (GMT) |
---|---|---|
committer | Giampaolo Rodola' <g.rodola@gmail.com> | 2012-09-25 19:34:54 (GMT) |
commit | 8286951f57ad596f248bc9ec765884e1536a87ee (patch) | |
tree | fda58b514779e619959ff10c6954d0638eb13eae | |
parent | 49379c05cd11ae075acf6e84824594bad785ef32 (diff) | |
parent | 847bb76f18b8b0059bcd3ee8cb428069ca7932ec (diff) | |
download | cpython-8286951f57ad596f248bc9ec765884e1536a87ee.zip cpython-8286951f57ad596f248bc9ec765884e1536a87ee.tar.gz cpython-8286951f57ad596f248bc9ec765884e1536a87ee.tar.bz2 |
merge 3.3
-rw-r--r-- | Doc/whatsnew/3.3.rst | 14 | ||||
-rw-r--r-- | Lib/mailbox.py | 44 | ||||
-rw-r--r-- | Lib/test/test_mailbox.py | 53 | ||||
-rw-r--r-- | Misc/NEWS | 2 |
4 files changed, 87 insertions, 26 deletions
diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst index c0e0e13..d4b9f89 100644 --- a/Doc/whatsnew/3.3.rst +++ b/Doc/whatsnew/3.3.rst @@ -1832,6 +1832,15 @@ Deprecated Python modules, functions and methods * :class:`abc.abstractstaticmethod` has been deprecated, use :class:`staticmethod` with :func:`abc.abstractmethod` instead. +* :mod:`imoprtlib` package: + + * :meth:`importlib.abc.SourceLoader.path_mtime` is now deprecated in favour of + :meth:`importlib.abc.SourceLoader.path_stats` as bytecode files now store + both the modification time and size of the source file the bytecode file was + compiled from. + + + Deprecated functions and types of the C API @@ -1963,11 +1972,6 @@ Porting Python code :attr:`sys.path_importer_cache` where it repesents the use of implicit finders, but semantically it should not change anything. -* :meth:`importlib.abc.SourceLoader.path_mtime` is now deprecated in favour of - :meth:`importlib.abc.SourceLoader.path_stats` as bytecode files now store - both the modification time and size of the source file the bytecode file was - compiled from. - * :class:`importlib.abc.Finder` no longer specifies a `find_module()` abstract method that must be implemented. If you were relying on subclasses to implement that method, make sure to check for the method's existence first. diff --git a/Lib/mailbox.py b/Lib/mailbox.py index 1841b0f..d3bf3fd 100644 --- a/Lib/mailbox.py +++ b/Lib/mailbox.py @@ -208,6 +208,9 @@ class Mailbox: raise ValueError("String input must be ASCII-only; " "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): # This assumes the target file is open in binary mode. """Dump message contents to target file.""" @@ -219,6 +222,9 @@ class Mailbox: data = buffer.read() data = data.replace(b'\n', linesep) 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)): if isinstance(message, io.StringIO): 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'\n', linesep) 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'): if hasattr(message, 'buffer'): warnings.warn("Use of text mode files is deprecated, " "use a binary mode file instead", DeprecationWarning, 3) message = message.buffer + lastline = None while True: line = message.readline() # Universal newline support. @@ -248,6 +258,10 @@ class Mailbox: line = b'>From ' + line[5:] line = line.replace(b'\n', linesep) 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: raise TypeError('Invalid message type: %s' % type(message)) @@ -833,30 +847,48 @@ class mbox(_mboxMMDF): _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): """Initialize an mbox mailbox.""" self._message_factory = mboxMessage _mboxMMDF.__init__(self, path, factory, create) - def _pre_message_hook(self, f): - """Called before writing each message to file f.""" - if f.tell() != 0: - f.write(linesep) + def _post_message_hook(self, f): + """Called after writing each message to file f.""" + f.write(linesep) def _generate_toc(self): """Generate key-to-(start, stop) table of contents.""" starts, stops = [], [] + last_was_empty = False self._file.seek(0) while True: line_pos = self._file.tell() line = self._file.readline() if line.startswith(b'From '): 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) + last_was_empty = False elif not line: - stops.append(line_pos) + if last_was_empty: + stops.append(line_pos - len(linesep)) + else: + stops.append(line_pos) break + elif line == linesep: + last_was_empty = True + else: + last_was_empty = False self._toc = dict(enumerate(zip(starts, stops))) self._next_key = len(self._toc) self._file_length = self._file.tell() diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py index fd7f319..6b1e933 100644 --- a/Lib/test/test_mailbox.py +++ b/Lib/test/test_mailbox.py @@ -53,7 +53,7 @@ class TestMailbox(TestBase): maxDiff = None _factory = None # Overridden by subclasses to reuse tests - _template = 'From: foo\n\n%s' + _template = 'From: foo\n\n%s\n' def setUp(self): self._path = support.TESTFN @@ -232,7 +232,7 @@ class TestMailbox(TestBase): key0 = self._box.add(self._template % 0) msg = self._box.get(key0) 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', False), False) self._box.close() @@ -240,14 +240,14 @@ class TestMailbox(TestBase): key1 = self._box.add(self._template % 1) msg = self._box.get(key1) self.assertEqual(msg['from'], 'foo') - self.assertEqual(msg.get_payload(), '1') + self.assertEqual(msg.get_payload(), '1\n') def test_getitem(self): # Retrieve message using __getitem__() key0 = self._box.add(self._template % 0) msg = self._box[key0] 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._box.discard(key0) self.assertRaises(KeyError, lambda: self._box[key0]) @@ -259,7 +259,7 @@ class TestMailbox(TestBase): msg0 = self._box.get_message(key0) self.assertIsInstance(msg0, mailbox.Message) 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)) def test_get_bytes(self): @@ -432,15 +432,15 @@ class TestMailbox(TestBase): self.assertIn(key0, self._box) key1 = self._box.add(self._template % 1) 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.assertIn(key1, self._box) key2 = self._box.add(self._template % 2) 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.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.assertEqual(len(self._box), 0) @@ -635,7 +635,7 @@ class TestMaildir(TestMailbox, unittest.TestCase): msg_returned = self._box.get_message(key) self.assertEqual(msg_returned.get_subdir(), 'new') 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.set_info('2,S') self._box[key] = msg2 @@ -643,7 +643,7 @@ class TestMaildir(TestMailbox, unittest.TestCase): msg_returned = self._box.get_message(key) self.assertEqual(msg_returned.get_subdir(), 'new') 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): # Add a message. @@ -996,20 +996,20 @@ class _TestMboxMMDF(_TestSingleFile): def test_add_from_string(self): # 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_payload(), '0') + self.assertEqual(self._box[key].get_payload(), '0\n') def test_add_from_bytes(self): # 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_payload(), '0') + self.assertEqual(self._box[key].get_payload(), '0\n') def test_add_mbox_or_mmdf_message(self): # Add an mboxMessage or 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) def test_open_close_open(self): @@ -1116,6 +1116,29 @@ class TestMbox(_TestMboxMMDF, unittest.TestCase): perms = st.st_mode 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): _factory = lambda self, path, factory=None: mailbox.MMDF(path, factory) @@ -33,6 +33,8 @@ Core and Builtins Library ------- +- Issue #15222: Insert blank line after each message in mbox mailboxes + - Issue #16013: Fix CSV Reader parsing issue with ending quote characters. Patch by Serhiy Storchaka. |