Issue #19222: Add support for the 'x' mode to the gzip module.

Original patch by Tim Heaney.
This commit is contained in:
Nadeem Vawda 2013-10-19 00:11:13 +02:00
parent 8a9e99cffc
commit ee1be99e05
4 changed files with 57 additions and 13 deletions

View File

@ -35,8 +35,8 @@ The module defines the following items:
:class:`bytes` object), or an existing file object to read from or write to. :class:`bytes` object), or an existing file object to read from or write to.
The *mode* argument can be any of ``'r'``, ``'rb'``, ``'a'``, ``'ab'``, The *mode* argument can be any of ``'r'``, ``'rb'``, ``'a'``, ``'ab'``,
``'w'``, or ``'wb'`` for binary mode, or ``'rt'``, ``'at'``, or ``'wt'`` for ``'w'``, ``'wb'``, ``'x'`` or ``'xb'`` for binary mode, or ``'rt'``,
text mode. The default is ``'rb'``. ``'at'``, ``'wt'``, or ``'xt'`` for text mode. The default is ``'rb'``.
The *compresslevel* argument is an integer from 0 to 9, as for the The *compresslevel* argument is an integer from 0 to 9, as for the
:class:`GzipFile` constructor. :class:`GzipFile` constructor.
@ -53,6 +53,9 @@ The module defines the following items:
Added support for *filename* being a file object, support for text mode, Added support for *filename* being a file object, support for text mode,
and the *encoding*, *errors* and *newline* arguments. and the *encoding*, *errors* and *newline* arguments.
.. versionchanged:: 3.4
Added support for the ``'x'``, ``'xb'`` and ``'xt'`` modes.
.. class:: GzipFile(filename=None, mode=None, compresslevel=9, fileobj=None, mtime=None) .. class:: GzipFile(filename=None, mode=None, compresslevel=9, fileobj=None, mtime=None)
@ -73,8 +76,9 @@ The module defines the following items:
original filename is not included in the header. original filename is not included in the header.
The *mode* argument can be any of ``'r'``, ``'rb'``, ``'a'``, ``'ab'``, ``'w'``, The *mode* argument can be any of ``'r'``, ``'rb'``, ``'a'``, ``'ab'``, ``'w'``,
or ``'wb'``, depending on whether the file will be read or written. The default ``'wb'``, ``'x'``, or ``'xb'``, depending on whether the file will be read or
is the mode of *fileobj* if discernible; otherwise, the default is ``'rb'``. written. The default is the mode of *fileobj* if discernible; otherwise, the
default is ``'rb'``.
Note that the file is always opened in binary mode. To open a compressed file Note that the file is always opened in binary mode. To open a compressed file
in text mode, use :func:`.open` (or wrap your :class:`GzipFile` with an in text mode, use :func:`.open` (or wrap your :class:`GzipFile` with an
@ -125,6 +129,9 @@ The module defines the following items:
.. versionchanged:: 3.3 .. versionchanged:: 3.3
The :meth:`io.BufferedIOBase.read1` method is now implemented. The :meth:`io.BufferedIOBase.read1` method is now implemented.
.. versionchanged:: 3.4
Added support for the ``'x'`` and ``'xb'`` modes.
.. function:: compress(data, compresslevel=9) .. function:: compress(data, compresslevel=9)

View File

@ -23,9 +23,9 @@ def open(filename, mode="rb", compresslevel=9,
The filename argument can be an actual filename (a str or bytes object), or The filename argument can be an actual filename (a str or bytes object), or
an existing file object to read from or write to. an existing file object to read from or write to.
The mode argument can be "r", "rb", "w", "wb", "a" or "ab" for binary mode, The mode argument can be "r", "rb", "w", "wb", "x", "xb", "a" or "ab" for
or "rt", "wt" or "at" for text mode. The default mode is "rb", and the binary mode, or "rt", "wt", "xt" or "at" for text mode. The default mode is
default compresslevel is 9. "rb", and the default compresslevel is 9.
For binary mode, this function is equivalent to the GzipFile constructor: For binary mode, this function is equivalent to the GzipFile constructor:
GzipFile(filename, mode, compresslevel). In this case, the encoding, errors GzipFile(filename, mode, compresslevel). In this case, the encoding, errors
@ -151,11 +151,11 @@ class GzipFile(io.BufferedIOBase):
fileobj, if discernible; otherwise, it defaults to the empty string, fileobj, if discernible; otherwise, it defaults to the empty string,
and in this case the original filename is not included in the header. and in this case the original filename is not included in the header.
The mode argument can be any of 'r', 'rb', 'a', 'ab', 'w', or 'wb', The mode argument can be any of 'r', 'rb', 'a', 'ab', 'w', 'wb', 'x', or
depending on whether the file will be read or written. The default 'xb' depending on whether the file will be read or written. The default
is the mode of fileobj if discernible; otherwise, the default is 'rb'. is the mode of fileobj if discernible; otherwise, the default is 'rb'.
A mode of 'r' is equivalent to one of 'rb', and similarly for 'w' and A mode of 'r' is equivalent to one of 'rb', and similarly for 'w' and
'wb', and 'a' and 'ab'. 'wb', 'a' and 'ab', and 'x' and 'xb'.
The compresslevel argument is an integer from 0 to 9 controlling the The compresslevel argument is an integer from 0 to 9 controlling the
level of compression; 1 is fastest and produces the least compression, level of compression; 1 is fastest and produces the least compression,
@ -201,7 +201,7 @@ class GzipFile(io.BufferedIOBase):
self.min_readsize = 100 self.min_readsize = 100
fileobj = _PaddedFile(fileobj) fileobj = _PaddedFile(fileobj)
elif mode.startswith(('w', 'a')): elif mode.startswith(('w', 'a', 'x')):
self.mode = WRITE self.mode = WRITE
self._init_write(filename) self._init_write(filename)
self.compress = zlib.compressobj(compresslevel, self.compress = zlib.compressobj(compresslevel,

View File

@ -131,6 +131,14 @@ class TestGzip(BaseTest):
if not ztxt: break if not ztxt: break
self.assertEqual(contents, b'a'*201) self.assertEqual(contents, b'a'*201)
def test_exclusive_write(self):
with gzip.GzipFile(self.filename, 'xb') as f:
f.write(data1 * 50)
with gzip.GzipFile(self.filename, 'rb') as f:
self.assertEqual(f.read(), data1 * 50)
with self.assertRaises(FileExistsError):
gzip.GzipFile(self.filename, 'xb')
def test_buffered_reader(self): def test_buffered_reader(self):
# Issue #7471: a GzipFile can be wrapped in a BufferedReader for # Issue #7471: a GzipFile can be wrapped in a BufferedReader for
# performance. # performance.
@ -206,6 +214,9 @@ class TestGzip(BaseTest):
self.test_write() self.test_write()
with gzip.GzipFile(self.filename, 'r') as f: with gzip.GzipFile(self.filename, 'r') as f:
self.assertEqual(f.myfileobj.mode, 'rb') self.assertEqual(f.myfileobj.mode, 'rb')
support.unlink(self.filename)
with gzip.GzipFile(self.filename, 'x') as f:
self.assertEqual(f.myfileobj.mode, 'xb')
def test_1647484(self): def test_1647484(self):
for mode in ('wb', 'rb'): for mode in ('wb', 'rb'):
@ -414,35 +425,59 @@ class TestGzip(BaseTest):
class TestOpen(BaseTest): class TestOpen(BaseTest):
def test_binary_modes(self): def test_binary_modes(self):
uncompressed = data1 * 50 uncompressed = data1 * 50
with gzip.open(self.filename, "wb") as f: with gzip.open(self.filename, "wb") as f:
f.write(uncompressed) f.write(uncompressed)
with open(self.filename, "rb") as f: with open(self.filename, "rb") as f:
file_data = gzip.decompress(f.read()) file_data = gzip.decompress(f.read())
self.assertEqual(file_data, uncompressed) self.assertEqual(file_data, uncompressed)
with gzip.open(self.filename, "rb") as f: with gzip.open(self.filename, "rb") as f:
self.assertEqual(f.read(), uncompressed) self.assertEqual(f.read(), uncompressed)
with gzip.open(self.filename, "ab") as f: with gzip.open(self.filename, "ab") as f:
f.write(uncompressed) f.write(uncompressed)
with open(self.filename, "rb") as f: with open(self.filename, "rb") as f:
file_data = gzip.decompress(f.read()) file_data = gzip.decompress(f.read())
self.assertEqual(file_data, uncompressed * 2) self.assertEqual(file_data, uncompressed * 2)
with self.assertRaises(FileExistsError):
gzip.open(self.filename, "xb")
support.unlink(self.filename)
with gzip.open(self.filename, "xb") as f:
f.write(uncompressed)
with open(self.filename, "rb") as f:
file_data = gzip.decompress(f.read())
self.assertEqual(file_data, uncompressed)
def test_implicit_binary_modes(self): def test_implicit_binary_modes(self):
# Test implicit binary modes (no "b" or "t" in mode string). # Test implicit binary modes (no "b" or "t" in mode string).
uncompressed = data1 * 50 uncompressed = data1 * 50
with gzip.open(self.filename, "w") as f: with gzip.open(self.filename, "w") as f:
f.write(uncompressed) f.write(uncompressed)
with open(self.filename, "rb") as f: with open(self.filename, "rb") as f:
file_data = gzip.decompress(f.read()) file_data = gzip.decompress(f.read())
self.assertEqual(file_data, uncompressed) self.assertEqual(file_data, uncompressed)
with gzip.open(self.filename, "r") as f: with gzip.open(self.filename, "r") as f:
self.assertEqual(f.read(), uncompressed) self.assertEqual(f.read(), uncompressed)
with gzip.open(self.filename, "a") as f: with gzip.open(self.filename, "a") as f:
f.write(uncompressed) f.write(uncompressed)
with open(self.filename, "rb") as f: with open(self.filename, "rb") as f:
file_data = gzip.decompress(f.read()) file_data = gzip.decompress(f.read())
self.assertEqual(file_data, uncompressed * 2) self.assertEqual(file_data, uncompressed * 2)
with self.assertRaises(FileExistsError):
gzip.open(self.filename, "x")
support.unlink(self.filename)
with gzip.open(self.filename, "x") as f:
f.write(uncompressed)
with open(self.filename, "rb") as f:
file_data = gzip.decompress(f.read())
self.assertEqual(file_data, uncompressed)
def test_text_modes(self): def test_text_modes(self):
uncompressed = data1.decode("ascii") * 50 uncompressed = data1.decode("ascii") * 50
uncompressed_raw = uncompressed.replace("\n", os.linesep) uncompressed_raw = uncompressed.replace("\n", os.linesep)
@ -476,6 +511,8 @@ class TestOpen(BaseTest):
gzip.open(123.456) gzip.open(123.456)
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
gzip.open(self.filename, "wbt") gzip.open(self.filename, "wbt")
with self.assertRaises(ValueError):
gzip.open(self.filename, "xbt")
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
gzip.open(self.filename, "rb", encoding="utf-8") gzip.open(self.filename, "rb", encoding="utf-8")
with self.assertRaises(ValueError): with self.assertRaises(ValueError):

View File

@ -54,8 +54,8 @@ Core and Builtins
Library Library
------- -------
- Issues #19201, #19223: Add "x" mode (exclusive creation) in opening file to - Issues #19201, #19222, #19223: Add "x" mode (exclusive creation) in opening
bz2 and lzma modules. Patches by Tim Heaney and Vajrasky Kok. file to bz2, gzip and lzma modules. Patches by Tim Heaney and Vajrasky Kok.
- Fix a reference count leak in _sre. - Fix a reference count leak in _sre.