Issue #21717: The zipfile.ZipFile.open function now supports 'x' (exclusive

creation) mode.
This commit is contained in:
Serhiy Storchaka 2015-03-25 10:09:41 +02:00
parent 489199765f
commit 764fc9bfac
5 changed files with 49 additions and 21 deletions

View File

@ -134,8 +134,11 @@ ZipFile Objects
Open a ZIP file, where *file* can be either a path to a file (a string) or a
file-like object. The *mode* parameter should be ``'r'`` to read an existing
file, ``'w'`` to truncate and write a new file, or ``'a'`` to append to an
existing file. If *mode* is ``'a'`` and *file* refers to an existing ZIP
file, ``'w'`` to truncate and write a new file, ``'x'`` to exclusive create
and write a new file, or ``'a'`` to append to an existing file.
If *mode* is ``'x'`` and *file* refers to an existing file,
a :exc:`FileExistsError` will be raised.
If *mode* is ``'a'`` and *file* refers to an existing ZIP
file, then additional files are added to it. If *file* does not refer to a
ZIP file, then a new ZIP archive is appended to the file. This is meant for
adding a ZIP archive to another file (such as :file:`python.exe`). If
@ -152,7 +155,7 @@ ZipFile Objects
extensions when the zipfile is larger than 2 GiB. If it is false :mod:`zipfile`
will raise an exception when the ZIP file would require ZIP64 extensions.
If the file is created with mode ``'a'`` or ``'w'`` and then
If the file is created with mode ``'w'``, ``'x'`` or ``'a'`` and then
:meth:`closed <close>` without adding any files to the archive, the appropriate
ZIP structures for an empty archive will be written to the file.
@ -174,6 +177,7 @@ ZipFile Objects
.. versionchanged:: 3.5
Added support for writing to unseekable streams.
Added support for the ``'x'`` mode.
.. method:: ZipFile.close()
@ -310,7 +314,8 @@ ZipFile Objects
*arcname* (by default, this will be the same as *filename*, but without a drive
letter and with leading path separators removed). If given, *compress_type*
overrides the value given for the *compression* parameter to the constructor for
the new entry. The archive must be open with mode ``'w'`` or ``'a'`` -- calling
the new entry.
The archive must be open with mode ``'w'``, ``'x'`` or ``'a'`` -- calling
:meth:`write` on a ZipFile created with mode ``'r'`` will raise a
:exc:`RuntimeError`. Calling :meth:`write` on a closed ZipFile will raise a
:exc:`RuntimeError`.
@ -337,10 +342,11 @@ ZipFile Objects
Write the string *bytes* to the archive; *zinfo_or_arcname* is either the file
name it will be given in the archive, or a :class:`ZipInfo` instance. If it's
an instance, at least the filename, date, and time must be given. If it's a
name, the date and time is set to the current date and time. The archive must be
opened with mode ``'w'`` or ``'a'`` -- calling :meth:`writestr` on a ZipFile
created with mode ``'r'`` will raise a :exc:`RuntimeError`. Calling
:meth:`writestr` on a closed ZipFile will raise a :exc:`RuntimeError`.
name, the date and time is set to the current date and time.
The archive must be opened with mode ``'w'``, ``'x'`` or ``'a'`` -- calling
:meth:`writestr` on a ZipFile created with mode ``'r'`` will raise a
:exc:`RuntimeError`. Calling :meth:`writestr` on a closed ZipFile will
raise a :exc:`RuntimeError`.
If given, *compress_type* overrides the value given for the *compression*
parameter to the constructor for the new entry, or in the *zinfo_or_arcname*
@ -368,7 +374,8 @@ The following data attributes are also available:
.. attribute:: ZipFile.comment
The comment text associated with the ZIP file. If assigning a comment to a
:class:`ZipFile` instance created with mode 'a' or 'w', this should be a
:class:`ZipFile` instance created with mode ``'w'``, ``'x'`` or ``'a'``,
this should be a
string no longer than 65535 bytes. Comments longer than this will be
truncated in the written archive when :meth:`close` is called.

View File

@ -454,6 +454,9 @@ zipfile
* Added support for writing ZIP files to unseekable streams.
(Contributed by Serhiy Storchaka in :issue:`23252`.)
* The :func:`zipfile.ZipFile.open` function now supports ``'x'`` (exclusive
creation) mode. (Contributed by Serhiy Storchaka in :issue:`21717`.)
Optimizations
=============

View File

