Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 503f908

Browse files
Issue #26039: Added zipfile.ZipInfo.from_file() and zipinfo.ZipInfo.is_dir().
Patch by Thomas Kluyver.
1 parent 46988d3 commit 503f908

5 files changed

Lines changed: 99 additions & 28 deletions

File tree

Doc/library/zipfile.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,22 @@ Instances of the :class:`ZipInfo` class are returned by the :meth:`.getinfo` and
465465
:meth:`.infolist` methods of :class:`ZipFile` objects. Each object stores
466466
information about a single member of the ZIP archive.
467467

468+
There is one classmethod to make a :class:`ZipInfo` instance for a filesystem
469+
file:
470+
471+
.. classmethod:: ZipInfo.from_file(filename, arcname=None)
472+
473+
Construct a :class:`ZipInfo` instance for a file on the filesystem, in
474+
preparation for adding it to a zip file.
475+
476+
*filename* should be the path to a file or directory on the filesystem.
477+
478+
If *arcname* is specified, it is used as the name within the archive.
479+
If *arcname* is not specified, the name will be the same as *filename*, but
480+
with any drive letter and leading path separators removed.
481+
482+
.. versionadded:: 3.6
483+
468484
Instances have the following attributes:
469485

470486

@@ -574,3 +590,11 @@ Instances have the following attributes:
574590
.. attribute:: ZipInfo.file_size
575591

576592
Size of the uncompressed file.
593+
594+
There is one method:
595+
596+
.. method:: ZipInfo.is_dir()
597+
598+
Return ``True`` if the ZipInfo represents a directory.
599+
600+
.. versionadded:: 3.6

Doc/whatsnew/3.6.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,16 @@ urllib.robotparser
140140
(Contributed by Nikolay Bogoychev in :issue:`16099`.)
141141

142142

143+
zipfile
144+
-------
145+
146+
A new :meth:`ZipInfo.from_file() <zipfile.ZipInfo.from_file>` class method
147+
allow to make :class:`~zipfile.ZipInfo` instance from a filesystem file.
148+
A new :meth:`ZipInfo.is_dir() <zipfile.ZipInfo.is_dir>` method can be used
149+
to check if the :class:`~zipfile.ZipInfo` instance represents a directory.
150+
(Contributed by Thomas Kluyver in :issue:`26039`.)
151+
152+
143153
Optimizations
144154
=============
145155

Lib/test/test_zipfile.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44
import sys
55
import importlib.util
6+
import posixpath
67
import time
78
import struct
89
import zipfile
@@ -2071,5 +2072,19 @@ class LzmaUniversalNewlineTests(AbstractUniversalNewlineTests,
20712072
unittest.TestCase):
20722073
compression = zipfile.ZIP_LZMA
20732074

2075+
class ZipInfoTests(unittest.TestCase):
2076+
def test_from_file(self):
2077+
zi = zipfile.ZipInfo.from_file(__file__)
2078+
self.assertEqual(posixpath.basename(zi.filename), 'test_zipfile.py')
2079+
self.assertFalse(zi.is_dir())
2080+
2081+
def test_from_dir(self):
2082+
dirpath = os.path.dirname(os.path.abspath(__file__))
2083+
zi = zipfile.ZipInfo.from_file(dirpath, 'stdlib_tests')
2084+
self.assertEqual(zi.filename, 'stdlib_tests/')
2085+
self.assertTrue(zi.is_dir())
2086+
self.assertEqual(zi.compress_type, zipfile.ZIP_STORED)
2087+
self.assertEqual(zi.file_size, 0)
2088+
20742089
if __name__ == "__main__":
20752090
unittest.main()

