diff --git a/news/13395.feature.rst b/news/13395.feature.rst new file mode 100644 index 00000000000..86dbc0dfb2c --- /dev/null +++ b/news/13395.feature.rst @@ -0,0 +1 @@ +add size attribute to pylock.toml diff --git a/src/pip/_internal/models/link.py b/src/pip/_internal/models/link.py index 87651c76e25..2a8567d717d 100644 --- a/src/pip/_internal/models/link.py +++ b/src/pip/_internal/models/link.py @@ -508,6 +508,23 @@ def hash_name(self) -> str | None: def show_url(self) -> str: return posixpath.basename(self._url.split("#", 1)[0].split("?", 1)[0]) + @property + def size(self) -> int | None: + """Fetch the size of the file from HTTP headers if available.""" + from pip._internal.network.session import PipSession + + try: + session = PipSession() + response = session.head(self.url, allow_redirects=True) + response.raise_for_status() + # Check if the 'Content-Length' header is present + size = response.headers.get("Content-Length") + if size: + return int(size) + except ValueError as e: + logger.warning("Could not fetch size for %s: %s", self.url, e) + return None + @property def is_file(self) -> bool: return self.scheme == "file" diff --git a/src/pip/_internal/models/pylock.py b/src/pip/_internal/models/pylock.py index e7df01a2bc3..474dd76dfa6 100644 --- a/src/pip/_internal/models/pylock.py +++ b/src/pip/_internal/models/pylock.py @@ -47,7 +47,7 @@ class PackageDirectory: class PackageArchive: url: str | None # (not supported) path: Optional[str] - # (not supported) size: Optional[int] + # (not supported) size: int | None # (not supported) upload_time: Optional[datetime] hashes: dict[str, str] subdirectory: str | None @@ -59,7 +59,7 @@ class PackageSdist: # (not supported) upload_time: Optional[datetime] url: str | None # (not supported) path: Optional[str] - # (not supported) size: Optional[int] + size: int | None hashes: dict[str, str] @@ -69,7 +69,7 @@ class PackageWheel: # (not supported) upload_time: Optional[datetime] url: str | None # (not supported) path: Optional[str] - # (not supported) size: Optional[int] + size: int | None hashes: dict[str, str] @@ -125,6 +125,7 @@ def from_install_requirement(cls, ireq: InstallRequirement, base_dir: Path) -> S raise NotImplementedError() package.archive = PackageArchive( url=download_info.url, + # size=link.size, hashes=download_info.info.hashes, subdirectory=download_info.subdirectory, ) @@ -142,6 +143,7 @@ def from_install_requirement(cls, ireq: InstallRequirement, base_dir: Path) -> S PackageWheel( name=link.filename, url=download_info.url, + size=link.size, hashes=download_info.info.hashes, ) ] @@ -149,6 +151,7 @@ def from_install_requirement(cls, ireq: InstallRequirement, base_dir: Path) -> S package.sdist = PackageSdist( name=link.filename, url=download_info.url, + size=link.size, hashes=download_info.info.hashes, ) else: diff --git a/tests/functional/test_lock.py b/tests/functional/test_lock.py index 727bd72c8a0..a8da8e38ef9 100644 --- a/tests/functional/test_lock.py +++ b/tests/functional/test_lock.py @@ -33,6 +33,7 @@ def test_lock_wheel_from_findlinks( "wheels": [ { "name": "simplewheel-2.0-1-py2.py3-none-any.whl", + "size": 2156, "url": path_to_url( str( shared_data.root @@ -80,6 +81,7 @@ def test_lock_sdist_from_findlinks( ), }, "name": "simple-2.0.tar.gz", + "size": 673, "url": path_to_url( str(shared_data.root / "packages" / "simple-2.0.tar.gz") ), @@ -171,6 +173,7 @@ def test_lock_local_editable_with_dep( / "simplewheel-2.0-1-py2.py3-none-any.whl" ) ), + "size": 2156, "hashes": { "sha256": ( "71e1ca6b16ae3382a698c284013f6650" @@ -234,3 +237,17 @@ def test_lock_archive(script: PipTestEnvironment, shared_data: TestData) -> None }, }, ] + + +def test_lock_includes_size(script: PipTestEnvironment, shared_data: TestData) -> None: + script.pip( + "lock", + "simplewheel==2.0", + "--no-index", + "--find-links", + str(shared_data.root / "packages/"), + expect_stderr=True, # for the experimental warning + ) + pylock = tomllib.loads(script.scratch_path.joinpath("pylock.toml").read_text()) + assert "size" in pylock["packages"][0]["wheels"][0] + assert pylock["packages"][0]["wheels"][0]["size"] > 0 diff --git a/tests/unit/test_link.py b/tests/unit/test_link.py index 92e92dffa3d..f82f28dc834 100644 --- a/tests/unit/test_link.py +++ b/tests/unit/test_link.py @@ -1,5 +1,7 @@ from __future__ import annotations +from unittest.mock import patch + import pytest from pip._internal.models.link import Link, links_equivalent @@ -188,6 +190,12 @@ def test_is_vcs(self, url: str, expected: bool) -> None: link = Link(url) assert link.is_vcs is expected + def test_link_size(self) -> None: + with patch("pip._internal.network.session.PipSession.head") as mock_head: + mock_head.return_value.headers = {"Content-Length": "12345"} + link = Link(url="https://example.com/package.tar.gz") + assert link.size == 12345 + @pytest.mark.parametrize( "url1, url2",