Issue #27029: Removed deprecated support of universal newlines mode from ZipFile.open().

This commit is contained in:
Serhiy Storchaka 2016-06-11 19:32:44 +03:00
parent e6dae877dc
commit e670be2273
5 changed files with 24 additions and 222 deletions

View file

@ -204,18 +204,13 @@ ZipFile Objects
Return a list of archive members by name. Return a list of archive members by name.
.. index::
single: universal newlines; zipfile.ZipFile.open method
.. method:: ZipFile.open(name, mode='r', pwd=None, *, force_zip64=False) .. method:: ZipFile.open(name, mode='r', pwd=None, *, force_zip64=False)
Access a member of the archive as a file-like object. *name* Access a member of the archive as a binary file-like object. *name*
is the name of the file in the archive, or a :class:`ZipInfo` object. The can be either the name of a file within the archive or a :class:`ZipInfo`
*mode* parameter, if included, must be one of the following: ``'r'`` (the object. The *mode* parameter, if included, must be ``'r'`` (the default)
default), ``'U'``, ``'rU'`` or ``'w'``. Choosing ``'U'`` or ``'rU'`` will or ``'w'``. *pwd* is the password used to decrypt encrypted ZIP files.
enable :term:`universal newlines` support in the read-only object. *pwd* is Calling :meth:`.open` on a closed ZipFile will raise a :exc:`RuntimeError`.
the password used to decrypt encrypted ZIP files. Calling :meth:`.open` on
a closed ZipFile will raise a :exc:`RuntimeError`.
:meth:`~ZipFile.open` is also a context manager and therefore supports the :meth:`~ZipFile.open` is also a context manager and therefore supports the
:keyword:`with` statement:: :keyword:`with` statement::
@ -224,7 +219,7 @@ ZipFile Objects
with myzip.open('eggs.txt') as myfile: with myzip.open('eggs.txt') as myfile:
print(myfile.read()) print(myfile.read())
With *mode* ``'r'``, ``'U'`` or ``'rU'``, the file-like object With *mode* ``'r'`` the file-like object
(``ZipExtFile``) is read-only and provides the following methods: (``ZipExtFile``) is read-only and provides the following methods:
:meth:`~io.BufferedIOBase.read`, :meth:`~io.IOBase.readline`, :meth:`~io.BufferedIOBase.read`, :meth:`~io.IOBase.readline`,
:meth:`~io.IOBase.readlines`, :meth:`__iter__`, :meth:`~io.IOBase.readlines`, :meth:`__iter__`,
@ -248,8 +243,8 @@ ZipFile Objects
or a :class:`ZipInfo` object. You will appreciate this when trying to read a or a :class:`ZipInfo` object. You will appreciate this when trying to read a
ZIP file that contains members with duplicate names. ZIP file that contains members with duplicate names.
.. deprecated-removed:: 3.4 3.6 .. versionchanged:: 3.6
The ``'U'`` or ``'rU'`` mode. Use :class:`io.TextIOWrapper` for reading Removed support of ``mode='U'``. Use :class:`io.TextIOWrapper` for reading
compressed text files in :term:`universal newlines` mode. compressed text files in :term:`universal newlines` mode.
.. versionchanged:: 3.6 .. versionchanged:: 3.6

View file

@ -580,6 +580,11 @@ API and Feature Removals
:mod:`tkinter` widget classes were removed (corresponding Tk commands :mod:`tkinter` widget classes were removed (corresponding Tk commands
were obsolete since Tk 4.0). were obsolete since Tk 4.0).
* The :meth:`~zipfile.ZipFile.open` method of the :class:`zipfile.ZipFile`
class no longer supports the ``'U'`` mode (was deprecated since Python 3.4).
Use :class:`io.TextIOWrapper` for reading compressed text files in
:term:`universal newlines` mode.
Porting to Python 3.6 Porting to Python 3.6
===================== =====================

View file

@ -38,10 +38,6 @@ def get_files(test):
yield f yield f
test.assertFalse(f.closed) test.assertFalse(f.closed)
def openU(zipfp, fn):
with check_warnings(('', DeprecationWarning)):
return zipfp.open(fn, 'rU')
class AbstractTestsWithSourceFile: class AbstractTestsWithSourceFile:
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
@ -1035,32 +1031,6 @@ class OtherTests(unittest.TestCase):
data += zipfp.read(info) data += zipfp.read(info)
self.assertIn(data, {b"foobar", b"barfoo"}) self.assertIn(data, {b"foobar", b"barfoo"})
def test_universal_deprecation(self):
f = io.BytesIO()
with zipfile.ZipFile(f, "w") as zipfp:
zipfp.writestr('spam.txt', b'ababagalamaga')
with zipfile.ZipFile(f, "r") as zipfp:
for mode in 'U', 'rU':
with self.assertWarns(DeprecationWarning):
zipopen = zipfp.open('spam.txt', mode)
zipopen.close()
def test_universal_readaheads(self):
f = io.BytesIO()
data = b'a\r\n' * 16 * 1024
with zipfile.ZipFile(f, 'w', zipfile.ZIP_STORED) as zipfp:
zipfp.writestr(TESTFN, data)
data2 = b''
with zipfile.ZipFile(f, 'r') as zipfp, \
openU(zipfp, TESTFN) as zipopen:
for line in zipopen:
data2 += line
self.assertEqual(data, data2.replace(b'\n', b'\r\n'))
def test_writestr_extended_local_header_issue1202(self): def test_writestr_extended_local_header_issue1202(self):
with zipfile.ZipFile(TESTFN2, 'w') as orig_zip: with zipfile.ZipFile(TESTFN2, 'w') as orig_zip:
for data in 'abcdefghijklmnop': for data in 'abcdefghijklmnop':
@ -1268,9 +1238,12 @@ class OtherTests(unittest.TestCase):
zipf.writestr("foo.txt", "O, for a Muse of Fire!") zipf.writestr("foo.txt", "O, for a Muse of Fire!")
with zipfile.ZipFile(TESTFN, mode="r") as zipf: with zipfile.ZipFile(TESTFN, mode="r") as zipf:
# read the data to make sure the file is there # read the data to make sure the file is there
zipf.read("foo.txt") zipf.read("foo.txt")
self.assertRaises(RuntimeError, zipf.open, "foo.txt", "q") self.assertRaises(RuntimeError, zipf.open, "foo.txt", "q")
# universal newlines support is removed
self.assertRaises(RuntimeError, zipf.open, "foo.txt", "U")
self.assertRaises(RuntimeError, zipf.open, "foo.txt", "rU")
def test_read0(self): def test_read0(self):
"""Check that calling read(0) on a ZipExtFile object returns an empty """Check that calling read(0) on a ZipExtFile object returns an empty
@ -2011,138 +1984,6 @@ class TestWithDirectory(unittest.TestCase):
unlink(TESTFN) unlink(TESTFN)
class AbstractUniversalNewlineTests:
@classmethod
def setUpClass(cls):
cls.line_gen = [bytes("Test of zipfile line %d." % i, "ascii")
for i in range(FIXEDTEST_SIZE)]
cls.seps = (b'\r', b'\r\n', b'\n')
cls.arcdata = {}
for n, s in enumerate(cls.seps):
cls.arcdata[s] = s.join(cls.line_gen) + s
def setUp(self):
self.arcfiles = {}
for n, s in enumerate(self.seps):
self.arcfiles[s] = '%s-%d' % (TESTFN, n)
with open(self.arcfiles[s], "wb") as f:
f.write(self.arcdata[s])
def make_test_archive(self, f, compression):
# Create the ZIP archive
with zipfile.ZipFile(f, "w", compression) as zipfp:
for fn in self.arcfiles.values():
zipfp.write(fn, fn)
def read_test(self, f, compression):
self.make_test_archive(f, compression)
# Read the ZIP archive
with zipfile.ZipFile(f, "r") as zipfp:
for sep, fn in self.arcfiles.items():
with openU(zipfp, fn) as fp:
zipdata = fp.read()
self.assertEqual(self.arcdata[sep], zipdata)
def test_read(self):
for f in get_files(self):
self.read_test(f, self.compression)
def readline_read_test(self, f, compression):
self.make_test_archive(f, compression)
# Read the ZIP archive
with zipfile.ZipFile(f, "r") as zipfp:
for sep, fn in self.arcfiles.items():
with openU(zipfp, fn) as zipopen:
data = b''
while True:
read = zipopen.readline()
if not read:
break
data += read
read = zipopen.read(5)
if not read:
break
data += read
self.assertEqual(data, self.arcdata[b'\n'])
def test_readline_read(self):
for f in get_files(self):
self.readline_read_test(f, self.compression)
def readline_test(self, f, compression):
self.make_test_archive(f, compression)
# Read the ZIP archive
with zipfile.ZipFile(f, "r") as zipfp:
for sep, fn in self.arcfiles.items():
with openU(zipfp, fn) as zipopen:
for line in self.line_gen:
linedata = zipopen.readline()
self.assertEqual(linedata, line + b'\n')
def test_readline(self):
for f in get_files(self):
self.readline_test(f, self.compression)
def readlines_test(self, f, compression):
self.make_test_archive(f, compression)
# Read the ZIP archive
with zipfile.ZipFile(f, "r") as zipfp:
for sep, fn in self.arcfiles.items():
with openU(zipfp, fn) as fp:
ziplines = fp.readlines()
for line, zipline in zip(self.line_gen, ziplines):
self.assertEqual(zipline, line + b'\n')
def test_readlines(self):
for f in get_files(self):
self.readlines_test(f, self.compression)
def iterlines_test(self, f, compression):
self.make_test_archive(f, compression)
# Read the ZIP archive
with zipfile.ZipFile(f, "r") as zipfp:
for sep, fn in self.arcfiles.items():
with openU(zipfp, fn) as fp:
for line, zipline in zip(self.line_gen, fp):
self.assertEqual(zipline, line + b'\n')
def test_iterlines(self):
for f in get_files(self):
self.iterlines_test(f, self.compression)
def tearDown(self):
for sep, fn in self.arcfiles.items():
unlink(fn)
unlink(TESTFN)
unlink(TESTFN2)
class StoredUniversalNewlineTests(AbstractUniversalNewlineTests,
unittest.TestCase):
compression = zipfile.ZIP_STORED
@requires_zlib
class DeflateUniversalNewlineTests(AbstractUniversalNewlineTests,
unittest.TestCase):
compression = zipfile.ZIP_DEFLATED
@requires_bz2
class Bzip2UniversalNewlineTests(AbstractUniversalNewlineTests,
unittest.TestCase):
compression = zipfile.ZIP_BZIP2
@requires_lzma
class LzmaUniversalNewlineTests(AbstractUniversalNewlineTests,
unittest.TestCase):
compression = zipfile.ZIP_LZMA
class ZipInfoTests(unittest.TestCase): class ZipInfoTests(unittest.TestCase):
def test_from_file(self): def test_from_file(self):
zi = zipfile.ZipInfo.from_file(__file__) zi = zipfile.ZipInfo.from_file(__file__)