Lib/zipfile.py

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ def __repr__(self):
371371
result.append(' filemode=%r' % stat.filemode(hi))
372372
if lo:
373373
result.append(' external_attr=%#x' % lo)
374-
isdir = self.filename[-1:] == '/'
374+
isdir = self.is_dir()
375375
if not isdir or self.file_size:
376376
result.append(' file_size=%r' % self.file_size)
377377
if ((not isdir or self.compress_size) and
@@ -469,6 +469,41 @@ def _decodeExtra(self):
469469

470470
extra = extra[ln+4:]
471471

472+
@classmethod
473+
def from_file(cls, filename, arcname=None):
474+
"""Construct an appropriate ZipInfo for a file on the filesystem.
475+
476+
filename should be the path to a file or directory on the filesystem.
477+
478+
arcname is the name which it will have within the archive (by default,
479+
this will be the same as filename, but without a drive letter and with
480+
leading path separators removed).
481+
"""
482+
st = os.stat(filename)
483+
isdir = stat.S_ISDIR(st.st_mode)
484+
mtime = time.localtime(st.st_mtime)
485+
date_time = mtime[0:6]
486+
# Create ZipInfo instance to store file information
487+
if arcname is None:
488+
arcname = filename
489+
arcname = os.path.normpath(os.path.splitdrive(arcname)[1])
490+
while arcname[0] in (os.sep, os.altsep):
491+
arcname = arcname[1:]
492+
if isdir:
493+
arcname += '/'
494+
zinfo = cls(arcname, date_time)
495+
zinfo.external_attr = (st.st_mode & 0xFFFF) << 16 # Unix attributes
496+
if isdir:
497+
zinfo.file_size = 0
498+
zinfo.external_attr |= 0x10 # MS-DOS directory flag
499+
else:
500+
zinfo.file_size = st.st_size
501+
502+
return zinfo
503+
504+
def is_dir(self):
505+
return self.filename[-1] == '/'
506+
472507

473508
class _ZipDecrypter:
474509
"""Class to handle decryption of files stored within a ZIP archive.
@@ -1389,7 +1424,7 @@ def _extract_member(self, member, targetpath, pwd):
13891424
if upperdirs and not os.path.exists(upperdirs):
13901425
os.makedirs(upperdirs)
13911426

1392-
if member.filename[-1] == '/':
1427+
if member.is_dir():
13931428
if not os.path.isdir(targetpath):
13941429
os.mkdir(targetpath)
13951430
return targetpath
@@ -1430,29 +1465,17 @@ def write(self, filename, arcname=None, compress_type=None):
14301465
raise RuntimeError(
14311466
"Attempt to write to ZIP archive that was already closed")
14321467

1433-
st = os.stat(filename)
1434-
isdir = stat.S_ISDIR(st.st_mode)
1435-
mtime = time.localtime(st.st_mtime)
1436-
date_time = mtime[0:6]
1437-
# Create ZipInfo instance to store file information
1438-
if arcname is None:
1439-
arcname = filename
1440-
arcname = os.path.normpath(os.path.splitdrive(arcname)[1])
1441-
while arcname[0] in (os.sep, os.altsep):
1442-
arcname = arcname[1:]
1443-
if isdir:
1444-
arcname += '/'
1445-
zinfo = ZipInfo(arcname, date_time)
1446-
zinfo.external_attr = (st[0] & 0xFFFF) << 16 # Unix attributes
1447-
if isdir:
1448-
zinfo.compress_type = ZIP_STORED
1449-
elif compress_type is None:
1450-
zinfo.compress_type = self.compression
1468+
zinfo = ZipInfo.from_file(filename, arcname)
1469+
1470+
if zinfo.is_dir():
1471+
zinfo.compress_size = 0
1472+
zinfo.CRC = 0
14511473
else:
1452-
zinfo.compress_type = compress_type
1474+
if compress_type is not None:
1475+
zinfo.compress_type = compress_type
1476+
else:
1477+
zinfo.compress_type = self.compression
14531478

1454-
zinfo.file_size = st.st_size
1455-
zinfo.flag_bits = 0x00
14561479
with self._lock:
14571480
if self._seekable:
14581481
self.fp.seek(self.start_dir)
@@ -1464,11 +1487,7 @@ def write(self, filename, arcname=None, compress_type=None):
14641487
self._writecheck(zinfo)
14651488
self._didModify = True
14661489

1467-
if isdir:
1468-
zinfo.file_size = 0
1469-
zinfo.compress_size = 0
1470-
zinfo.CRC = 0
1471-
zinfo.external_attr |= 0x10 # MS-DOS directory flag
1490+
if zinfo.is_dir():
14721491
self.filelist.append(zinfo)
14731492
self.NameToInfo[zinfo.filename] = zinfo
14741493
self.fp.write(zinfo.FileHeader(False))

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ Core and Builtins
170170
Library
171171
-------
172172

173+
- Issue #26039: Added zipfile.ZipInfo.from_file() and zipinfo.ZipInfo.is_dir().
174+
Patch by Thomas Kluyver.
175+
173176
- Issue #12923: Reset FancyURLopener's redirect counter even if there is an
174177
exception. Based on patches by Brian Brazil and Daniel Rocco.
175178

0 commit comments

Comments
 (0)