Issue #21717: The zipfile.ZipFile.open function now supports 'x' (exclusive
creation) mode.
This commit is contained in:
parent
489199765f
commit
764fc9bfac
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
=============
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue