Skip to content

Commit 67d6639

Browse files
committed
Improve _attempt_resumes_or_redownloads and update tests
1 parent 1d4249a commit 67d6639

File tree

3 files changed

+21
-59
lines changed

3 files changed

+21
-59
lines changed

news/13441.bugfix.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Add the full file from a resume retry download to the cache.
1+
Resumed downloads are saved to the HTTP cache like any other normal download.

src/pip/_internal/network/download.py

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -270,12 +270,7 @@ def _cache_resumed_download(
270270
requests can use the cache.
271271
"""
272272
url = download.link.url_without_fragment
273-
if url.startswith("https://"):
274-
adapter = self._session.adapters["https://"]
275-
elif url.startswith("http://"):
276-
adapter = self._session.adapters["http://"]
277-
else:
278-
return
273+
adapter = self._session.get_adapter(url)
279274

280275
# Check if the adapter is the CacheControlAdapter (i.e. caching is enabled)
281276
if not isinstance(adapter, CacheControlAdapter):
@@ -285,12 +280,9 @@ def _cache_resumed_download(
285280
return
286281

287282
# Check SafeFileCache is being used
288-
if not isinstance(adapter.cache, SafeFileCache):
289-
logger.debug(
290-
"Skipping resume download caching: "
291-
"cache doesn't support separate body storage"
292-
)
293-
return
283+
assert isinstance(
284+
adapter.cache, SafeFileCache
285+
), "separate body cache not in use!"
294286

295287
synthetic_request = PreparedRequest()
296288
synthetic_request.prepare(method="GET", url=url, headers={})
@@ -308,14 +300,12 @@ def _cache_resumed_download(
308300
preload_content=False,
309301
)
310302

311-
# Stream the file to cache
303+
# Save metadata and then stream the file contents to cache.
312304
cache_url = adapter.controller.cache_url(url)
313-
adapter.cache.set(
314-
cache_url,
315-
adapter.controller.serializer.dumps(
316-
synthetic_request, synthetic_response, b""
317-
),
305+
metadata_blob = adapter.controller.serializer.dumps(
306+
synthetic_request, synthetic_response, b""
318307
)
308+
adapter.cache.set(cache_url, metadata_blob)
319309
download.output_file.flush()
320310
with open(download.output_file.name, "rb") as f:
321311
adapter.cache.set_body_from_io(cache_url, f)

tests/unit/test_network_download.py

Lines changed: 12 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,14 @@
99

1010
from pip._internal.exceptions import IncompleteDownloadError
1111
from pip._internal.models.link import Link
12-
from pip._internal.network.cache import SafeFileCache
1312
from pip._internal.network.download import (
1413
Downloader,
1514
_get_http_response_size,
1615
_log_download,
1716
parse_content_disposition,
1817
sanitize_content_filename,
1918
)
20-
from pip._internal.network.session import CacheControlAdapter, PipSession
19+
from pip._internal.network.session import PipSession
2120
from pip._internal.network.utils import HEADERS
2221

2322
from tests.lib.requests_mocks import MockResponse
@@ -355,8 +354,9 @@ def test_downloader(
355354

356355
def test_resumed_download_caching(tmpdir: Path) -> None:
357356
"""Test that resumed downloads are cached properly for future use."""
358-
session = PipSession()
359-
link = Link("http://example.com/foo.tgz")
357+
cache_dir = tmpdir / "cache"
358+
session = PipSession(cache=str(cache_dir))
359+
link = Link("https://example.com/foo.tgz")
360360
downloader = Downloader(session, "on", resume_retries=5)
361361

362362
# Mock an incomplete download followed by a successful resume
@@ -371,27 +371,8 @@ def test_resumed_download_caching(tmpdir: Path) -> None:
371371
responses = [incomplete_resp, resume_resp]
372372
_http_get_mock = MagicMock(side_effect=responses)
373373

374-
# Mock the session's adapters to have a cache controller
375-
mock_adapter = MagicMock(spec=CacheControlAdapter)
376-
mock_controller = MagicMock()
377-
mock_adapter.controller = mock_controller
378-
mock_controller.cache_url = MagicMock(return_value="cache_key")
379-
mock_controller.serializer = MagicMock()
380-
mock_controller.serializer.dumps = MagicMock(return_value=b"serialized_data")
381-
382-
# Mock the cache to be a SafeFileCache
383-
mock_cache = MagicMock(spec=SafeFileCache)
384-
mock_adapter.cache = mock_cache
385-
386-
# Create a mock for the session adapters
387-
adapters_mock = MagicMock()
388-
adapters_mock.__getitem__ = MagicMock(return_value=mock_adapter)
389-
390-
with (
391-
patch.object(Downloader, "_http_get", _http_get_mock),
392-
patch.object(session, "adapters", adapters_mock),
393-
):
394-
374+
with patch.object(Downloader, "_http_get", _http_get_mock):
375+
# Perform the download (incomplete then resumed)
395376
filepath, _ = downloader(link, str(tmpdir))
396377

397378
# Verify the file was downloaded correctly
@@ -400,18 +381,9 @@ def test_resumed_download_caching(tmpdir: Path) -> None:
400381
expected_bytes = b"0cfa7e9d-1868-4dd7-9fb3-f2561d5dfd89"
401382
assert downloaded_bytes == expected_bytes
402383

403-
# Verify that cache.set was called for metadata
404-
mock_cache.set.assert_called_once()
405-
406-
# Verify that set_body_from_io was called for streaming the body
407-
mock_cache.set_body_from_io.assert_called_once()
408-
409-
# Verify the call arguments
410-
set_call_args = mock_cache.set.call_args
411-
assert set_call_args[0][0] == "cache_key" # First argument should be cache_key
412-
413-
set_body_call_args = mock_cache.set_body_from_io.call_args
414-
415-
assert set_body_call_args[0][0] == "cache_key"
416-
assert hasattr(set_body_call_args[0][1], "read")
417-
assert set_body_call_args[0][1].name == filepath
384+
# Verify that the cache directory was created and contains cache files
385+
# The resumed download should have been cached for future use
386+
assert cache_dir.exists()
387+
cache_files = list(cache_dir.rglob("*"))
388+
# Should have cache files (both metadata and body files)
389+
assert len([f for f in cache_files if f.is_file()]) == 2

0 commit comments

Comments
 (0)