Skip to content

Commit

Permalink
Better incomplete download error message
Browse files Browse the repository at this point in the history
  • Loading branch information
yichi-yang authored and gmargaritis committed Sep 26, 2024
1 parent 0617d7c commit a091ca1
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 16 deletions.
33 changes: 33 additions & 0 deletions src/pip/_internal/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -775,3 +775,36 @@ def __init__(self, *, distribution: "BaseDistribution") -> None:
),
hint_stmt=None,
)

class IncompleteDownloadError(DiagnosticPipError):
"""Raised when the downloader receives fewer bytes than advertised
in the Content-Length header."""

reference = "incomplete-download-error"

def __init__(
self, link: str, resume_incomplete: bool, resume_attempts: int
) -> None:
if resume_incomplete:
message = (
"Download failed after {} attempts because not enough bytes are"
" received. The incomplete file has been cleaned up."
).format(resume_attempts)
hint = "Use --incomplete-download-retries to configure resume retry limit."
else:
message = (
"Download failed because not enough bytes are received."
" The incomplete file has been cleaned up."
)
hint = (
"Use --incomplete-downloads=resume to make pip retry failed download."
)

super().__init__(
message=message,
context="File: {}\n"
"Resume failed download: {}\n"
"Resume retry limit: {}".format(link, resume_incomplete, resume_attempts),
hint_stmt=hint,
note_stmt="This is an issue with network connectivity, not pip.",
)
19 changes: 4 additions & 15 deletions src/pip/_internal/network/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from pip._vendor.requests.models import Response

from pip._internal.cli.progress_bars import get_download_progress_renderer
from pip._internal.exceptions import NetworkConnectionError
from pip._internal.exceptions import IncompleteDownloadError, NetworkConnectionError
from pip._internal.models.index import PyPI
from pip._internal.models.link import Link
from pip._internal.network.cache import is_from_cache
Expand Down Expand Up @@ -229,21 +229,10 @@ def __call__(self, link: Link, location: str) -> Tuple[str, str]:
content_file.write(chunk)

if total_length is not None and bytes_received < total_length:
if self._resume_incomplete:
logger.critical(
"Failed to download %s after %d resumption attempts.",
link,
self._resume_attempts,
)
else:
logger.critical(
"Failed to download %s."
" Set --incomplete-downloads=resume to automatically"
"resume incomplete download.",
link,
)
os.remove(filepath)
raise RuntimeError("Incomplete download")
raise IncompleteDownloadError(
str(link), self._resume_incomplete, self._resume_attempts
)

content_type = resp.headers.get("Content-Type", "")
return filepath, content_type
Expand Down
3 changes: 2 additions & 1 deletion tests/unit/test_network_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import pytest

from pip._internal.exceptions import IncompleteDownloadError
from pip._internal.models.link import Link
from pip._internal.network.download import (
Downloader,
Expand Down Expand Up @@ -349,7 +350,7 @@ def test_downloader(
if expected_bytes is None:
remove = MagicMock(return_value=None)
with patch("os.remove", remove):
with pytest.raises(RuntimeError):
with pytest.raises(IncompleteDownloadError):
downloader(link, str(tmpdir))
# Make sure the incomplete file is removed
remove.assert_called_once()
Expand Down

0 comments on commit a091ca1

Please sign in to comment.