summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSerhiy Storchaka <storchaka@gmail.com>2015-03-25 08:09:41 (GMT)
committerSerhiy Storchaka <storchaka@gmail.com>2015-03-25 08:09:41 (GMT)
commit764fc9bfac84d564c526628974a9bd5edfa5bcab (patch)
tree84a9d8736869ad41ca0c5e17550fb14cb9afba34
parent489199765ff313d0a7dad70b8fbf416667c43862 (diff)
downloadcpython-764fc9bfac84d564c526628974a9bd5edfa5bcab.zip
cpython-764fc9bfac84d564c526628974a9bd5edfa5bcab.tar.gz
cpython-764fc9bfac84d564c526628974a9bd5edfa5bcab.tar.bz2
Issue #21717: The zipfile.ZipFile.open function now supports 'x' (exclusive
creation) mode.
-rw-r--r--Doc/library/zipfile.rst25
-rw-r--r--Doc/whatsnew/3.5.rst3
-rw-r--r--Lib/test/test_zipfile.py13
-rw-r--r--Lib/zipfile.py26
-rw-r--r--Misc/NEWS3
5 files changed, 49 insertions, 21 deletions
diff --git a/Doc/library/zipfile.rst b/Doc/library/zipfile.rst
index c1dda25..a15c461 100644
--- a/Doc/library/zipfile.rst
+++ b/Doc/library/zipfile.rst
@@ -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.
diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst
index aa5af04..0996350 100644
--- a/Doc/whatsnew/3.5.rst
+++ b/Doc/whatsnew/3.5.rst
@@ -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
=============
diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile.py
index 4cd5fe3..1b2dc85 100644
--- a/Lib/test/test_zipfile.py
+++ b/Lib/test/test_zipfile.py
@@ -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)
diff --git a/Lib/zipfile.py b/Lib/zipfile.py
index 55afa08..d545c55 100644
--- a/Lib/zipfile.py
+++ b/Lib/zipfile.py
@@ -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)
diff --git a/Misc/NEWS b/Misc/NEWS
index b85eeaf..6534443 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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().