]> granicus.if.org Git - python/commitdiff
Issue #26039: Added zipfile.ZipInfo.from_file() and zipinfo.ZipInfo.is_dir().
authorSerhiy Storchaka <storchaka@gmail.com>
Sun, 7 Feb 2016 22:02:25 +0000 (00:02 +0200)
committerSerhiy Storchaka <storchaka@gmail.com>
Sun, 7 Feb 2016 22:02:25 +0000 (00:02 +0200)
Patch by Thomas Kluyver.

Doc/library/zipfile.rst
Doc/whatsnew/3.6.rst
Lib/test/test_zipfile.py
Lib/zipfile.py
Misc/NEWS

index e3c221731750f34a3cfc03b607a7a9d1b84975b5..54b8cc5a2d4f7eb55d0276a25b440cc5d96f964d 100644 (file)
@@ -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
index 158dad3efe4a48e1e34805cfd493e0fb909a0617..b7cc15966666fb56fad3800efff98aeca84cd325 100644 (file)
@@ -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
 =============
 
index 2c10821a0cda3a67476c2df764ab1e547d76fc2b..8589342a80f8823ec90ae1b8024a9e872e2f081b 100644 (file)
@@ -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()
index 56a2479fb3850a4e4dac10da15b3eebb1c17614f..e0598d27ed76523c8efc3ea3e3c80825e5bcdae1 100644 (file)
@@ -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))
index 510748b25697c0529a548919cb15ac74cab6e390..a7c3f58556862b6f402ff1b6c2247b7f8c4facc1 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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.