Issue #21717: tarfile.open() now supports 'x' (exclusive creation) mode.
This commit is contained in:
parent
6767757589
commit
0fe6325acf
|
@ -62,6 +62,23 @@ Some facts and figures:
|
|||
+------------------+---------------------------------------------+
|
||||
| ``'r:xz'`` | Open for reading with lzma compression. |
|
||||
+------------------+---------------------------------------------+
|
||||
| ``'x'`` or | Create a tarfile exclusively without |
|
||||
| ``'x:'`` | compression. |
|
||||
| | Raise an :exc:`FileExistsError` exception |
|
||||
| | if it is already exists. |
|
||||
+------------------+---------------------------------------------+
|
||||
| ``'x:gz'`` | Create a tarfile with gzip compression. |
|
||||
| | Raise an :exc:`FileExistsError` exception |
|
||||
| | if it is already exists. |
|
||||
+------------------+---------------------------------------------+
|
||||
| ``'x:bz2'`` | Create a tarfile with bzip2 compression. |
|
||||
| | Raise an :exc:`FileExistsError` exception |
|
||||
| | if it is already exists. |
|
||||
+------------------+---------------------------------------------+
|
||||
| ``'x:xz'`` | Create a tarfile with lzma compression. |
|
||||
| | Raise an :exc:`FileExistsError` exception |
|
||||
| | if it is already exists. |
|
||||
+------------------+---------------------------------------------+
|
||||
| ``'a' or 'a:'`` | Open for appending with no compression. The |
|
||||
| | file is created if it does not exist. |
|
||||
+------------------+---------------------------------------------+
|
||||
|
@ -82,9 +99,9 @@ Some facts and figures:
|
|||
If *fileobj* is specified, it is used as an alternative to a :term:`file object`
|
||||
opened in binary mode for *name*. It is supposed to be at position 0.
|
||||
|
||||
For modes ``'w:gz'``, ``'r:gz'``, ``'w:bz2'``, ``'r:bz2'``, :func:`tarfile.open`
|
||||
accepts the keyword argument *compresslevel* to specify the compression level of
|
||||
the file.
|
||||
For modes ``'w:gz'``, ``'r:gz'``, ``'w:bz2'``, ``'r:bz2'``, ``'x:gz'``,
|
||||
``'x:bz2'``, :func:`tarfile.open` accepts the keyword argument
|
||||
*compresslevel* to specify the compression level of the file.
|
||||
|
||||
For special purposes, there is a second format for *mode*:
|
||||
``'filemode|[compression]'``. :func:`tarfile.open` will return a :class:`TarFile`
|
||||
|
@ -127,6 +144,8 @@ Some facts and figures:
|
|||
| | writing. |
|
||||
+-------------+--------------------------------------------+
|
||||
|
||||
.. versionchanged:: 3.5
|
||||
The ``'x'`` (exclusive creation) mode was added.
|
||||
|
||||
.. class:: TarFile
|
||||
|
||||
|
@ -252,8 +271,8 @@ be finalized; only the internally used file object will be closed. See the
|
|||
In this case, the file object's :attr:`name` attribute is used if it exists.
|
||||
|
||||
*mode* is either ``'r'`` to read from an existing archive, ``'a'`` to append
|
||||
data to an existing file or ``'w'`` to create a new file overwriting an existing
|
||||
one.
|
||||
data to an existing file, ``'w'`` to create a new file overwriting an existing
|
||||
one or ``'x'`` to create a new file only if it's not exists.
|
||||
|
||||
If *fileobj* is given, it is used for reading or writing data. If it can be
|
||||
determined, *mode* is overridden by *fileobj*'s mode. *fileobj* will be used
|
||||
|
@ -292,12 +311,14 @@ be finalized; only the internally used file object will be closed. See the
|
|||
to be handled. The default settings will work for most users.
|
||||
See section :ref:`tar-unicode` for in-depth information.
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
Use ``'surrogateescape'`` as the default for the *errors* argument.
|
||||
|
||||
The *pax_headers* argument is an optional dictionary of strings which
|
||||
will be added as a pax global header if *format* is :const:`PAX_FORMAT`.
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
Use ``'surrogateescape'`` as the default for the *errors* argument.
|
||||
|
||||
.. versionchanged:: 3.5
|
||||
The ``'x'`` (exclusive creation) mode was added.
|
||||
|
||||
.. classmethod:: TarFile.open(...)
|
||||
|
||||
|
|
|
@ -334,6 +334,12 @@ socket
|
|||
:meth:`socket.socket.send`.
|
||||
(Contributed by Giampaolo Rodola' in :issue:`17552`.)
|
||||
|
||||
tarfile
|
||||
-------
|
||||
|
||||
* The :func:`tarfile.open` function now supports ``'x'`` (exclusive creation)
|
||||
mode. (Contributed by Berker Peksag in :issue:`21717`.)
|
||||
|
||||
time
|
||||
----
|
||||
|
||||
|
|
|
@ -1409,9 +1409,9 @@ class TarFile(object):
|
|||
can be determined, `mode' is overridden by `fileobj's mode.
|
||||
`fileobj' is not closed, when TarFile is closed.
|
||||
"""
|
||||
modes = {"r": "rb", "a": "r+b", "w": "wb"}
|
||||
modes = {"r": "rb", "a": "r+b", "w": "wb", "x": "xb"}
|
||||
if mode not in modes:
|
||||
raise ValueError("mode must be 'r', 'a' or 'w'")
|
||||
raise ValueError("mode must be 'r', 'a', 'w' or 'x'")
|
||||
self.mode = mode
|
||||
self._mode = modes[mode]
|
||||
|
||||
|
@ -1524,6 +1524,15 @@ class TarFile(object):
|
|||
'w:bz2' open for writing with bzip2 compression
|
||||
'w:xz' open for writing with lzma compression
|
||||
|
||||
'x' or 'x:' create a tarfile exclusively without compression, raise
|
||||
an exception if the file is already created
|
||||
'x:gz' create an gzip compressed tarfile, raise an exception
|
||||
if the file is already created
|
||||
'x:bz2' create an bzip2 compressed tarfile, raise an exception
|
||||
if the file is already created
|
||||
'x:xz' create an lzma compressed tarfile, raise an exception
|
||||
if the file is already created
|
||||
|
||||
'r|*' open a stream of tar blocks with transparent compression
|
||||
'r|' open an uncompressed stream of tar blocks for reading
|
||||
'r|gz' open a gzip compressed stream of tar blocks
|
||||
|
@ -1582,7 +1591,7 @@ class TarFile(object):
|
|||
t._extfileobj = False
|
||||
return t
|
||||
|
||||
elif mode in ("a", "w"):
|
||||
elif mode in ("a", "w", "x"):
|
||||
return cls.taropen(name, mode, fileobj, **kwargs)
|
||||
|
||||
raise ValueError("undiscernible mode")
|
||||
|
@ -1591,8 +1600,8 @@ class TarFile(object):
|
|||
def taropen(cls, name, mode="r", fileobj=None, **kwargs):
|
||||
"""Open uncompressed tar archive name for reading or writing.
|
||||
"""
|
||||
if mode not in ("r", "a", "w"):
|
||||
raise ValueError("mode must be 'r', 'a' or 'w'")
|
||||
if mode not in ("r", "a", "w", "x"):
|
||||
raise ValueError("mode must be 'r', 'a', 'w' or 'x'")
|
||||
return cls(name, mode, fileobj, **kwargs)
|
||||
|
||||
@classmethod
|
||||
|
@ -1600,8 +1609,8 @@ class TarFile(object):
|
|||
"""Open gzip compressed tar archive name for reading or writing.
|
||||
Appending is not allowed.
|
||||
"""
|
||||
if mode not in ("r", "w"):
|
||||
raise ValueError("mode must be 'r' or 'w'")
|
||||
if mode not in ("r", "w", "x"):
|
||||
raise ValueError("mode must be 'r', 'w' or 'x'")
|
||||
|
||||
try:
|
||||
import gzip
|
||||
|
@ -1634,8 +1643,8 @@ class TarFile(object):
|
|||
"""Open bzip2 compressed tar archive name for reading or writing.
|
||||
Appending is not allowed.
|
||||
"""
|
||||
if mode not in ("r", "w"):
|
||||
raise ValueError("mode must be 'r' or 'w'.")
|
||||
if mode not in ("r", "w", "x"):
|
||||
raise ValueError("mode must be 'r', 'w' or 'x'")
|
||||
|
||||
try:
|
||||
import bz2
|
||||
|
@ -1663,8 +1672,8 @@ class TarFile(object):
|
|||
"""Open lzma compressed tar archive name for reading or writing.
|
||||
Appending is not allowed.
|
||||
"""
|
||||
if mode not in ("r", "w"):
|
||||
raise ValueError("mode must be 'r' or 'w'")
|
||||
if mode not in ("r", "w", "x"):
|
||||
raise ValueError("mode must be 'r', 'w' or 'x'")
|
||||
|
||||
try:
|
||||
import lzma
|
||||
|
@ -1751,7 +1760,7 @@ class TarFile(object):
|
|||
addfile(). If given, `arcname' specifies an alternative name for the
|
||||
file in the archive.
|
||||
"""
|
||||
self._check("aw")
|
||||
self._check("awx")
|
||||
|
||||
# When fileobj is given, replace name by
|
||||
# fileobj's real name.
|
||||
|
@ -1885,7 +1894,7 @@ class TarFile(object):
|
|||
TarInfo object, if it returns None the TarInfo object will be
|
||||
excluded from the archive.
|
||||
"""
|
||||
self._check("aw")
|
||||
self._check("awx")
|
||||
|
||||
if arcname is None:
|
||||
arcname = name
|
||||
|
@ -1942,7 +1951,7 @@ class TarFile(object):
|
|||
On Windows platforms, `fileobj' should always be opened with mode
|
||||
'rb' to avoid irritation about the file size.
|
||||
"""
|
||||
self._check("aw")
|
||||
self._check("awx")
|
||||
|
||||
tarinfo = copy.copy(tarinfo)
|
||||
|
||||
|
|
|
@ -1428,6 +1428,88 @@ class GNUWriteTest(unittest.TestCase):
|
|||
("longlnk/" * 127) + "longlink_")
|
||||
|
||||
|
||||
class CreateTest(TarTest, unittest.TestCase):
|
||||
|
||||
prefix = "x:"
|
||||
|
||||
file_path = os.path.join(TEMPDIR, "spameggs42")
|
||||
|
||||
def setUp(self):
|
||||
support.unlink(tmpname)
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
with open(cls.file_path, "wb") as fobj:
|
||||
fobj.write(b"aaa")
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
support.unlink(cls.file_path)
|
||||
|
||||
def test_create(self):
|
||||
with tarfile.open(tmpname, self.mode) as tobj:
|
||||
tobj.add(self.file_path)
|
||||
|
||||
with self.taropen(tmpname) as tobj:
|
||||
names = tobj.getnames()
|
||||
self.assertEqual(len(names), 1)
|
||||
self.assertIn('spameggs42', names[0])
|
||||
|
||||
def test_create_existing(self):
|
||||
with tarfile.open(tmpname, self.mode) as tobj:
|
||||
tobj.add(self.file_path)
|
||||
|
||||
with self.assertRaises(FileExistsError):
|
||||
tobj = tarfile.open(tmpname, self.mode)
|
||||
|
||||
with self.taropen(tmpname) as tobj:
|
||||
names = tobj.getnames()
|
||||
self.assertEqual(len(names), 1)
|
||||
self.assertIn('spameggs42', names[0])
|
||||
|
||||
def test_create_taropen(self):
|
||||
with self.taropen(tmpname, "x") as tobj:
|
||||
tobj.add(self.file_path)
|
||||
|
||||
with self.taropen(tmpname) as tobj:
|
||||
names = tobj.getnames()
|
||||
self.assertEqual(len(names), 1)
|
||||
self.assertIn('spameggs42', names[0])
|
||||
|
||||
def test_create_existing_taropen(self):
|
||||
with self.taropen(tmpname, "x") as tobj:
|
||||
tobj.add(self.file_path)
|
||||
|
||||
with self.assertRaises(FileExistsError):
|
||||
with self.taropen(tmpname, "x"):
|
||||
pass
|
||||
|
||||
with self.taropen(tmpname) as tobj:
|
||||
names = tobj.getnames()
|
||||
self.assertEqual(len(names), 1)
|
||||
self.assertIn("spameggs42", names[0])
|
||||
|
||||
|
||||
class GzipCreateTest(GzipTest, CreateTest):
|
||||
pass
|
||||
|
||||
|
||||
class Bz2CreateTest(Bz2Test, CreateTest):
|
||||
pass
|
||||
|
||||
|
||||
class LzmaCreateTest(LzmaTest, CreateTest):
|
||||
pass
|
||||
|
||||
|
||||
class CreateWithXModeTest(CreateTest):
|
||||
|
||||
prefix = "x"
|
||||
|
||||
test_create_taropen = None
|
||||
test_create_existing_taropen = None
|
||||
|
||||
|
||||
@unittest.skipUnless(hasattr(os, "link"), "Missing hardlink implementation")
|
||||
class HardlinkTest(unittest.TestCase):
|
||||
# Test the creation of LNKTYPE (hardlink) members in an archive.
|
||||
|
|
Loading…
Reference in New Issue