Kaydet (Commit) 503f9080 authored tarafından Serhiy Storchaka's avatar Serhiy Storchaka

Issue #26039: Added zipfile.ZipInfo.from_file() and zipinfo.ZipInfo.is_dir().

Patch by Thomas Kluyver.
üst 46988d36
...@@ -465,6 +465,22 @@ Instances of the :class:`ZipInfo` class are returned by the :meth:`.getinfo` and ...@@ -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 :meth:`.infolist` methods of :class:`ZipFile` objects. Each object stores
information about a single member of the ZIP archive. 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: Instances have the following attributes:
...@@ -574,3 +590,11 @@ Instances have the following attributes: ...@@ -574,3 +590,11 @@ Instances have the following attributes:
.. attribute:: ZipInfo.file_size .. attribute:: ZipInfo.file_size
Size of the uncompressed file. 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 ...@@ -140,6 +140,16 @@ urllib.robotparser
(Contributed by Nikolay Bogoychev in :issue:`16099`.) (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 Optimizations
============= =============
......
...@@ -3,6 +3,7 @@ import io ...@@ -3,6 +3,7 @@ import io
import os import os
import sys import sys
import importlib.util import importlib.util
import posixpath
import time import time
import struct import struct
import zipfile import zipfile
...@@ -2071,5 +2072,19 @@ class LzmaUniversalNewlineTests(AbstractUniversalNewlineTests, ...@@ -2071,5 +2072,19 @@ class LzmaUniversalNewlineTests(AbstractUniversalNewlineTests,
unittest.TestCase): unittest.TestCase):
compression = zipfile.ZIP_LZMA 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__": if __name__ == "__main__":
unittest.main() unittest.main()
...@@ -371,7 +371,7 @@ class ZipInfo (object): ...@@ -371,7 +371,7 @@ class ZipInfo (object):
result.append(' filemode=%r' % stat.filemode(hi)) result.append(' filemode=%r' % stat.filemode(hi))
if lo: if lo:
result.append(' external_attr=%#x' % lo) result.append(' external_attr=%#x' % lo)
isdir = self.filename[-1:] == '/' isdir = self.is_dir()
if not isdir or self.file_size: if not isdir or self.file_size:
result.append(' file_size=%r' % self.file_size) result.append(' file_size=%r' % self.file_size)
if ((not isdir or self.compress_size) and if ((not isdir or self.compress_size) and
...@@ -469,6 +469,41 @@ class ZipInfo (object): ...@@ -469,6 +469,41 @@ class ZipInfo (object):
extra = extra[ln+4:] 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 _ZipDecrypter:
"""Class to handle decryption of files stored within a ZIP archive. """Class to handle decryption of files stored within a ZIP archive.
...@@ -1389,7 +1424,7 @@ class ZipFile: ...@@ -1389,7 +1424,7 @@ class ZipFile:
if upperdirs and not os.path.exists(upperdirs): if upperdirs and not os.path.exists(upperdirs):
os.makedirs(upperdirs) os.makedirs(upperdirs)
if member.filename[-1] == '/': if member.is_dir():
if not os.path.isdir(targetpath): if not os.path.isdir(targetpath):
os.mkdir(targetpath) os.mkdir(targetpath)
return targetpath return targetpath
...@@ -1430,29 +1465,17 @@ class ZipFile: ...@@ -1430,29 +1465,17 @@ class ZipFile:
raise RuntimeError( raise RuntimeError(
"Attempt to write to ZIP archive that was already closed") "Attempt to write to ZIP archive that was already closed")
st = os.stat(filename) zinfo = ZipInfo.from_file(filename, arcname)
isdir = stat.S_ISDIR(st.st_mode)
mtime = time.localtime(st.st_mtime) if zinfo.is_dir():
date_time = mtime[0:6] zinfo.compress_size = 0
# Create ZipInfo instance to store file information zinfo.CRC = 0
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: 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: with self._lock:
if self._seekable: if self._seekable:
self.fp.seek(self.start_dir) self.fp.seek(self.start_dir)
...@@ -1464,11 +1487,7 @@ class ZipFile: ...@@ -1464,11 +1487,7 @@ class ZipFile:
self._writecheck(zinfo) self._writecheck(zinfo)
self._didModify = True self._didModify = True
if isdir: if zinfo.is_dir():
zinfo.file_size = 0
zinfo.compress_size = 0
zinfo.CRC = 0
zinfo.external_attr |= 0x10 # MS-DOS directory flag
self.filelist.append(zinfo) self.filelist.append(zinfo)
self.NameToInfo[zinfo.filename] = zinfo self.NameToInfo[zinfo.filename] = zinfo
self.fp.write(zinfo.FileHeader(False)) self.fp.write(zinfo.FileHeader(False))
......
...@@ -170,6 +170,9 @@ Core and Builtins ...@@ -170,6 +170,9 @@ Core and Builtins
Library 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 - Issue #12923: Reset FancyURLopener's redirect counter even if there is an
exception. Based on patches by Brian Brazil and Daniel Rocco. exception. Based on patches by Brian Brazil and Daniel Rocco.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment