@@ -41,9 +41,10 @@ def _prepare_download(
41
41
resp : Response ,
42
42
link : Link ,
43
43
progress_bar : str ,
44
- total_length : Optional [int ],
45
44
range_start : Optional [int ] = 0 ,
46
45
) -> Iterable [bytes ]:
46
+ total_length = _get_http_response_size (resp )
47
+
47
48
if link .netloc == PyPI .file_storage_domain :
48
49
url = link .show_url
49
50
else :
@@ -170,21 +171,14 @@ def __init__(
170
171
def __call__ (self , link : Link , location : str ) -> Tuple [str , str ]:
171
172
"""Download the file given by link into location."""
172
173
resp = _http_get_download (self ._session , link )
173
- total_length = _get_http_response_size (resp )
174
174
content_type = resp .headers .get ("Content-Type" , "" )
175
175
176
176
filename = _get_http_response_filename (resp , link )
177
177
filepath = os .path .join (location , filename )
178
178
179
179
with open (filepath , "wb" ) as content_file :
180
- bytes_received = self ._process_response (
181
- resp , link , content_file , 0 , total_length
182
- )
183
- # If possible, check for an incomplete download and attempt resuming.
184
- if total_length and bytes_received < total_length :
185
- self ._attempt_resume (
186
- resp , link , content_file , total_length , bytes_received
187
- )
180
+ bytes_received = self ._process_response (resp , link , content_file , 0 )
181
+ self ._attempt_resume_if_needed (resp , link , content_file , bytes_received )
188
182
189
183
return filepath , content_type
190
184
@@ -194,11 +188,11 @@ def _process_response(
194
188
link : Link ,
195
189
content_file : BinaryIO ,
196
190
bytes_received : int ,
197
- total_length : Optional [int ],
198
191
) -> int :
199
192
"""Process the response and write the chunks to the file."""
193
+ total_length = _get_http_response_size (resp )
200
194
chunks = _prepare_download (
201
- resp , link , self ._progress_bar , total_length , range_start = bytes_received
195
+ resp , link , self ._progress_bar , range_start = bytes_received
202
196
)
203
197
return self ._write_chunks_to_file (
204
198
chunks , content_file , allow_partial = bool (total_length )
@@ -223,15 +217,15 @@ def _write_chunks_to_file(
223
217
224
218
return bytes_received
225
219
226
- def _attempt_resume (
220
+ def _attempt_resume_if_needed (
227
221
self ,
228
222
resp : Response ,
229
223
link : Link ,
230
224
content_file : BinaryIO ,
231
- total_length : Optional [int ],
232
225
bytes_received : int ,
233
226
) -> None :
234
- """Attempt to resume the download if connection was dropped."""
227
+ """Attempt to resume/retry the download if connection was dropped."""
228
+ total_length = _get_http_response_size (resp )
235
229
etag_or_last_modified = _get_http_response_etag_or_last_modified (resp )
236
230
237
231
attempts_left = self ._resume_retries
@@ -246,28 +240,30 @@ def _attempt_resume(
246
240
)
247
241
248
242
try :
243
+ # Try to resume the download using a HTTP range request.
249
244
resume_resp = _http_get_download (
250
245
self ._session ,
251
246
link ,
252
247
range_start = bytes_received ,
253
248
if_range = etag_or_last_modified ,
254
249
)
255
250
256
- # If the server responded with 200 (e.g. when the file has been
257
- # modified on the server or range requests are not supported by
258
- # the server), reset the download to start from the beginning.
259
- restart = resume_resp .status_code != HTTPStatus .PARTIAL_CONTENT
260
- if restart :
251
+ # Fallback: if the server responded with 200 (i.e., the file has
252
+ # since been modified or range requests are unsupported) or any
253
+ # other unexpected status, restart the download from the beginning.
254
+ must_restart = resume_resp .status_code != HTTPStatus .PARTIAL_CONTENT
255
+ if must_restart :
261
256
bytes_received , total_length , etag_or_last_modified = (
262
257
self ._reset_download_state (resume_resp , content_file )
263
258
)
264
259
265
260
bytes_received += self ._process_response (
266
- resume_resp , link , content_file , bytes_received , total_length
261
+ resume_resp , link , content_file , bytes_received
267
262
)
268
263
except (ConnectionError , ReadTimeoutError , OSError ):
269
264
continue
270
265
266
+ # No more resume attempts. Raise an error if the download is still incomplete.
271
267
if total_length and bytes_received < total_length :
272
268
os .remove (content_file .name )
273
269
raise IncompleteDownloadError (
0 commit comments