diff -r e48bd90262b5 Doc/library/zipfile.rst --- a/Doc/library/zipfile.rst Sat Jan 16 23:44:16 2016 -0500 +++ b/Doc/library/zipfile.rst Wed Jan 27 00:01:12 2016 +0000 @@ -465,6 +465,23 @@ :meth:`.infolist` methods of :class:`ZipFile` objects. Each object stores information about a single member of the ZIP archive. +There is one classmethod: + +.. classmethod:: ZipInfo.from_file(filename, arcname=None) + + Construct a :class:`ZipInfo` instance for a file on the filesystem, in + preparation for adding it to a zip file. + + ``filename`` should be the path to a file or directory on the filesystem. + + ``arcname`` is the name which it will have within the archive (by default, + this will be the same as filename, but without a drive letter and with + leading path separators removed). + + .. versionadded:: 3.6 + +to make a :class:`ZipInfo` instance for a filesystem file + Instances have the following attributes: @@ -574,3 +591,10 @@ .. attribute:: ZipInfo.file_size Size of the uncompressed file. + +.. attribute:: ZipInfo.isdir + + ``True`` if the ZipInfo represents a directory, ``False`` otherwise. + Read-only property computed from :attr:`flag_bits`. + + .. versionadded:: 3.6 diff -r e48bd90262b5 Lib/test/test_zipfile.py --- a/Lib/test/test_zipfile.py Sat Jan 16 23:44:16 2016 -0500 +++ b/Lib/test/test_zipfile.py Wed Jan 27 00:01:12 2016 +0000 @@ -2071,5 +2071,21 @@ unittest.TestCase): compression = zipfile.ZIP_LZMA +class ZipInfoTests(unittest.TestCase): + def test_from_file(self): + zi = zipfile.ZipInfo.from_file(__file__) + name = zi.filename.split('/')[-1] + self.assertEqual(name, 'test_zipfile.py') + assert not zi.isdir + + def test_from_dir(self): + dirpath = os.path.dirname(os.path.abspath(__file__)) + zi = zipfile.ZipInfo.from_file(dirpath, 'stdlib_tests') + self.assertEqual(zi.filename, 'stdlib_tests/') + assert zi.isdir + self.assertEqual(zi.compress_type, zipfile.ZIP_STORED) + self.assertEqual(zi.file_size, 0) + self.assertEqual(zi.compress_size, 0) + if __name__ == "__main__": unittest.main() diff -r e48bd90262b5 Lib/zipfile.py --- a/Lib/zipfile.py Sat Jan 16 23:44:16 2016 -0500 +++ b/Lib/zipfile.py Wed Jan 27 00:01:12 2016 +0000 @@ -469,6 +469,46 @@ extra = extra[ln+4:] + @classmethod + def from_file(cls, filename, arcname=None): + """Construct an appropriate ZipInfo for a file on the filesystem. + + filename should be the path to a file or directory on the filesystem. + + arcname is the name which it will have within the archive (by default, + this will be the same as filename, but without a drive letter and with + leading path separators removed). + """ + st = os.stat(filename) + isdir = stat.S_ISDIR(st.st_mode) + mtime = time.localtime(st.st_mtime) + date_time = mtime[0:6] + # Create ZipInfo instance to store file information + if arcname is None: + arcname = filename + arcname = os.path.normpath(os.path.splitdrive(arcname)[1]) + while arcname[0] in (os.sep, os.altsep): + arcname = arcname[1:] + if isdir: + arcname += '/' + zinfo = cls(arcname, date_time) + zinfo.flag_bits = 0x00 + zinfo.external_attr = (st.st_mode & 0xFFFF) << 16 # Unix attributes + if isdir: + zinfo.compress_type = ZIP_STORED + zinfo.file_size = 0 + zinfo.compress_size = 0 + zinfo.CRC = 0 + zinfo.external_attr |= 0x10 # MS-DOS directory flag + else: + zinfo.file_size = st.st_size + + return zinfo + + @property + def isdir(self): + return bool(self.external_attr & 0x10) + class _ZipDecrypter: """Class to handle decryption of files stored within a ZIP archive. @@ -1430,29 +1470,12 @@ raise RuntimeError( "Attempt to write to ZIP archive that was already closed") - st = os.stat(filename) - isdir = stat.S_ISDIR(st.st_mode) - mtime = time.localtime(st.st_mtime) - date_time = mtime[0:6] - # Create ZipInfo instance to store file information - if arcname is None: - arcname = filename - arcname = os.path.normpath(os.path.splitdrive(arcname)[1]) - while arcname[0] in (os.sep, os.altsep): - arcname = arcname[1:] - if isdir: - arcname += '/' - zinfo = ZipInfo(arcname, date_time) - zinfo.external_attr = (st[0] & 0xFFFF) << 16 # Unix attributes - if isdir: - zinfo.compress_type = ZIP_STORED - elif compress_type is None: - zinfo.compress_type = self.compression - else: - zinfo.compress_type = compress_type + zinfo = ZipInfo.from_file(filename, arcname) - zinfo.file_size = st.st_size - zinfo.flag_bits = 0x00 + if not zinfo.isdir: + zinfo.compress_type = compress_type if (compress_type is not None) \ + else self.compression + with self._lock: if self._seekable: self.fp.seek(self.start_dir) @@ -1464,11 +1487,7 @@ self._writecheck(zinfo) self._didModify = True - if isdir: - zinfo.file_size = 0 - zinfo.compress_size = 0 - zinfo.CRC = 0 - zinfo.external_attr |= 0x10 # MS-DOS directory flag + if zinfo.isdir: self.filelist.append(zinfo) self.NameToInfo[zinfo.filename] = zinfo self.fp.write(zinfo.FileHeader(False))