unzip-http 0.4__tar.gz → 0.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- unzip-http-0.6/PKG-INFO +50 -0
- {unzip-http-0.4 → unzip-http-0.6}/README.md +10 -4
- {unzip-http-0.4 → unzip-http-0.6}/setup.py +5 -2
- unzip-http-0.6/unzip_http.egg-info/PKG-INFO +50 -0
- {unzip-http-0.4 → unzip-http-0.6}/unzip_http.egg-info/SOURCES.txt +1 -1
- unzip-http-0.6/unzip_http.egg-info/entry_points.txt +2 -0
- {unzip-http-0.4 → unzip-http-0.6}/unzip_http.py +157 -13
- unzip-http-0.4/PKG-INFO +0 -45
- unzip-http-0.4/unzip-http +0 -62
- unzip-http-0.4/unzip_http.egg-info/PKG-INFO +0 -45
- {unzip-http-0.4 → unzip-http-0.6}/LICENSE-mit.txt +0 -0
- {unzip-http-0.4 → unzip-http-0.6}/MANIFEST.in +0 -0
- {unzip-http-0.4 → unzip-http-0.6}/requirements.txt +0 -0
- {unzip-http-0.4 → unzip-http-0.6}/setup.cfg +0 -0
- {unzip-http-0.4 → unzip-http-0.6}/unzip_http.egg-info/dependency_links.txt +0 -0
- {unzip-http-0.4 → unzip-http-0.6}/unzip_http.egg-info/requires.txt +0 -0
- {unzip-http-0.4 → unzip-http-0.6}/unzip_http.egg-info/top_level.txt +0 -0
unzip-http-0.6/PKG-INFO
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: unzip-http
|
|
3
|
+
Version: 0.6
|
|
4
|
+
Summary: extract files from .zip files over http without downloading entire archive
|
|
5
|
+
Home-page: https://github.com/saulpw/unzip-http
|
|
6
|
+
Author: Saul Pwanson
|
|
7
|
+
Keywords: http zip unzip
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE-mit.txt
|
|
13
|
+
|
|
14
|
+
# unzip-http
|
|
15
|
+
|
|
16
|
+
Extract individual files from .zip files over http without downloading the entire archive.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
pip install unzip-http
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
unzip_http [-l] [-f] [-o] <url> <filenames..>
|
|
25
|
+
|
|
26
|
+
Extract <filenames> from a remote .zip at `<url>` to stdout.
|
|
27
|
+
|
|
28
|
+
A filename can be a wildcard glob; all matching files are extracted in this case.
|
|
29
|
+
|
|
30
|
+
Specify multiple filenames as distinct arguments (separated with spaces on the command line).
|
|
31
|
+
|
|
32
|
+
Note: HTTP server must send `Accept-Ranges: bytes` and `Content-Length` in headers (most do).
|
|
33
|
+
|
|
34
|
+
Options:
|
|
35
|
+
|
|
36
|
+
- `-l`: List files in remote .zip file (default if no filenames given)
|
|
37
|
+
- `-f`: Recreate folder structure from .zip file when extracting (instead of extracting files to the current directly to the current directory)
|
|
38
|
+
- `-o`: Write files to stdout (if multiple files, concatenate them in zipfile order)
|
|
39
|
+
|
|
40
|
+
# Python module `unzip_http`
|
|
41
|
+
|
|
42
|
+
import unzip_http
|
|
43
|
+
|
|
44
|
+
rzf = unzip_http.RemoteZipFile('https://example.com/foo.zip')
|
|
45
|
+
binfp = rzf.open('bar.bin')
|
|
46
|
+
txtfp = rzf.open_text('baz.txt')
|
|
47
|
+
|
|
48
|
+
# Credits
|
|
49
|
+
|
|
50
|
+
`unzip-http` was written by [Saul Pwanson](https://saul.pw) and made available for use under the MIT License.
|
|
@@ -8,15 +8,21 @@ Extract individual files from .zip files over http without downloading the entir
|
|
|
8
8
|
|
|
9
9
|
## Usage
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
unzip_http [-l] [-f] [-o] <url> <filenames..>
|
|
12
12
|
|
|
13
13
|
Extract <filenames> from a remote .zip at `<url>` to stdout.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
A filename can be a wildcard glob; all matching files are extracted in this case.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Specify multiple filenames as distinct arguments (separated with spaces on the command line).
|
|
18
18
|
|
|
19
|
-
Note: HTTP server must send `Accept-Ranges: bytes` and `Content-Length` in headers.
|
|
19
|
+
Note: HTTP server must send `Accept-Ranges: bytes` and `Content-Length` in headers (most do).
|
|
20
|
+
|
|
21
|
+
Options:
|
|
22
|
+
|
|
23
|
+
- `-l`: List files in remote .zip file (default if no filenames given)
|
|
24
|
+
- `-f`: Recreate folder structure from .zip file when extracting (instead of extracting files to the current directly to the current directory)
|
|
25
|
+
- `-o`: Write files to stdout (if multiple files, concatenate them in zipfile order)
|
|
20
26
|
|
|
21
27
|
# Python module `unzip_http`
|
|
22
28
|
|
|
@@ -14,7 +14,7 @@ def requirements():
|
|
|
14
14
|
|
|
15
15
|
setup(
|
|
16
16
|
name="unzip-http",
|
|
17
|
-
version="0.
|
|
17
|
+
version="0.6",
|
|
18
18
|
description="extract files from .zip files over http without downloading entire archive",
|
|
19
19
|
long_description=readme(),
|
|
20
20
|
long_description_content_type="text/markdown",
|
|
@@ -27,6 +27,9 @@ setup(
|
|
|
27
27
|
url="https://github.com/saulpw/unzip-http",
|
|
28
28
|
python_requires=">=3.8",
|
|
29
29
|
py_modules=["unzip_http"],
|
|
30
|
-
scripts=["
|
|
30
|
+
scripts=["unzip_http.py"],
|
|
31
|
+
entry_points={
|
|
32
|
+
"console_scripts": ["unzip_http=unzip_http:main"],
|
|
33
|
+
},
|
|
31
34
|
install_requires=requirements(),
|
|
32
35
|
)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: unzip-http
|
|
3
|
+
Version: 0.6
|
|
4
|
+
Summary: extract files from .zip files over http without downloading entire archive
|
|
5
|
+
Home-page: https://github.com/saulpw/unzip-http
|
|
6
|
+
Author: Saul Pwanson
|
|
7
|
+
Keywords: http zip unzip
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE-mit.txt
|
|
13
|
+
|
|
14
|
+
# unzip-http
|
|
15
|
+
|
|
16
|
+
Extract individual files from .zip files over http without downloading the entire archive.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
pip install unzip-http
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
unzip_http [-l] [-f] [-o] <url> <filenames..>
|
|
25
|
+
|
|
26
|
+
Extract <filenames> from a remote .zip at `<url>` to stdout.
|
|
27
|
+
|
|
28
|
+
A filename can be a wildcard glob; all matching files are extracted in this case.
|
|
29
|
+
|
|
30
|
+
Specify multiple filenames as distinct arguments (separated with spaces on the command line).
|
|
31
|
+
|
|
32
|
+
Note: HTTP server must send `Accept-Ranges: bytes` and `Content-Length` in headers (most do).
|
|
33
|
+
|
|
34
|
+
Options:
|
|
35
|
+
|
|
36
|
+
- `-l`: List files in remote .zip file (default if no filenames given)
|
|
37
|
+
- `-f`: Recreate folder structure from .zip file when extracting (instead of extracting files to the current directly to the current directory)
|
|
38
|
+
- `-o`: Write files to stdout (if multiple files, concatenate them in zipfile order)
|
|
39
|
+
|
|
40
|
+
# Python module `unzip_http`
|
|
41
|
+
|
|
42
|
+
import unzip_http
|
|
43
|
+
|
|
44
|
+
rzf = unzip_http.RemoteZipFile('https://example.com/foo.zip')
|
|
45
|
+
binfp = rzf.open('bar.bin')
|
|
46
|
+
txtfp = rzf.open_text('baz.txt')
|
|
47
|
+
|
|
48
|
+
# Credits
|
|
49
|
+
|
|
50
|
+
`unzip-http` was written by [Saul Pwanson](https://saul.pw) and made available for use under the MIT License.
|
|
@@ -3,10 +3,10 @@ MANIFEST.in
|
|
|
3
3
|
README.md
|
|
4
4
|
requirements.txt
|
|
5
5
|
setup.py
|
|
6
|
-
unzip-http
|
|
7
6
|
unzip_http.py
|
|
8
7
|
unzip_http.egg-info/PKG-INFO
|
|
9
8
|
unzip_http.egg-info/SOURCES.txt
|
|
10
9
|
unzip_http.egg-info/dependency_links.txt
|
|
10
|
+
unzip_http.egg-info/entry_points.txt
|
|
11
11
|
unzip_http.egg-info/requires.txt
|
|
12
12
|
unzip_http.egg-info/top_level.txt
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
1
3
|
# Copyright (c) 2022 Saul Pwanson
|
|
2
4
|
#
|
|
3
5
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
@@ -18,22 +20,50 @@
|
|
|
18
20
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
21
|
# SOFTWARE.
|
|
20
22
|
|
|
23
|
+
"""
|
|
24
|
+
usage: unzip_http [-h] [-l] [-f] [-o] url [files ...]
|
|
25
|
+
|
|
26
|
+
Extract individual files from .zip files over http without downloading the
|
|
27
|
+
entire archive. HTTP server must send `Accept-Ranges: bytes` and
|
|
28
|
+
`Content-Length` in headers.
|
|
29
|
+
|
|
30
|
+
positional arguments:
|
|
31
|
+
url URL of the remote zip file
|
|
32
|
+
files Files to extract. If no filenames given, displays .zip
|
|
33
|
+
contents (filenames and sizes). Each filename can be a
|
|
34
|
+
wildcard glob.
|
|
35
|
+
|
|
36
|
+
options:
|
|
37
|
+
-h, --help show this help message and exit
|
|
38
|
+
-l, --list List files in the remote zip file
|
|
39
|
+
-f, --full-filepaths Recreate folder structure from zip file when extracting
|
|
40
|
+
(instead of extracting the files to the current
|
|
41
|
+
directory)
|
|
42
|
+
-o, --stdout Write files to stdout (if multiple files: concatenate
|
|
43
|
+
them to stdout, in zipfile order)
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
import sys
|
|
21
47
|
import os
|
|
22
48
|
import io
|
|
49
|
+
import math
|
|
50
|
+
import time
|
|
23
51
|
import zlib
|
|
24
52
|
import struct
|
|
25
53
|
import fnmatch
|
|
54
|
+
import argparse
|
|
26
55
|
import pathlib
|
|
27
56
|
import urllib.parse
|
|
28
57
|
|
|
29
|
-
import urllib3
|
|
30
58
|
|
|
31
|
-
__version__ = '0.
|
|
59
|
+
__version__ = '0.6'
|
|
32
60
|
|
|
33
61
|
|
|
34
62
|
def error(s):
|
|
35
63
|
raise Exception(s)
|
|
36
64
|
|
|
65
|
+
def warning(s):
|
|
66
|
+
print(s, file=sys.stderr)
|
|
37
67
|
|
|
38
68
|
def get_bits(val:int, *args):
|
|
39
69
|
'Generate bitfields (one for each arg) from LSB to MSB.'
|
|
@@ -59,6 +89,9 @@ class RemoteZipInfo:
|
|
|
59
89
|
sec, mins, hour, day, mon, year = get_bits(date_time, 5, 6, 5, 5, 4, 7)
|
|
60
90
|
self.date_time = (year+1980, mon, day, hour, mins, sec)
|
|
61
91
|
|
|
92
|
+
def is_dir(self):
|
|
93
|
+
return self.filename.endswith('/')
|
|
94
|
+
|
|
62
95
|
def parse_extra(self, extra):
|
|
63
96
|
i = 0
|
|
64
97
|
while i < len(extra):
|
|
@@ -93,6 +126,7 @@ class RemoteZipFile:
|
|
|
93
126
|
magic_eocd = b'\x50\x4b\x05\x06'
|
|
94
127
|
|
|
95
128
|
def __init__(self, url):
|
|
129
|
+
import urllib3
|
|
96
130
|
self.url = url
|
|
97
131
|
self.http = urllib3.PoolManager()
|
|
98
132
|
self.zip_size = 0
|
|
@@ -120,10 +154,13 @@ class RemoteZipFile:
|
|
|
120
154
|
r = resp.headers.get('Accept-Ranges', '')
|
|
121
155
|
if r != 'bytes':
|
|
122
156
|
hostname = urllib.parse.urlparse(self.url).netloc
|
|
123
|
-
|
|
157
|
+
warning(f"{hostname} Accept-Ranges header ('{r}') is not 'bytes'--trying anyway")
|
|
124
158
|
|
|
125
159
|
self.zip_size = int(resp.headers['Content-Length'])
|
|
126
|
-
resp = self.get_range(
|
|
160
|
+
resp = self.get_range(
|
|
161
|
+
max(self.zip_size-65536, 0),
|
|
162
|
+
65536
|
|
163
|
+
)
|
|
127
164
|
|
|
128
165
|
cdir_start = -1
|
|
129
166
|
i = resp.data.rfind(self.magic_eocd64)
|
|
@@ -140,7 +177,15 @@ class RemoteZipFile:
|
|
|
140
177
|
if cdir_start < 0 or cdir_start >= self.zip_size:
|
|
141
178
|
error('cannot find central directory')
|
|
142
179
|
|
|
143
|
-
|
|
180
|
+
if self.zip_size <= 65536:
|
|
181
|
+
filehdr_index = cdir_start
|
|
182
|
+
else:
|
|
183
|
+
filehdr_index = 65536 - (self.zip_size - cdir_start)
|
|
184
|
+
|
|
185
|
+
if filehdr_index < 0:
|
|
186
|
+
resp = self.get_range(cdir_start, self.zip_size - cdir_start)
|
|
187
|
+
filehdr_index = 0
|
|
188
|
+
|
|
144
189
|
cdir_end = filehdr_index + cdir_bytes
|
|
145
190
|
while filehdr_index < cdir_end:
|
|
146
191
|
sizeof_cdirentry = struct.calcsize(self.fmt_cdirentry)
|
|
@@ -166,19 +211,26 @@ class RemoteZipFile:
|
|
|
166
211
|
rzi.parse_extra(extra)
|
|
167
212
|
yield rzi
|
|
168
213
|
|
|
169
|
-
def
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
214
|
+
def extract(self, member, path=None, pwd=None):
|
|
215
|
+
if pwd:
|
|
216
|
+
raise NotImplementedError('Passwords not supported yet')
|
|
217
|
+
|
|
218
|
+
path = path or pathlib.Path('.')
|
|
219
|
+
|
|
220
|
+
outpath = path/member
|
|
173
221
|
os.makedirs(outpath.parent, exist_ok=True)
|
|
174
|
-
with self.open(
|
|
175
|
-
with open(path/
|
|
222
|
+
with self.open(member) as fpin:
|
|
223
|
+
with open(path/member, mode='wb') as fpout:
|
|
176
224
|
while True:
|
|
177
225
|
r = fpin.read(65536)
|
|
178
226
|
if not r:
|
|
179
227
|
break
|
|
180
228
|
fpout.write(r)
|
|
181
229
|
|
|
230
|
+
def extractall(self, path=None, members=None, pwd=None):
|
|
231
|
+
for fn in members or self.namelist():
|
|
232
|
+
self.extract(fn, path, pwd=pwd)
|
|
233
|
+
|
|
182
234
|
def get_range(self, start, n):
|
|
183
235
|
return self.http.request('GET', self.url, headers={'Range': f'bytes={start}-{start+n-1}'}, preload_content=False)
|
|
184
236
|
|
|
@@ -204,12 +256,12 @@ class RemoteZipFile:
|
|
|
204
256
|
return self.get_range(f.header_offset + sizeof_localhdr + fnlen + extralen, f.compress_size)
|
|
205
257
|
elif method == 8: # DEFLATE
|
|
206
258
|
resp = self.get_range(f.header_offset + sizeof_localhdr + fnlen + extralen, f.compress_size)
|
|
207
|
-
return RemoteZipStream(resp, f)
|
|
259
|
+
return io.BufferedReader(RemoteZipStream(resp, f))
|
|
208
260
|
else:
|
|
209
261
|
error(f'unknown compression method {method}')
|
|
210
262
|
|
|
211
263
|
def open_text(self, fn):
|
|
212
|
-
return io.TextIOWrapper(
|
|
264
|
+
return io.TextIOWrapper(self.open(fn))
|
|
213
265
|
|
|
214
266
|
|
|
215
267
|
class RemoteZipStream(io.RawIOBase):
|
|
@@ -239,3 +291,95 @@ class RemoteZipStream(io.RawIOBase):
|
|
|
239
291
|
self._buffer = self._buffer[n:]
|
|
240
292
|
|
|
241
293
|
return ret
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
### script start
|
|
297
|
+
|
|
298
|
+
class StreamProgress:
|
|
299
|
+
def __init__(self, fp, name='', total=0):
|
|
300
|
+
self.name = name
|
|
301
|
+
self.fp = fp
|
|
302
|
+
self.total = total
|
|
303
|
+
self.start_time = time.time()
|
|
304
|
+
self.last_update = 0
|
|
305
|
+
self.amtread = 0
|
|
306
|
+
|
|
307
|
+
def read(self, n):
|
|
308
|
+
r = self.fp.read(n)
|
|
309
|
+
self.amtread += len(r)
|
|
310
|
+
now = time.time()
|
|
311
|
+
if now - self.last_update > 0.1:
|
|
312
|
+
self.last_update = now
|
|
313
|
+
|
|
314
|
+
elapsed_s = now - self.start_time
|
|
315
|
+
sys.stderr.write(f'\r{elapsed_s:.0f}s {self.amtread/10**6:.02f}/{self.total/10**6:.02f}MB ({self.amtread/10**6/elapsed_s:.02f} MB/s) {self.name}')
|
|
316
|
+
|
|
317
|
+
if not r:
|
|
318
|
+
sys.stderr.write('\n')
|
|
319
|
+
|
|
320
|
+
return r
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def list_files(rzf):
|
|
324
|
+
def safelog(x):
|
|
325
|
+
return 1 if x == 0 else math.ceil(math.log10(x))
|
|
326
|
+
|
|
327
|
+
digits_compr = max(safelog(f.compress_size) for f in rzf.infolist())
|
|
328
|
+
digits_plain = max(safelog(f.file_size ) for f in rzf.infolist())
|
|
329
|
+
fmtstr = f'%{digits_compr}d -> %{digits_plain}d\t%s'
|
|
330
|
+
for f in rzf.infolist():
|
|
331
|
+
print(fmtstr % (f.compress_size, f.file_size, f.filename), file=sys.stderr)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def extract_one(outfile, rzf, f, ofname):
|
|
335
|
+
print(f'Extracting {f.filename} to {ofname}...', file=sys.stderr)
|
|
336
|
+
|
|
337
|
+
fp = StreamProgress(rzf.open(f), name=f.filename, total=f.compress_size)
|
|
338
|
+
while r := fp.read(2**18):
|
|
339
|
+
outfile.write(r)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def download_file(f, rzf, args):
|
|
343
|
+
if not any(fnmatch.fnmatch(f.filename, g) for g in args.files):
|
|
344
|
+
return
|
|
345
|
+
|
|
346
|
+
if args.stdout:
|
|
347
|
+
extract_one(sys.stdout.buffer, rzf, f, "stdout")
|
|
348
|
+
else:
|
|
349
|
+
path = pathlib.Path(f.filename)
|
|
350
|
+
if args.full_filepaths:
|
|
351
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
352
|
+
else:
|
|
353
|
+
path = path.name
|
|
354
|
+
|
|
355
|
+
with open(str(path), 'wb') as of:
|
|
356
|
+
extract_one(of, rzf, f, str(path))
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def main():
|
|
360
|
+
parser = argparse.ArgumentParser(prog='unzip-http', \
|
|
361
|
+
description="Extract individual files from .zip files over http without downloading the entire archive. HTTP server must send `Accept-Ranges: bytes` and `Content-Length` in headers.")
|
|
362
|
+
|
|
363
|
+
parser.add_argument('-l', '--list', action='store_true', default=False,
|
|
364
|
+
help="List files in the remote zip file")
|
|
365
|
+
parser.add_argument('-f', '--full-filepaths', action='store_true', default=False,
|
|
366
|
+
help="Recreate folder structure from zip file when extracting (instead of extracting the files to the current directory)")
|
|
367
|
+
parser.add_argument('-o', '--stdout', action='store_true', default=False,
|
|
368
|
+
help="Write files to stdout (if multiple files: concatenate them to stdout, in zipfile order)")
|
|
369
|
+
|
|
370
|
+
parser.add_argument("url", nargs=1, help="URL of the remote zip file")
|
|
371
|
+
parser.add_argument("files", nargs='*', help="Files to extract. If no filenames given, displays .zip contents (filenames and sizes). Each filename can be a wildcard glob.")
|
|
372
|
+
|
|
373
|
+
args = parser.parse_args()
|
|
374
|
+
|
|
375
|
+
rzf = RemoteZipFile(args.url[0])
|
|
376
|
+
if args.list or len(args.files) == 0:
|
|
377
|
+
list_files(rzf)
|
|
378
|
+
else:
|
|
379
|
+
for f in rzf.infolist():
|
|
380
|
+
download_file(f, rzf, args)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
if __name__ == '__main__':
|
|
385
|
+
main()
|
unzip-http-0.4/PKG-INFO
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: unzip-http
|
|
3
|
-
Version: 0.4
|
|
4
|
-
Summary: extract files from .zip files over http without downloading entire archive
|
|
5
|
-
Home-page: https://github.com/saulpw/unzip-http
|
|
6
|
-
Author: Saul Pwanson
|
|
7
|
-
License: UNKNOWN
|
|
8
|
-
Description: # unzip-http
|
|
9
|
-
|
|
10
|
-
Extract individual files from .zip files over http without downloading the entire archive.
|
|
11
|
-
|
|
12
|
-
## Install
|
|
13
|
-
|
|
14
|
-
pip install unzip-http
|
|
15
|
-
|
|
16
|
-
## Usage
|
|
17
|
-
|
|
18
|
-
unzip-http <url.zip> <filenames..>
|
|
19
|
-
|
|
20
|
-
Extract <filenames> from a remote .zip at `<url>` to stdout.
|
|
21
|
-
|
|
22
|
-
If no filenames given, displays .zip contents (filenames and sizes).
|
|
23
|
-
|
|
24
|
-
Each filename can be a wildcard glob; all matching files are concatenated and sent to stdout in zipfile order.
|
|
25
|
-
|
|
26
|
-
Note: HTTP server must send `Accept-Ranges: bytes` and `Content-Length` in headers.
|
|
27
|
-
|
|
28
|
-
# Python module `unzip_http`
|
|
29
|
-
|
|
30
|
-
import unzip_http
|
|
31
|
-
|
|
32
|
-
rzf = unzip_http.RemoteZipFile('https://example.com/foo.zip')
|
|
33
|
-
binfp = rzf.open('bar.bin')
|
|
34
|
-
txtfp = rzf.open_text('baz.txt')
|
|
35
|
-
|
|
36
|
-
# Credits
|
|
37
|
-
|
|
38
|
-
`unzip-http` was written by [Saul Pwanson](https://saul.pw) and made available for use under the MIT License.
|
|
39
|
-
|
|
40
|
-
Keywords: http zip unzip
|
|
41
|
-
Platform: UNKNOWN
|
|
42
|
-
Classifier: Development Status :: 4 - Beta
|
|
43
|
-
Classifier: Programming Language :: Python :: 3
|
|
44
|
-
Requires-Python: >=3.8
|
|
45
|
-
Description-Content-Type: text/markdown
|
unzip-http-0.4/unzip-http
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
'''
|
|
4
|
-
Usage:
|
|
5
|
-
unzip-http <url.zip> <filenames..>
|
|
6
|
-
|
|
7
|
-
Extract <filenames> from a remote .zip at <url> to stdout.
|
|
8
|
-
If no filenames given, displays .zip contents (filenames and sizes).
|
|
9
|
-
Each filename can be a wildcard glob; all matching files are concatenated and sent to stdout in zipfile order.
|
|
10
|
-
|
|
11
|
-
HTTP server must send `Accept-Ranges: bytes` and `Content-Length` in headers.
|
|
12
|
-
'''
|
|
13
|
-
|
|
14
|
-
import sys
|
|
15
|
-
import io
|
|
16
|
-
import time
|
|
17
|
-
import fnmatch
|
|
18
|
-
|
|
19
|
-
import unzip_http
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class StreamProgress:
|
|
23
|
-
def __init__(self, fp, name='', total=0):
|
|
24
|
-
self.name = name
|
|
25
|
-
self.fp = fp
|
|
26
|
-
self.total = total
|
|
27
|
-
self.start_time = time.time()
|
|
28
|
-
self.last_update = 0
|
|
29
|
-
self.amtread = 0
|
|
30
|
-
|
|
31
|
-
def read(self, n):
|
|
32
|
-
r = self.fp.read(n)
|
|
33
|
-
self.amtread += len(r)
|
|
34
|
-
now = time.time()
|
|
35
|
-
if now - self.last_update > 0.1:
|
|
36
|
-
self.last_update = now
|
|
37
|
-
|
|
38
|
-
elapsed_s = now - self.start_time
|
|
39
|
-
sys.stderr.write(f'\r{elapsed_s:.0f}s {self.amtread/10**6:.02f}/{self.total/10**6:.02f}MB ({self.amtread/10**6/elapsed_s:.02f} MB/s) {self.name}')
|
|
40
|
-
|
|
41
|
-
if not r:
|
|
42
|
-
sys.stderr.write('\n')
|
|
43
|
-
|
|
44
|
-
return r
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def main(url, *globs):
|
|
48
|
-
rzf = unzip_http.RemoteZipFile(url)
|
|
49
|
-
for f in rzf.infolist():
|
|
50
|
-
if not globs:
|
|
51
|
-
print(f'{f.compress_size} -> {f.file_size} {f.filename}')
|
|
52
|
-
elif any(fnmatch.fnmatch(f.filename, g) for g in globs):
|
|
53
|
-
fp = StreamProgress(rzf.open(f), name=f.filename, total=f.compress_size)
|
|
54
|
-
while r := fp.read(2**18):
|
|
55
|
-
sys.stdout.buffer.write(r)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
args = sys.argv[1:]
|
|
59
|
-
if not args:
|
|
60
|
-
print(__doc__, file=sys.stderr)
|
|
61
|
-
else:
|
|
62
|
-
main(*args)
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: unzip-http
|
|
3
|
-
Version: 0.4
|
|
4
|
-
Summary: extract files from .zip files over http without downloading entire archive
|
|
5
|
-
Home-page: https://github.com/saulpw/unzip-http
|
|
6
|
-
Author: Saul Pwanson
|
|
7
|
-
License: UNKNOWN
|
|
8
|
-
Description: # unzip-http
|
|
9
|
-
|
|
10
|
-
Extract individual files from .zip files over http without downloading the entire archive.
|
|
11
|
-
|
|
12
|
-
## Install
|
|
13
|
-
|
|
14
|
-
pip install unzip-http
|
|
15
|
-
|
|
16
|
-
## Usage
|
|
17
|
-
|
|
18
|
-
unzip-http <url.zip> <filenames..>
|
|
19
|
-
|
|
20
|
-
Extract <filenames> from a remote .zip at `<url>` to stdout.
|
|
21
|
-
|
|
22
|
-
If no filenames given, displays .zip contents (filenames and sizes).
|
|
23
|
-
|
|
24
|
-
Each filename can be a wildcard glob; all matching files are concatenated and sent to stdout in zipfile order.
|
|
25
|
-
|
|
26
|
-
Note: HTTP server must send `Accept-Ranges: bytes` and `Content-Length` in headers.
|
|
27
|
-
|
|
28
|
-
# Python module `unzip_http`
|
|
29
|
-
|
|
30
|
-
import unzip_http
|
|
31
|
-
|
|
32
|
-
rzf = unzip_http.RemoteZipFile('https://example.com/foo.zip')
|
|
33
|
-
binfp = rzf.open('bar.bin')
|
|
34
|
-
txtfp = rzf.open_text('baz.txt')
|
|
35
|
-
|
|
36
|
-
# Credits
|
|
37
|
-
|
|
38
|
-
`unzip-http` was written by [Saul Pwanson](https://saul.pw) and made available for use under the MIT License.
|
|
39
|
-
|
|
40
|
-
Keywords: http zip unzip
|
|
41
|
-
Platform: UNKNOWN
|
|
42
|
-
Classifier: Development Status :: 4 - Beta
|
|
43
|
-
Classifier: Programming Language :: Python :: 3
|
|
44
|
-
Requires-Python: >=3.8
|
|
45
|
-
Description-Content-Type: text/markdown
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|