1 # The software in this package is distributed under the GNU General
2 # Public License version 2 (with a special exception described below).
4 # A copy of GNU General Public License (GPL) is included in this distribution,
5 # in the file COPYING.GPL.
7 # As a special exception, if other files instantiate templates or use macros
8 # or inline functions from this file, or you compile this file and link it
9 # with other works to produce a work based on this file, this file
10 # does not by itself cause the resulting work to be covered
11 # by the GNU General Public License.
13 # However the source code for this file must still be made available
14 # in accordance with section (3) of the GNU General Public License.
16 # This exception does not invalidate any other reasons why a work based
17 # on this file might be covered by the GNU General Public License.
19 # Copyright (c) 2016-2018 Intra2net AG <info@intra2net.com>
21 """ Streamable version of zipfile
23 Python's :py:class:`zipfile.ZipFile` can only write to seekable streams
24 since version 3.5 and only implements adding files as wholes. This module
25 implements class :py:class:`ZipStream` which is a subclass of ZipFile that can
26 read from non-seekable input streams and write to non-seekable output streams.
30 from pyi2ncommon.zip_stream import ZipStream
31 with ZipStream(output_stream, 'w') as zip:
32 info = zip.create_zipinfo(big_file)
33 with open(big_file, 'rb') as input_stream: # always read binary!
34 zip.write_stream(input_stream, info)
36 .. codeauthor:: Intra2net AG <info@intra2net>
42 from .type_helpers import isstr
45 class BytesTellWrapper:
47 Wrapper around a write-only stream that supports tell but not seek
49 copy of zipfile._Tellable
51 def __init__(self, fp):
55 def write(self, data):
56 n = self.fp.write(data)
70 class ZipStream(ZipFile):
71 """Subclass of ZipFile that supports non-seekable input and output"""
73 def __init__(self, file, *args, **kwargs):
75 Create ZipStream instance which is like a ZipFile plus functions
76 create_zipinfo and write_stream.
78 ZipFile determines whether output stream can seek() and tell().
79 Unfortunately some streams (like sys.stdout.buffer when redirecting
80 output) seem to support these methods but only return 0 from tell.
82 :param bool force_wrap: force wrapping of output stream with
85 if 'force_wrap' in kwargs and kwargs['force_wrap']:
87 raise ValueError('force_wrap only makes sense for streams')
88 del kwargs['force_wrap']
89 super(ZipStream, self).__init__(BytesTellWrapper(file), *args,
92 super(ZipStream, self).__init__(file, *args, **kwargs)
94 def create_zipinfo(self, filename, arcname=None):
96 Create ZipInfo for given file
98 Optionally set arcname as name of file inside archive.
100 return ZipInfo.from_file(filename, arcname)
102 def write_stream(self, src, zinfo):
104 Add data from byte stream stream src to archive with info in ZipInfo.
106 Param zinfo must be a ZipInfo, created e.g. with
107 :py:meth:`ZipStream.create_zipinfo`
109 Note: you cannot add directories this way (removed the corresponding
112 This is a shortened version of python's
113 :py:func:`zipfile.ZipFile.write`.
117 "Attempt to write to ZIP archive that was already closed")
120 "Can't write to ZIP archive while an open writing handle exists"
124 raise ValueError('streaming a dir entry does not make sense')
125 if zinfo.compress_type is None:
126 zinfo.compress_type = self.compression
128 with self.open(zinfo, 'w') as dest:
129 shutil.copyfileobj(src, dest, 1024*8)