From bd049ab298cfac279d99584c8de824ddd32e951f Mon Sep 17 00:00:00 2001 From: Nadeem Vawda Date: Sat, 19 Oct 2013 00:06:19 +0200 Subject: Issue #19201: Add support for the 'x' mode to the lzma module. Patch by Tim Heaney and Vajrasky Kok. --- Doc/library/lzma.rst | 15 +++++++++++---- Lib/lzma.py | 13 +++++++------ Lib/test/test_lzma.py | 32 +++++++++++++++++++++++++++++--- Misc/ACKS | 1 + Misc/NEWS | 3 +++ 5 files changed, 51 insertions(+), 13 deletions(-) diff --git a/Doc/library/lzma.rst b/Doc/library/lzma.rst index 5fd5039775..b20120637d 100644 --- a/Doc/library/lzma.rst +++ b/Doc/library/lzma.rst @@ -39,8 +39,8 @@ Reading and writing compressed files opened, or it can be an existing 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"``. When opening a file for reading, the *format* and *filters* arguments have the same meanings as for :class:`LZMADecompressor`. In this case, the *check* @@ -57,6 +57,9 @@ Reading and writing compressed files :class:`io.TextIOWrapper` instance with the specified encoding, error handling behavior, and line ending(s). + .. versionchanged:: 3.4 + Added support for the ``"x"``, ``"xb"`` and ``"xt"`` modes. + .. class:: LZMAFile(filename=None, mode="r", \*, format=None, check=-1, preset=None, filters=None) @@ -69,8 +72,9 @@ Reading and writing compressed files file will not be closed when the :class:`LZMAFile` is closed. 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"``. @@ -98,6 +102,9 @@ Reading and writing compressed files byte of data will be returned, unless EOF has been reached. The exact number of bytes returned is unspecified (the *size* argument is ignored). + .. versionchanged:: 3.4 + Added support for the ``"x"`` and ``"xb"`` modes. + Compressing and decompressing data in memory -------------------------------------------- diff --git a/Lib/lzma.py b/Lib/lzma.py index b2e2f7ed0e..011ad271fb 100644 --- a/Lib/lzma.py +++ b/Lib/lzma.py @@ -54,9 +54,9 @@ class LZMAFile(io.BufferedIOBase): bytes object), in which case the named file is opened, or it can be an existing file object to read from or write to. - 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" respectively. + mode can be "r" for reading (default), "w" for (over)writing, + "x" for creating exclusively, or "a" for appending. These can + equivalently be given as "rb", "wb", "xb" and "ab" respectively. format specifies the container format to use for the file. If mode is "r", this defaults to FORMAT_AUTO. Otherwise, the @@ -112,7 +112,7 @@ class LZMAFile(io.BufferedIOBase): self._decompressor = LZMADecompressor(**self._init_args) self._buffer = b"" self._buffer_offset = 0 - elif mode in ("w", "wb", "a", "ab"): + elif mode in ("w", "wb", "a", "ab", "x", "xb"): if format is None: format = FORMAT_XZ mode_code = _MODE_WRITE @@ -426,8 +426,9 @@ def open(filename, mode="rb", *, object), in which case the named file is opened, or it can be an existing file object to read from or write to. - The mode argument can be "r", "rb" (default), "w", "wb", "a" or "ab" - for binary mode, or "rt", "wt" or "at" for text mode. + The mode argument can be "r", "rb" (default), "w", "wb", "x", "xb", + "a", or "ab" for binary mode, or "rt", "wt", "xt", or "at" for text + mode. The format, check, preset and filters arguments specify the compression settings, as for LZMACompressor, LZMADecompressor and diff --git a/Lib/test/test_lzma.py b/Lib/test/test_lzma.py index 20d85828e6..b6af563300 100644 --- a/Lib/test/test_lzma.py +++ b/Lib/test/test_lzma.py @@ -362,6 +362,8 @@ class FileTestCase(unittest.TestCase): pass with LZMAFile(BytesIO(), "w") as f: pass + with LZMAFile(BytesIO(), "x") as f: + pass with LZMAFile(BytesIO(), "a") as f: pass @@ -389,13 +391,29 @@ class FileTestCase(unittest.TestCase): with LZMAFile(TESTFN, "ab"): pass + def test_init_with_x_mode(self): + self.addCleanup(unlink, TESTFN) + for mode in ("x", "xb"): + unlink(TESTFN) + with LZMAFile(TESTFN, mode): + pass + with self.assertRaises(FileExistsError): + with LZMAFile(TESTFN, mode): + pass + def test_init_bad_mode(self): with self.assertRaises(ValueError): LZMAFile(BytesIO(COMPRESSED_XZ), (3, "x")) with self.assertRaises(ValueError): LZMAFile(BytesIO(COMPRESSED_XZ), "") with self.assertRaises(ValueError): - LZMAFile(BytesIO(COMPRESSED_XZ), "x") + LZMAFile(BytesIO(COMPRESSED_XZ), "xt") + with self.assertRaises(ValueError): + LZMAFile(BytesIO(COMPRESSED_XZ), "x+") + with self.assertRaises(ValueError): + LZMAFile(BytesIO(COMPRESSED_XZ), "rx") + with self.assertRaises(ValueError): + LZMAFile(BytesIO(COMPRESSED_XZ), "wx") with self.assertRaises(ValueError): LZMAFile(BytesIO(COMPRESSED_XZ), "rt") with self.assertRaises(ValueError): @@ -1021,8 +1039,6 @@ class OpenTestCase(unittest.TestCase): # Test invalid parameter combinations. with self.assertRaises(ValueError): lzma.open(TESTFN, "") - with self.assertRaises(ValueError): - lzma.open(TESTFN, "x") with self.assertRaises(ValueError): lzma.open(TESTFN, "rbt") with self.assertRaises(ValueError): @@ -1072,6 +1088,16 @@ class OpenTestCase(unittest.TestCase): with lzma.open(bio, "rt", newline="\r") as f: self.assertEqual(f.readlines(), [text]) + def test_x_mode(self): + self.addCleanup(unlink, TESTFN) + for mode in ("x", "xb", "xt"): + unlink(TESTFN) + with lzma.open(TESTFN, mode): + pass + with self.assertRaises(FileExistsError): + with lzma.open(TESTFN, mode): + pass + class MiscellaneousTestCase(unittest.TestCase): diff --git a/Misc/ACKS b/Misc/ACKS index e204bc30df..880e4f0baa 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -506,6 +506,7 @@ Janko Hauser Rycharde Hawkes Ben Hayden Jochen Hayek +Tim Heaney Henrik Heimbuerger Christian Heimes Thomas Heller diff --git a/Misc/NEWS b/Misc/NEWS index a0af8cdd59..4d514f54eb 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -54,6 +54,9 @@ Core and Builtins Library ------- +- Issue #19201: Add "x" mode (exclusive creation) in opening file to lzma + module. Patch by Tim Heaney and Vajrasky Kok. + - Fix a reference count leak in _sre. - Issue #19262: Initial check in of the 'asyncio' package (a.k.a. Tulip, -- cgit v1.2.1