Issue #26039: Added zipfile.ZipInfo.from_file() and zipinfo.ZipInfo.is_dir().
Patch by Thomas Kluyver.
This commit is contained in:
parent
46988d3659
commit
503f908090
|
@ -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
|
||||
|
|
|
@ -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
|
||||
=============
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
else:
|
||||
zinfo.compress_type = compress_type
|
||||
zinfo = ZipInfo.from_file(filename, arcname)
|
||||
|
||||
if zinfo.is_dir():
|
||||
zinfo.compress_size = 0
|
||||
zinfo.CRC = 0
|
||||
else:
|
||||
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.
|
||||
|
||||
|
|
Loading…
Reference in New Issue