diff options
author | Serhiy Storchaka <storchaka@gmail.com> | 2016-02-07 22:02:25 (GMT) |
---|---|---|
committer | Serhiy Storchaka <storchaka@gmail.com> | 2016-02-07 22:02:25 (GMT) |
commit | 503f908090bd22df70ed21e3bb3c2513a1eee969 (patch) | |
tree | 2722994f3caf3551cf4cfe9556df3f27b4d58e78 | |
parent | 46988d365946f2b61812b6ff60cb557e036fa6b1 (diff) | |
download | cpython-503f908090bd22df70ed21e3bb3c2513a1eee969.zip cpython-503f908090bd22df70ed21e3bb3c2513a1eee969.tar.gz cpython-503f908090bd22df70ed21e3bb3c2513a1eee969.tar.bz2 |
Issue #26039: Added zipfile.ZipInfo.from_file() and zipinfo.ZipInfo.is_dir().
Patch by Thomas Kluyver.
-rw-r--r-- | Doc/library/zipfile.rst | 24 | ||||
-rw-r--r-- | Doc/whatsnew/3.6.rst | 10 | ||||
-rw-r--r-- | Lib/test/test_zipfile.py | 15 | ||||
-rw-r--r-- | Lib/zipfile.py | 75 | ||||
-rw-r--r-- | Misc/NEWS | 3 |
5 files changed, 99 insertions, 28 deletions
diff --git a/Doc/library/zipfile.rst b/Doc/library/zipfile.rst index e3c2217..54b8cc5 100644 --- a/Doc/library/zipfile.rst +++ b/Doc/library/zipfile.rst @@ -465,6 +465,22 @@ Instances of the :class:`ZipInfo` class are returned by the :meth:`.getinfo` and :meth:`.infolist` methods of :class:`ZipFile` objects. Each object stores information about a single member of the ZIP archive. +There is one classmethod to make a :class:`ZipInfo` instance for a filesystem +file: + +.. 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. + + If *arcname* is specified, it is used as the name within the archive. + If *arcname* is not specified, the name will be the same as *filename*, but + with any drive letter and leading path separators removed. + + .. versionadded:: 3.6 + Instances have the following attributes: @@ -574,3 +590,11 @@ Instances have the following attributes: .. attribute:: ZipInfo.file_size Size of the uncompressed file. + +There is one method: + +.. method:: ZipInfo.is_dir() + + Return ``True`` if the ZipInfo represents a directory. + + .. versionadded:: 3.6 diff --git a/Doc/whatsnew/3.6.rst b/Doc/whatsnew/3.6.rst index 158dad3..b7cc159 100644 --- a/Doc/whatsnew/3.6.rst +++ b/Doc/whatsnew/3.6.rst @@ -140,6 +140,16 @@ urllib.robotparser (Contributed by Nikolay Bogoychev in :issue:`16099`.) +zipfile +------- + +A new :meth:`ZipInfo.from_file() <zipfile.ZipInfo.from_file>` class method +allow to make :class:`~zipfile.ZipInfo` instance from a filesystem file. +A new :meth:`ZipInfo.is_dir() <zipfile.ZipInfo.is_dir>` method can be used +to check if the :class:`~zipfile.ZipInfo` instance represents a directory. +(Contributed by Thomas Kluyver in :issue:`26039`.) + + Optimizations ============= diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile.py index 2c10821..8589342 100644 --- a/Lib/test/test_zipfile.py +++ b/Lib/test/test_zipfile.py @@ -3,6 +3,7 @@ import io import os import sys import importlib.util +import posixpath import time import struct import zipfile @@ -2071,5 +2072,19 @@ class LzmaUniversalNewlineTests(AbstractUniversalNewlineTests, unittest.TestCase): compression = zipfile.ZIP_LZMA +class ZipInfoTests(unittest.TestCase): + def test_from_file(self): + zi = zipfile.ZipInfo.from_file(__file__) + self.assertEqual(posixpath.basename(zi.filename), 'test_zipfile.py') + self.assertFalse(zi.is_dir()) + + 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/') + self.assertTrue(zi.is_dir()) + self.assertEqual(zi.compress_type, zipfile.ZIP_STORED) + self.assertEqual(zi.file_size, 0) + if __name__ == "__main__": unittest.main() diff --git a/Lib/zipfile.py b/Lib/zipfile.py index 56a2479..e0598d2 100644 --- a/Lib/zipfile.py +++ b/Lib/zipfile.py @@ -371,7 +371,7 @@ class ZipInfo (object): result.append(' filemode=%r' % stat.filemode(hi)) if lo: result.append(' external_attr=%#x' % lo) - isdir = self.filename[-1:] == '/' + isdir = self.is_dir() if not isdir or self.file_size: result.append(' file_size=%r' % self.file_size) if ((not isdir or self.compress_size) and @@ -469,6 +469,41 @@ class ZipInfo (object): 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.external_attr = (st.st_mode & 0xFFFF) << 16 # Unix attributes + if isdir: + zinfo.file_size = 0 + zinfo.external_attr |= 0x10 # MS-DOS directory flag + else: + zinfo.file_size = st.st_size + + return zinfo + + def is_dir(self): + return self.filename[-1] == '/' + class _ZipDecrypter: """Class to handle decryption of files stored within a ZIP archive. @@ -1389,7 +1424,7 @@ class ZipFile: if upperdirs and not os.path.exists(upperdirs): os.makedirs(upperdirs) - if member.filename[-1] == '/': + if member.is_dir(): if not os.path.isdir(targetpath): os.mkdir(targetpath) return targetpath @@ -1430,29 +1465,17 @@ class ZipFile: 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 + zinfo = ZipInfo.from_file(filename, arcname) + + if zinfo.is_dir(): + zinfo.compress_size = 0 + zinfo.CRC = 0 else: - zinfo.compress_type = compress_type + if compress_type is not None: + zinfo.compress_type = compress_type + else: + zinfo.compress_type = self.compression - zinfo.file_size = st.st_size - zinfo.flag_bits = 0x00 with self._lock: if self._seekable: self.fp.seek(self.start_dir) @@ -1464,11 +1487,7 @@ class ZipFile: 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.is_dir(): self.filelist.append(zinfo) self.NameToInfo[zinfo.filename] = zinfo self.fp.write(zinfo.FileHeader(False)) @@ -170,6 +170,9 @@ Core and Builtins Library ------- +- Issue #26039: Added zipfile.ZipInfo.from_file() and zipinfo.ZipInfo.is_dir(). + Patch by Thomas Kluyver. + - Issue #12923: Reset FancyURLopener's redirect counter even if there is an exception. Based on patches by Brian Brazil and Daniel Rocco. |