summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Ezeh <sam.z.ezeh@gmail.com>2022-04-05 19:41:38 +0100
committerGitHub <noreply@github.com>2022-04-05 11:41:38 -0700
commit050a8f94c678a05d506fe192c863c4a572178c42 (patch)
tree576ea8be15d5683c681a4c258567d814231f9d76
parent9e88b572fb904b172f9e344069fb7118f1cee517 (diff)
downloadcpython-git-050a8f94c678a05d506fe192c863c4a572178c42.tar.gz
bpo-4833: Add ZipFile.mkdir (GH-32160)
-rw-r--r--Doc/library/zipfile.rst11
-rw-r--r--Lib/test/test_zipfile.py53
-rw-r--r--Lib/zipfile.py53
-rw-r--r--Misc/NEWS.d/next/Library/2022-03-28-20-16-37.bpo-4833.2vSUE5.rst1
4 files changed, 101 insertions, 17 deletions
diff --git a/Doc/library/zipfile.rst b/Doc/library/zipfile.rst
index bfcc883de6..d6a1fce49c 100644
--- a/Doc/library/zipfile.rst
+++ b/Doc/library/zipfile.rst
@@ -478,6 +478,17 @@ ZipFile Objects
a closed ZipFile will raise a :exc:`ValueError`. Previously,
a :exc:`RuntimeError` was raised.
+.. method:: ZipFile.mkdir(zinfo_or_directory, mode=511)
+
+ Create a directory inside the archive. If *zinfo_or_directory* is a string,
+ a directory is created inside the archive with the mode that is specified in
+ the *mode* argument. If, however, *zinfo_or_directory* is
+ a :class:`ZipInfo` instance then the *mode* argument is ignored.
+
+ The archive must be opened with mode ``'w'``, ``'x'`` or ``'a'``.
+
+ .. versionadded:: 3.11
+
The following data attributes are also available:
diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile.py
index 26c40457e6..17111b3a40 100644
--- a/Lib/test/test_zipfile.py
+++ b/Lib/test/test_zipfile.py
@@ -2637,6 +2637,59 @@ class TestWithDirectory(unittest.TestCase):
self.assertTrue(os.path.isdir(os.path.join(target, "x")))
self.assertEqual(os.listdir(target), ["x"])
+ def test_mkdir(self):
+ with zipfile.ZipFile(TESTFN, "w") as zf:
+ zf.mkdir("directory")
+ zinfo = zf.filelist[0]
+ self.assertEqual(zinfo.filename, "directory/")
+ self.assertEqual(zinfo.external_attr, (0o40777 << 16) | 0x10)
+
+ zf.mkdir("directory2/")
+ zinfo = zf.filelist[1]
+ self.assertEqual(zinfo.filename, "directory2/")
+ self.assertEqual(zinfo.external_attr, (0o40777 << 16) | 0x10)
+
+ zf.mkdir("directory3", mode=0o777)
+ zinfo = zf.filelist[2]
+ self.assertEqual(zinfo.filename, "directory3/")
+ self.assertEqual(zinfo.external_attr, (0o40777 << 16) | 0x10)
+
+ old_zinfo = zipfile.ZipInfo("directory4/")
+ old_zinfo.external_attr = (0o40777 << 16) | 0x10
+ old_zinfo.CRC = 0
+ old_zinfo.file_size = 0
+ old_zinfo.compress_size = 0
+ zf.mkdir(old_zinfo)
+ new_zinfo = zf.filelist[3]
+ self.assertEqual(old_zinfo.filename, "directory4/")
+ self.assertEqual(old_zinfo.external_attr, new_zinfo.external_attr)
+
+ target = os.path.join(TESTFN2, "target")
+ os.mkdir(target)
+ zf.extractall(target)
+ self.assertEqual(set(os.listdir(target)), {"directory", "directory2", "directory3", "directory4"})
+
+ def test_create_directory_with_write(self):
+ with zipfile.ZipFile(TESTFN, "w") as zf:
+ zf.writestr(zipfile.ZipInfo('directory/'), '')
+
+ zinfo = zf.filelist[0]
+ self.assertEqual(zinfo.filename, "directory/")
+
+ directory = os.path.join(TESTFN2, "directory2")
+ os.mkdir(directory)
+ mode = os.stat(directory).st_mode
+ zf.write(directory, arcname="directory2/")
+ zinfo = zf.filelist[1]
+ self.assertEqual(zinfo.filename, "directory2/")
+ self.assertEqual(zinfo.external_attr, (mode << 16) | 0x10)
+
+ target = os.path.join(TESTFN2, "target")
+ os.mkdir(target)
+ zf.extractall(target)
+
+ self.assertEqual(set(os.listdir(target)), {"directory", "directory2"})
+
def tearDown(self):
rmtree(TESTFN2)
if os.path.exists(TESTFN):
diff --git a/Lib/zipfile.py b/Lib/zipfile.py
index 721834aff1..dc02011084 100644
--- a/Lib/zipfile.py
+++ b/Lib/zipfile.py
@@ -1772,6 +1772,7 @@ class ZipFile:
if zinfo.is_dir():
zinfo.compress_size = 0
zinfo.CRC = 0
+ self.mkdir(zinfo)
else:
if compress_type is not None:
zinfo.compress_type = compress_type
@@ -1783,23 +1784,6 @@ class ZipFile:
else:
zinfo._compresslevel = self.compresslevel
- if zinfo.is_dir():
- with self._lock:
- if self._seekable:
- self.fp.seek(self.start_dir)
- zinfo.header_offset = self.fp.tell() # Start of header bytes
- if zinfo.compress_type == ZIP_LZMA:
- # Compressed data includes an end-of-stream (EOS) marker
- zinfo.flag_bits |= _MASK_COMPRESS_OPTION_1
-
- self._writecheck(zinfo)
- self._didModify = True
-
- self.filelist.append(zinfo)
- self.NameToInfo[zinfo.filename] = zinfo
- self.fp.write(zinfo.FileHeader(False))
- self.start_dir = self.fp.tell()
- else:
with open(filename, "rb") as src, self.open(zinfo, 'w') as dest:
shutil.copyfileobj(src, dest, 1024*8)
@@ -1844,6 +1828,41 @@ class ZipFile:
with self.open(zinfo, mode='w') as dest:
dest.write(data)
+ def mkdir(self, zinfo_or_directory_name, mode=511):
+ """Creates a directory inside the zip archive."""
+ if isinstance(zinfo_or_directory_name, ZipInfo):
+ zinfo = zinfo_or_directory_name
+ if not zinfo.is_dir():
+ raise ValueError("The given ZipInfo does not describe a directory")
+ elif isinstance(zinfo_or_directory_name, str):
+ directory_name = zinfo_or_directory_name
+ if not directory_name.endswith("/"):
+ directory_name += "/"
+ zinfo = ZipInfo(directory_name)
+ zinfo.compress_size = 0
+ zinfo.CRC = 0
+ zinfo.external_attr = ((0o40000 | mode) & 0xFFFF) << 16
+ zinfo.file_size = 0
+ zinfo.external_attr |= 0x10
+ else:
+ raise TypeError("Expected type str or ZipInfo")
+
+ with self._lock:
+ if self._seekable:
+ self.fp.seek(self.start_dir)
+ zinfo.header_offset = self.fp.tell() # Start of header bytes
+ if zinfo.compress_type == ZIP_LZMA:
+ # Compressed data includes an end-of-stream (EOS) marker
+ zinfo.flag_bits |= _MASK_COMPRESS_OPTION_1
+
+ self._writecheck(zinfo)
+ self._didModify = True
+
+ self.filelist.append(zinfo)
+ self.NameToInfo[zinfo.filename] = zinfo
+ self.fp.write(zinfo.FileHeader(False))
+ self.start_dir = self.fp.tell()
+
def __del__(self):
"""Call the "close()" method in case the user forgot."""
self.close()
diff --git a/Misc/NEWS.d/next/Library/2022-03-28-20-16-37.bpo-4833.2vSUE5.rst b/Misc/NEWS.d/next/Library/2022-03-28-20-16-37.bpo-4833.2vSUE5.rst
new file mode 100644
index 0000000000..7696091221
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-03-28-20-16-37.bpo-4833.2vSUE5.rst
@@ -0,0 +1 @@
+Add :meth:`ZipFile.mkdir`