From 8a9e99cffcb579c208ccf92454be931e8a26dc39 Mon Sep 17 00:00:00 2001 From: Nadeem Vawda Date: Sat, 19 Oct 2013 00:11:06 +0200 Subject: [PATCH] Issue #19223: Add support for the 'x' mode to the bz2 module. Patch by Tim Heaney and Vajrasky Kok. --- Doc/library/bz2.rst | 15 +++++-- Lib/bz2.py | 16 +++++--- Lib/test/test_bz2.py | 93 +++++++++++++++++++++++++++----------------- Misc/NEWS | 4 +- 4 files changed, 80 insertions(+), 48 deletions(-) diff --git a/Doc/library/bz2.rst b/Doc/library/bz2.rst index d06a39a10d0..44e133100ba 100644 --- a/Doc/library/bz2.rst +++ b/Doc/library/bz2.rst @@ -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. The *mode* argument can be any of ``'r'``, ``'rb'``, ``'w'``, ``'wb'``, - ``'a'``, or ``'ab'`` for binary mode, or ``'rt'``, ``'wt'``, or ``'at'`` for - text mode. The default is ``'rb'``. + ``'x'``, ``'xb'``, ``'a'`` or ``'ab'`` for binary mode, or ``'rt'``, + ``'wt'``, ``'xt'``, or ``'at'`` for text mode. The default is ``'rb'``. The *compresslevel* argument is an integer from 1 to 9, as for the :class:`BZ2File` constructor. @@ -54,6 +54,9 @@ All of the classes in this module may safely be accessed from multiple threads. .. versionadded:: 3.3 + .. versionchanged:: 3.4 + The ``'x'`` (exclusive creation) mode was added. + .. 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. The *mode* argument can be either ``'r'`` for reading (default), ``'w'`` for - overwriting, or ``'a'`` for appending. These can equivalently be given as - ``'rb'``, ``'wb'``, and ``'ab'`` respectively. + overwriting, ``'x'`` for exclusive creation, or ``'a'`` for appending. These + 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 ``'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 multi-stream files. + .. versionchanged:: 3.4 + The ``'x'`` (exclusive creation) mode was added. + Incremental (de)compression --------------------------- diff --git a/Lib/bz2.py b/Lib/bz2.py index 6e6a2b99487..6bc611e71fc 100644 --- a/Lib/bz2.py +++ b/Lib/bz2.py @@ -49,12 +49,12 @@ class BZ2File(io.BufferedIOBase): which will be used to read or write the compressed data. mode can be 'r' for reading (default), 'w' for (over)writing, - or 'a' for appending. These can equivalently be given as 'rb', - 'wb', and 'ab'. + 'x' for creating exclusively, or 'a' for appending. These can + equivalently be given as 'rb', 'wb', 'xb', and 'ab'. 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 compression, and 9 (default) produces the most compression. @@ -87,6 +87,10 @@ class BZ2File(io.BufferedIOBase): mode = "wb" mode_code = _MODE_WRITE self._compressor = BZ2Compressor(compresslevel) + elif mode in ("x", "xb"): + mode = "xb" + mode_code = _MODE_WRITE + self._compressor = BZ2Compressor(compresslevel) elif mode in ("a", "ab"): mode = "ab" 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 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 - binary mode, or "rt", "wt" or "at" for text mode. The default mode - is "rb", and the default compresslevel is 9. + The mode argument can be "r", "rb", "w", "wb", "x", "xb", "a" or + "ab" for binary mode, or "rt", "wt", "xt" or "at" for text mode. + The default mode is "rb", and the default compresslevel is 9. For binary mode, this function is equivalent to the BZ2File constructor: BZ2File(filename, mode, compresslevel). In this case, diff --git a/Lib/test/test_bz2.py b/Lib/test/test_bz2.py index 7090cd6935b..d087cc3ee9d 100644 --- a/Lib/test/test_bz2.py +++ b/Lib/test/test_bz2.py @@ -8,6 +8,7 @@ import os import random import subprocess import sys +from test.support import unlink try: import threading @@ -715,49 +716,67 @@ class OpenTest(BaseTest): return bz2.open(*args, **kwargs) def test_binary_modes(self): - with self.open(self.filename, "wb") as f: - f.write(self.TEXT) - with open(self.filename, "rb") as f: - file_data = self.decompress(f.read()) - self.assertEqual(file_data, self.TEXT) - with self.open(self.filename, "rb") as f: - self.assertEqual(f.read(), self.TEXT) - with self.open(self.filename, "ab") as f: - f.write(self.TEXT) - with open(self.filename, "rb") as f: - file_data = self.decompress(f.read()) - self.assertEqual(file_data, self.TEXT * 2) + for mode in ("wb", "xb"): + if mode == "xb": + unlink(self.filename) + with self.open(self.filename, mode) as f: + f.write(self.TEXT) + with open(self.filename, "rb") as f: + file_data = self.decompress(f.read()) + self.assertEqual(file_data, self.TEXT) + with self.open(self.filename, "rb") as f: + self.assertEqual(f.read(), self.TEXT) + with self.open(self.filename, "ab") as f: + 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): # Test implicit binary modes (no "b" or "t" in mode string). - with self.open(self.filename, "w") as f: - f.write(self.TEXT) - with open(self.filename, "rb") as f: - file_data = self.decompress(f.read()) - self.assertEqual(file_data, self.TEXT) - with self.open(self.filename, "r") as f: - self.assertEqual(f.read(), self.TEXT) - with self.open(self.filename, "a") as f: - f.write(self.TEXT) - with open(self.filename, "rb") as f: - file_data = self.decompress(f.read()) - self.assertEqual(file_data, self.TEXT * 2) + for mode in ("w", "x"): + if mode == "x": + unlink(self.filename) + with self.open(self.filename, mode) as f: + f.write(self.TEXT) + with open(self.filename, "rb") as f: + file_data = self.decompress(f.read()) + self.assertEqual(file_data, self.TEXT) + with self.open(self.filename, "r") as f: + self.assertEqual(f.read(), self.TEXT) + with self.open(self.filename, "a") as f: + 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): text = self.TEXT.decode("ascii") text_native_eol = text.replace("\n", os.linesep) - with self.open(self.filename, "wt") as f: - 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) - with self.open(self.filename, "rt") as f: - self.assertEqual(f.read(), text) - with self.open(self.filename, "at") as f: - 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) + for mode in ("wt", "xt"): + if mode == "xt": + unlink(self.filename) + with self.open(self.filename, mode) as f: + 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) + with self.open(self.filename, "rt") as f: + self.assertEqual(f.read(), text) + with self.open(self.filename, "at") as f: + 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): with self.open(BytesIO(self.DATA), "r") as f: @@ -772,6 +791,8 @@ class OpenTest(BaseTest): # Test invalid parameter combinations. self.assertRaises(ValueError, self.open, self.filename, "wbt") + self.assertRaises(ValueError, + self.open, self.filename, "xbt") self.assertRaises(ValueError, self.open, self.filename, "rb", encoding="utf-8") self.assertRaises(ValueError, diff --git a/Misc/NEWS b/Misc/NEWS index 4d514f54eb8..33ce1840cc3 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -54,8 +54,8 @@ Core and Builtins Library ------- -- Issue #19201: Add "x" mode (exclusive creation) in opening file to lzma - module. Patch by Tim Heaney and Vajrasky Kok. +- Issues #19201, #19223: Add "x" mode (exclusive creation) in opening file to + bz2 and lzma modules. Patches by Tim Heaney and Vajrasky Kok. - Fix a reference count leak in _sre.