1
1
"""Lazy ZIP over HTTP"""
2
2
3
- __all__ = ['LazyZip ' ]
3
+ __all__ = ['dist_from_wheel_url ' ]
4
4
5
5
from bisect import bisect_left , bisect_right
6
6
from contextlib import contextmanager
12
12
13
13
from pip ._internal .network .utils import HEADERS , response_chunks
14
14
from pip ._internal .utils .typing import MYPY_CHECK_RUNNING
15
+ from pip ._internal .utils .wheel import pkg_resources_distribution_for_wheel
15
16
16
17
if MYPY_CHECK_RUNNING :
17
18
from typing import Any , Dict , Iterator , List , Optional , Tuple
18
19
20
+ from pip ._vendor .pkg_resources import Distribution
19
21
from pip ._vendor .requests .models import Response
20
22
21
23
from pip ._internal .network .session import PipSession
22
24
23
25
24
- class LazyZip :
26
+ def dist_from_wheel_url (name , url , session ):
27
+ # type: (str, str, PipSession) -> Distribution
28
+ """Return a pkg_resources.Distribution from the given wheel URL.
29
+
30
+ """
31
+ with LazyZipOverHTTP (url , session ) as wheel :
32
+ # For read-only ZIP files, ZipFile only needs methods read,
33
+ # seek, seekable and tell, not the whole IO protocol.
34
+ zip_file = ZipFile (wheel ) # type: ignore
35
+ # After context manager exit, wheel.name
36
+ # is an invalid file by intention.
37
+ return pkg_resources_distribution_for_wheel (zip_file , name , wheel .name )
38
+
39
+
40
+ class LazyZipOverHTTP :
25
41
"""File-like object mapped to a ZIP file over HTTP.
26
42
27
43
This uses HTTP range requests to lazily fetch the file's content,
28
44
which is supposed to be fed to ZipFile.
29
45
"""
30
46
31
- def __init__ (self , session , url , chunk_size = CONTENT_CHUNK_SIZE ):
32
- # type: (PipSession, str , int) -> None
47
+ def __init__ (self , url , session , chunk_size = CONTENT_CHUNK_SIZE ):
48
+ # type: (str, PipSession , int) -> None
33
49
head = session .head (url , headers = HEADERS )
34
50
head .raise_for_status ()
35
51
assert head .status_code == 200
@@ -39,7 +55,9 @@ def __init__(self, session, url, chunk_size=CONTENT_CHUNK_SIZE):
39
55
self .truncate (self ._length )
40
56
self ._left = [] # type: List[int]
41
57
self ._right = [] # type: List[int]
42
- self ._check_zip ('bytes' in head .headers .get ('Accept-Ranges' , 'none' ))
58
+ if 'bytes' not in head .headers .get ('Accept-Ranges' , 'none' ):
59
+ raise RuntimeError ('range request is not supported' )
60
+ self ._check_zip ()
43
61
44
62
@property
45
63
def mode (self ):
@@ -50,7 +68,7 @@ def mode(self):
50
68
@property
51
69
def name (self ):
52
70
# type: () -> str
53
- """File name ."""
71
+ """Path to the underlying file ."""
54
72
return self ._file .name
55
73
56
74
def seekable (self ):
@@ -120,7 +138,7 @@ def writable(self):
120
138
return False
121
139
122
140
def __enter__ (self ):
123
- # type: () -> LazyZip
141
+ # type: () -> LazyZipOverHTTP
124
142
self ._file .__enter__ ()
125
143
return self
126
144
@@ -141,21 +159,16 @@ def _stay(self):
141
159
finally :
142
160
self .seek (pos )
143
161
144
- def _check_zip (self , range_request ):
145
- # type: (bool ) -> None
162
+ def _check_zip (self ):
163
+ # type: () -> None
146
164
"""Check and download until the file is a valid ZIP."""
147
165
end = self ._length - 1
148
- if not range_request :
149
- self ._download (0 , end )
150
- return
151
166
for start in reversed (range (0 , end , self ._chunk_size )):
152
167
self ._download (start , end )
153
168
with self ._stay ():
154
169
try :
155
170
# For read-only ZIP files, ZipFile only needs
156
171
# methods read, seek, seekable and tell.
157
- # The best way to type-hint in this case is to use
158
- # Python 3.8+ typing.Protocol.
159
172
ZipFile (self ) # type: ignore
160
173
except BadZipfile :
161
174
pass
0 commit comments