summaryrefslogtreecommitdiff
path: root/Lib/zipfile.py
diff options
context:
space:
mode:
authorBo Bayles <bbayles@gmail.com>2018-01-29 23:54:07 -0600
committerGregory P. Smith <greg@krypto.org>2018-01-29 21:54:07 -0800
commitce237c7d58ba207575cdfb0195a58a6407fbf717 (patch)
tree94f6d04e82d5c1e7b14e45f7778a23cdca88f8ab /Lib/zipfile.py
parentf4d644f36ffb6cb11b34bfcf533c14cfaebf709a (diff)
downloadcpython-git-ce237c7d58ba207575cdfb0195a58a6407fbf717.tar.gz
bpo-21417: Add compresslevel= to the zipfile module (GH-5385)
This allows the compression level to be specified when writing zipfiles (for the entire file *and* overridden on a per-file basis). Contributed by Bo Bayles
Diffstat (limited to 'Lib/zipfile.py')
-rw-r--r--Lib/zipfile.py43
1 files changed, 35 insertions, 8 deletions
diff --git a/Lib/zipfile.py b/Lib/zipfile.py
index 37ce3281e0..f9db45f58a 100644
--- a/Lib/zipfile.py
+++ b/Lib/zipfile.py
@@ -295,6 +295,7 @@ class ZipInfo (object):
'filename',
'date_time',
'compress_type',
+ '_compresslevel',
'comment',
'extra',
'create_system',
@@ -334,6 +335,7 @@ class ZipInfo (object):
# Standard values:
self.compress_type = ZIP_STORED # Type of compression for the file
+ self._compresslevel = None # Level for the compressor
self.comment = b"" # Comment for each file
self.extra = b"" # ZIP extra data
if sys.platform == 'win32':
@@ -654,12 +656,16 @@ def _check_compression(compression):
raise NotImplementedError("That compression method is not supported")
-def _get_compressor(compress_type):
+def _get_compressor(compress_type, compresslevel=None):
if compress_type == ZIP_DEFLATED:
- return zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
- zlib.DEFLATED, -15)
+ if compresslevel is not None:
+ return zlib.compressobj(compresslevel, zlib.DEFLATED, -15)
+ return zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -15)
elif compress_type == ZIP_BZIP2:
+ if compresslevel is not None:
+ return bz2.BZ2Compressor(compresslevel)
return bz2.BZ2Compressor()
+ # compresslevel is ignored for ZIP_LZMA
elif compress_type == ZIP_LZMA:
return LZMACompressor()
else:
@@ -963,7 +969,8 @@ class _ZipWriteFile(io.BufferedIOBase):
self._zinfo = zinfo
self._zip64 = zip64
self._zipfile = zf
- self._compressor = _get_compressor(zinfo.compress_type)
+ self._compressor = _get_compressor(zinfo.compress_type,
+ zinfo._compresslevel)
self._file_size = 0
self._compress_size = 0
self._crc = 0
@@ -1035,7 +1042,8 @@ class _ZipWriteFile(io.BufferedIOBase):
class ZipFile:
""" Class with methods to open, read, write, close, list zip files.
- z = ZipFile(file, mode="r", compression=ZIP_STORED, allowZip64=True)
+ z = ZipFile(file, mode="r", compression=ZIP_STORED, allowZip64=True,
+ compresslevel=None)
file: Either the path to the file, or a file-like object.
If it is a path, the file will be opened and closed by ZipFile.
@@ -1046,13 +1054,19 @@ class ZipFile:
allowZip64: if True ZipFile will create files with ZIP64 extensions when
needed, otherwise it will raise an exception when this would
be necessary.
+ compresslevel: None (default for the given compression type) or an integer
+ specifying the level to pass to the compressor.
+ When using ZIP_STORED or ZIP_LZMA this keyword has no effect.
+ When using ZIP_DEFLATED integers 0 through 9 are accepted.
+ When using ZIP_BZIP2 integers 1 through 9 are accepted.
"""
fp = None # Set here since __del__ checks it
_windows_illegal_name_trans_table = None
- def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True):
+ def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True,
+ compresslevel=None):
"""Open the ZIP file with mode read 'r', write 'w', exclusive create 'x',
or append 'a'."""
if mode not in ('r', 'w', 'x', 'a'):
@@ -1066,6 +1080,7 @@ class ZipFile:
self.NameToInfo = {} # Find file info given name
self.filelist = [] # List of ZipInfo instances for archive
self.compression = compression # Method of compression
+ self.compresslevel = compresslevel
self.mode = mode
self.pwd = None
self._comment = b''
@@ -1342,6 +1357,7 @@ class ZipFile:
elif mode == 'w':
zinfo = ZipInfo(name)
zinfo.compress_type = self.compression
+ zinfo._compresslevel = self.compresslevel
else:
# Get info object for name
zinfo = self.getinfo(name)
@@ -1575,7 +1591,8 @@ class ZipFile:
raise LargeZipFile(requires_zip64 +
" would require ZIP64 extensions")
- def write(self, filename, arcname=None, compress_type=None):
+ def write(self, filename, arcname=None,
+ compress_type=None, compresslevel=None):
"""Put the bytes from filename into the archive under the name
arcname."""
if not self.fp:
@@ -1597,6 +1614,11 @@ class ZipFile:
else:
zinfo.compress_type = self.compression
+ if compresslevel is not None:
+ zinfo._compresslevel = compresslevel
+ else:
+ zinfo._compresslevel = self.compresslevel
+
if zinfo.is_dir():
with self._lock:
if self._seekable:
@@ -1617,7 +1639,8 @@ class ZipFile:
with open(filename, "rb") as src, self.open(zinfo, 'w') as dest:
shutil.copyfileobj(src, dest, 1024*8)
- def writestr(self, zinfo_or_arcname, data, compress_type=None):
+ def writestr(self, zinfo_or_arcname, data,
+ compress_type=None, compresslevel=None):
"""Write a file into the archive. The contents is 'data', which
may be either a 'str' or a 'bytes' instance; if it is a 'str',
it is encoded as UTF-8 first.
@@ -1629,6 +1652,7 @@ class ZipFile:
zinfo = ZipInfo(filename=zinfo_or_arcname,
date_time=time.localtime(time.time())[:6])
zinfo.compress_type = self.compression
+ zinfo._compresslevel = self.compresslevel
if zinfo.filename[-1] == '/':
zinfo.external_attr = 0o40775 << 16 # drwxrwxr-x
zinfo.external_attr |= 0x10 # MS-DOS directory flag
@@ -1648,6 +1672,9 @@ class ZipFile:
if compress_type is not None:
zinfo.compress_type = compress_type
+ if compresslevel is not None:
+ zinfo._compresslevel = compresslevel
+
zinfo.file_size = len(data) # Uncompressed size
with self._lock:
with self.open(zinfo, mode='w') as dest: