Issue #19223: Add support for the 'x' mode to the bz2 module.

Patch by Tim Heaney and Vajrasky Kok.
This commit is contained in:
Nadeem Vawda 2013-10-19 00:11:06 +02:00
parent 42ca98217c
commit 8a9e99cffc
4 changed files with 80 additions and 48 deletions

View File

@ -37,8 +37,8 @@ All of the classes in this module may safely be accessed from multiple threads.
file object to read from or write to. file object to read from or write to.
The *mode* argument can be any of ``'r'``, ``'rb'``, ``'w'``, ``'wb'``, The *mode* argument can be any of ``'r'``, ``'rb'``, ``'w'``, ``'wb'``,
``'a'``, or ``'ab'`` for binary mode, or ``'rt'``, ``'wt'``, or ``'at'`` for ``'x'``, ``'xb'``, ``'a'`` or ``'ab'`` for binary mode, or ``'rt'``,
text mode. The default is ``'rb'``. ``'wt'``, ``'xt'``, or ``'at'`` for text mode. The default is ``'rb'``.
The *compresslevel* argument is an integer from 1 to 9, as for the The *compresslevel* argument is an integer from 1 to 9, as for the
:class:`BZ2File` constructor. :class:`BZ2File` constructor.
@ -54,6 +54,9 @@ All of the classes in this module may safely be accessed from multiple threads.
.. versionadded:: 3.3 .. versionadded:: 3.3
.. versionchanged:: 3.4
The ``'x'`` (exclusive creation) mode was added.
.. class:: BZ2File(filename, mode='r', buffering=None, compresslevel=9) .. class:: BZ2File(filename, mode='r', buffering=None, compresslevel=9)
@ -64,8 +67,9 @@ All of the classes in this module may safely be accessed from multiple threads.
be used to read or write the compressed data. be used to read or write the compressed data.
The *mode* argument can be either ``'r'`` for reading (default), ``'w'`` for The *mode* argument can be either ``'r'`` for reading (default), ``'w'`` for
overwriting, or ``'a'`` for appending. These can equivalently be given as overwriting, ``'x'`` for exclusive creation, or ``'a'`` for appending. These
``'rb'``, ``'wb'``, and ``'ab'`` respectively. can equivalently be given as ``'rb'``, ``'wb'``, ``'xb'`` and ``'ab'``
respectively.
If *filename* is a file object (rather than an actual file name), a mode of If *filename* is a file object (rather than an actual file name), a mode of
``'w'`` does not truncate the file, and is instead equivalent to ``'a'``. ``'w'`` does not truncate the file, and is instead equivalent to ``'a'``.
@ -108,6 +112,9 @@ All of the classes in this module may safely be accessed from multiple threads.
The ``'a'`` (append) mode was added, along with support for reading The ``'a'`` (append) mode was added, along with support for reading
multi-stream files. multi-stream files.
.. versionchanged:: 3.4
The ``'x'`` (exclusive creation) mode was added.
Incremental (de)compression Incremental (de)compression
--------------------------- ---------------------------

View File

@ -49,12 +49,12 @@ class BZ2File(io.BufferedIOBase):
which will be used to read or write the compressed data. which will be used to read or write the compressed data.
mode can be 'r' for reading (default), 'w' for (over)writing, mode can be 'r' for reading (default), 'w' for (over)writing,
or 'a' for appending. These can equivalently be given as 'rb', 'x' for creating exclusively, or 'a' for appending. These can
'wb', and 'ab'. equivalently be given as 'rb', 'wb', 'xb', and 'ab'.
buffering is ignored. Its use is deprecated. buffering is ignored. Its use is deprecated.
If mode is 'w' or 'a', compresslevel can be a number between 1 If mode is 'w', 'x' or 'a', compresslevel can be a number between 1
and 9 specifying the level of compression: 1 produces the least and 9 specifying the level of compression: 1 produces the least
compression, and 9 (default) produces the most compression. compression, and 9 (default) produces the most compression.
@ -87,6 +87,10 @@ class BZ2File(io.BufferedIOBase):
mode = "wb" mode = "wb"
mode_code = _MODE_WRITE mode_code = _MODE_WRITE
self._compressor = BZ2Compressor(compresslevel) self._compressor = BZ2Compressor(compresslevel)
elif mode in ("x", "xb"):
mode = "xb"
mode_code = _MODE_WRITE
self._compressor = BZ2Compressor(compresslevel)
elif mode in ("a", "ab"): elif mode in ("a", "ab"):
mode = "ab" mode = "ab"
mode_code = _MODE_WRITE mode_code = _MODE_WRITE
@ -443,9 +447,9 @@ def open(filename, mode="rb", compresslevel=9,
The filename argument can be an actual filename (a str or bytes The filename argument can be an actual filename (a str or bytes
object), or an existing file object to read from or write to. object), or an existing file object to read from or write to.
The mode argument can be "r", "rb", "w", "wb", "a" or "ab" for The mode argument can be "r", "rb", "w", "wb", "x", "xb", "a" or
binary mode, or "rt", "wt" or "at" for text mode. The default mode "ab" for binary mode, or "rt", "wt", "xt" or "at" for text mode.
is "rb", and the default compresslevel is 9. The default mode is "rb", and the default compresslevel is 9.
For binary mode, this function is equivalent to the BZ2File For binary mode, this function is equivalent to the BZ2File
constructor: BZ2File(filename, mode, compresslevel). In this case, constructor: BZ2File(filename, mode, compresslevel). In this case,

View File

@ -8,6 +8,7 @@ import os
import random import random
import subprocess import subprocess
import sys import sys
from test.support import unlink
try: try:
import threading import threading
@ -715,49 +716,67 @@ class OpenTest(BaseTest):
return bz2.open(*args, **kwargs) return bz2.open(*args, **kwargs)
def test_binary_modes(self): def test_binary_modes(self):
with self.open(self.filename, "wb") as f: for mode in ("wb", "xb"):
f.write(self.TEXT) if mode == "xb":
with open(self.filename, "rb") as f: unlink(self.filename)
file_data = self.decompress(f.read()) with self.open(self.filename, mode) as f:
self.assertEqual(file_data, self.TEXT) f.write(self.TEXT)
with self.open(self.filename, "rb") as f: with open(self.filename, "rb") as f:
self.assertEqual(f.read(), self.TEXT) file_data = self.decompress(f.read())
with self.open(self.filename, "ab") as f: self.assertEqual(file_data, self.TEXT)
f.write(self.TEXT) with self.open(self.filename, "rb") as f:
with open(self.filename, "rb") as f: self.assertEqual(f.read(), self.TEXT)
file_data = self.decompress(f.read()) with self.open(self.filename, "ab") as f:
self.assertEqual(file_data, self.TEXT * 2) f.write(self.TEXT)
with open(self.filename, "rb") as f:
file_data = self.decompress(f.read())
self.assertEqual(file_data, self.TEXT * 2)
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).
with self.open(self.filename, "w") as f: for mode in ("w", "x"):
f.write(self.TEXT) if mode == "x":
with open(self.filename, "rb") as f: unlink(self.filename)
file_data = self.decompress(f.read()) with self.open(self.filename, mode) as f:
self.assertEqual(file_data, self.TEXT) f.write(self.TEXT)
with self.open(self.filename, "r") as f: with open(self.filename, "rb") as f:
self.assertEqual(f.read(), self.TEXT) file_data = self.decompress(f.read())
with self.open(self.filename, "a") as f: self.assertEqual(file_data, self.TEXT)
f.write(self.TEXT) with self.open(self.filename, "r") as f:
with open(self.filename, "rb") as f: self.assertEqual(f.read(), self.TEXT)
file_data = self.decompress(f.read()) with self.open(self.filename, "a") as f:
self.assertEqual(file_data, self.TEXT * 2) f.write(self.TEXT)
with open(self.filename, "rb") as f:
file_data = self.decompress(f.read())
self.assertEqual(file_data, self.TEXT * 2)
def test_text_modes(self): def test_text_modes(self):
text = self.TEXT.decode("ascii") text = self.TEXT.decode("ascii")
text_native_eol = text.replace("\n", os.linesep) text_native_eol = text.replace("\n", os.linesep)
with self.open(self.filename, "wt") as f: for mode in ("wt", "xt"):
f.write(text) if mode == "xt":
with open(self.filename, "rb") as f: unlink(self.filename)
file_data = self.decompress(f.read()).decode("ascii") with self.open(self.filename, mode) as f:
self.assertEqual(file_data, text_native_eol) f.write(text)
with self.open(self.filename, "rt") as f: with open(self.filename, "rb") as f:
self.assertEqual(f.read(), text) file_data = self.decompress(f.read()).decode("ascii")
with self.open(self.filename, "at") as f: self.assertEqual(file_data, text_native_eol)
f.write(text) with self.open(self.filename, "rt") as f:
with open(self.filename, "rb") as f: self.assertEqual(f.read(), text)
file_data = self.decompress(f.read()).decode("ascii") with self.open(self.filename, "at") as f:
self.assertEqual(file_data, text_native_eol * 2) f.write(text)
with open(self.filename, "rb") as f:
file_data = self.decompress(f.read()).decode("ascii")
self.assertEqual(file_data, text_native_eol * 2)
def test_x_mode(self):
for mode in ("x", "xb", "xt"):
unlink(self.filename)
with self.open(self.filename, mode) as f:
pass
with self.assertRaises(FileExistsError):
with self.open(self.filename, mode) as f:
pass
def test_fileobj(self): def test_fileobj(self):
with self.open(BytesIO(self.DATA), "r") as f: with self.open(BytesIO(self.DATA), "r") as f:
@ -772,6 +791,8 @@ class OpenTest(BaseTest):
# Test invalid parameter combinations. # Test invalid parameter combinations.
self.assertRaises(ValueError, self.assertRaises(ValueError,
self.open, self.filename, "wbt") self.open, self.filename, "wbt")
self.assertRaises(ValueError,
self.open, self.filename, "xbt")
self.assertRaises(ValueError, self.assertRaises(ValueError,
self.open, self.filename, "rb", encoding="utf-8") self.open, self.filename, "rb", encoding="utf-8")
self.assertRaises(ValueError, self.assertRaises(ValueError,

View File

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