View file

@ -743,9 +743,6 @@ class ZipExtFile(io.BufferedIOBase):
# Read from compressed files in 4k blocks. # Read from compressed files in 4k blocks.
MIN_READ_SIZE = 4096 MIN_READ_SIZE = 4096
# Search for universal newlines or line chunks.
PATTERN = re.compile(br'^(?P<chunk>[^\r\n]+)|(?P<newline>\n|\r\n?)')
def __init__(self, fileobj, mode, zipinfo, decrypter=None, def __init__(self, fileobj, mode, zipinfo, decrypter=None,
close_fileobj=False): close_fileobj=False):
self._fileobj = fileobj self._fileobj = fileobj
@ -762,7 +759,6 @@ class ZipExtFile(io.BufferedIOBase):
self._readbuffer = b'' self._readbuffer = b''
self._offset = 0 self._offset = 0
self._universal = 'U' in mode
self.newlines = None self.newlines = None
# Adjust read size for encrypted files since the first 12 bytes # Adjust read size for encrypted files since the first 12 bytes
@ -799,7 +795,7 @@ class ZipExtFile(io.BufferedIOBase):
If limit is specified, at most limit bytes will be read. If limit is specified, at most limit bytes will be read.
""" """
if not self._universal and limit < 0: if limit < 0:
# Shortcut common case - newline found in buffer. # Shortcut common case - newline found in buffer.
i = self._readbuffer.find(b'\n', self._offset) + 1 i = self._readbuffer.find(b'\n', self._offset) + 1
if i > 0: if i > 0:
@ -807,41 +803,7 @@ class ZipExtFile(io.BufferedIOBase):
self._offset = i self._offset = i
return line return line
if not self._universal: return io.BufferedIOBase.readline(self, limit)
return io.BufferedIOBase.readline(self, limit)
line = b''
while limit < 0 or len(line) < limit:
readahead = self.peek(2)
if readahead == b'':
return line
#
# Search for universal newlines or line chunks.
#
# The pattern returns either a line chunk or a newline, but not
# both. Combined with peek(2), we are assured that the sequence
# '\r\n' is always retrieved completely and never split into
# separate newlines - '\r', '\n' due to coincidental readaheads.
#
match = self.PATTERN.search(readahead)
newline = match.group('newline')
if newline is not None:
if self.newlines is None:
self.newlines = []
if newline not in self.newlines:
self.newlines.append(newline)
self._offset += len(newline)
return line + b'\n'
chunk = match.group('chunk')
if limit >= 0:
chunk = chunk[: limit - len(line)]
self._offset += len(chunk)
line += chunk
return line
def peek(self, n=1): def peek(self, n=1):
"""Returns buffered bytes without advancing the position.""" """Returns buffered bytes without advancing the position."""
@ -1360,12 +1322,8 @@ class ZipFile:
files. If the size is known in advance, it is best to pass a ZipInfo files. If the size is known in advance, it is best to pass a ZipInfo
instance for name, with zinfo.file_size set. instance for name, with zinfo.file_size set.
""" """
if mode not in {"r", "w", "U", "rU"}: if mode not in {"r", "w"}:
raise RuntimeError('open() requires mode "r", "w", "U", or "rU"') raise RuntimeError('open() requires mode "r" or "w"')
if 'U' in mode:
import warnings
warnings.warn("'U' mode is deprecated",
DeprecationWarning, 2)
if pwd and not isinstance(pwd, bytes): if pwd and not isinstance(pwd, bytes):
raise TypeError("pwd: expected bytes, got %s" % type(pwd)) raise TypeError("pwd: expected bytes, got %s" % type(pwd))
if pwd and (mode == "w"): if pwd and (mode == "w"):

View file

@ -38,6 +38,9 @@ Core and Builtins
Library Library
------- -------
- Issue #27029: Removed deprecated support of universal newlines mode from
ZipFile.open().
- Issue #27030: Unknown escapes consisting of ``'\'`` and ASCII letter in - Issue #27030: Unknown escapes consisting of ``'\'`` and ASCII letter in
regular expressions now are errors. The re.LOCALE flag now can be used regular expressions now are errors. The re.LOCALE flag now can be used
only with bytes patterns. only with bytes patterns.