@ -1104,6 +1104,19 @@ class OtherTests(unittest.TestCase):
self.assertEqual(zf.filelist[0].filename, "foo.txt")
self.assertEqual(zf.filelist[1].filename, "\xf6.txt")
def test_exclusive_create_zip_file(self):
"""Test exclusive creating a new zipfile."""
unlink(TESTFN2)
filename = 'testfile.txt'
content = b'hello, world. this is some content.'
with zipfile.ZipFile(TESTFN2, "x", zipfile.ZIP_STORED) as zipfp:
zipfp.writestr(filename, content)
with self.assertRaises(FileExistsError):
zipfile.ZipFile(TESTFN2, "x", zipfile.ZIP_STORED)
with zipfile.ZipFile(TESTFN2, "r") as zipfp:
self.assertEqual(zipfp.namelist(), [filename])
self.assertEqual(zipfp.read(filename), content)
def test_create_non_existent_file_for_append(self):
if os.path.exists(TESTFN):
os.unlink(TESTFN)

View File

@ -962,7 +962,8 @@ class ZipFile:
file: Either the path to the file, or a file-like object.
If it is a path, the file will be opened and closed by ZipFile.
mode: The mode can be either read "r", write "w" or append "a".
mode: The mode can be either read 'r', write 'w', exclusive create 'x',
or append 'a'.
compression: ZIP_STORED (no compression), ZIP_DEFLATED (requires zlib),
ZIP_BZIP2 (requires bz2) or ZIP_LZMA (requires lzma).
allowZip64: if True ZipFile will create files with ZIP64 extensions when
@ -975,9 +976,10 @@ class ZipFile:
_windows_illegal_name_trans_table = None
def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True):
"""Open the ZIP file with mode read "r", write "w" or append "a"."""
if mode not in ("r", "w", "a"):
raise RuntimeError('ZipFile() requires mode "r", "w", or "a"')
"""Open the ZIP file with mode read 'r', write 'w', exclusive create 'x',
or append 'a'."""
if mode not in ('r', 'w', 'x', 'a'):
raise RuntimeError("ZipFile requires mode 'r', 'w', 'x', or 'a'")
_check_compression(compression)
@ -996,8 +998,8 @@ class ZipFile:
# No, it's a filename
self._filePassed = 0
self.filename = file
modeDict = {'r' : 'rb', 'w': 'w+b', 'a' : 'r+b',
'r+b': 'w+b', 'w+b': 'wb'}
modeDict = {'r' : 'rb', 'w': 'w+b', 'x': 'x+b', 'a' : 'r+b',
'r+b': 'w+b', 'w+b': 'wb', 'x+b': 'xb'}
filemode = modeDict[mode]
while True:
try:
@ -1019,7 +1021,7 @@ class ZipFile:
try:
if mode == 'r':
self._RealGetContents()
elif mode == 'w':
elif mode in ('w', 'x'):
# set the modified flag so central directory gets written
# even if no files are added to the archive
self._didModify = True
@ -1050,7 +1052,7 @@ class ZipFile:
self._didModify = True
self.start_dir = self.fp.tell()
else:
raise RuntimeError('Mode must be "r", "w" or "a"')
raise RuntimeError("Mode must be 'r', 'w', 'x', or 'a'")
except:
fp = self.fp
self.fp = None
@ -1400,8 +1402,8 @@ class ZipFile:
if zinfo.filename in self.NameToInfo:
import warnings
warnings.warn('Duplicate name: %r' % zinfo.filename, stacklevel=3)
if self.mode not in ("w", "a"):
raise RuntimeError('write() requires mode "w" or "a"')
if self.mode not in ('w', 'x', 'a'):
raise RuntimeError("write() requires mode 'w', 'x', or 'a'")
if not self.fp:
raise RuntimeError(
"Attempt to write ZIP archive that was already closed")
@ -1588,13 +1590,13 @@ class ZipFile:
self.close()
def close(self):
"""Close the file, and for mode "w" and "a" write the ending
"""Close the file, and for mode 'w', 'x' and 'a' write the ending
records."""
if self.fp is None:
return
try:
if self.mode in ("w", "a") and self._didModify: # write ending records
if self.mode in ('w', 'x', 'a') and self._didModify: # write ending records
with self._lock:
if self._seekable:
self.fp.seek(self.start_dir)

View File

@ -30,6 +30,9 @@ Core and Builtins
Library
-------
- Issue #21717: The zipfile.ZipFile.open function now supports 'x' (exclusive
creation) mode.
- Issue #21802: The reader in BufferedRWPair now is closed even when closing
writer failed in BufferedRWPair.close().