diff --git a/news/14187.feature.rst b/news/14187.feature.rst new file mode 100644 index 0000000000..1471d35c6b --- /dev/null +++ b/news/14187.feature.rst @@ -0,0 +1,3 @@ +Support ``--no-binary`` with ``-r pylock.toml``. Before, pip would get the +sdist from the index instead of the locked URL, or fail if no wheel were +available available in the lock file. diff --git a/src/pip/_internal/cli/req_command.py b/src/pip/_internal/cli/req_command.py index f58a970613..0e218ab5f2 100644 --- a/src/pip/_internal/cli/req_command.py +++ b/src/pip/_internal/cli/req_command.py @@ -368,7 +368,7 @@ def get_requirements( "without prior warning." ) for package, package_dist in select_from_pylock_path_or_url( - filename, session=session + filename, session=session, format_control=options.format_control ): req_to_add, locked_link = install_req_from_pylock_package( package, diff --git a/src/pip/_internal/utils/pylock.py b/src/pip/_internal/utils/pylock.py index 70df98693b..ab1937efde 100644 --- a/src/pip/_internal/utils/pylock.py +++ b/src/pip/_internal/utils/pylock.py @@ -17,6 +17,7 @@ Pylock, is_valid_pylock_path, ) +from pip._vendor.packaging.utils import NormalizedName from pip._vendor.packaging.version import Version from pip._internal.exceptions import DiagnosticPipError, InstallationError @@ -25,6 +26,7 @@ from pip._internal.utils.urls import path_to_url, url_to_path if TYPE_CHECKING: + from pip._internal.index.package_finder import FormatControl from pip._internal.network.session import PipSession from pip._internal.req.req_install import InstallRequirement @@ -267,6 +269,7 @@ def _get_pylock_path_or_url_content(path_or_url: str, session: PipSession) -> st def select_from_pylock_path_or_url( pylock_path_or_url: str, session: PipSession, + format_control: FormatControl | None, ) -> Iterator[ tuple[ Package, @@ -289,10 +292,16 @@ def select_from_pylock_path_or_url( f"Invalid pylock file {pylock_path_or_url!r}: {exc}" ) from exc + def prefer_sdist_predicate(name: NormalizedName) -> bool: + if format_control is None: + return False + allowed_formats = format_control.get_allowed_formats(name) + if "source" in allowed_formats and "binary" not in allowed_formats: + return True + return False + try: - # TODO: for completeness, pylock.select should support preferring sdist - # over wheels to support --no-binary - yield from lock.select() + yield from lock.select(prefer_sdist_predicate=prefer_sdist_predicate) except Exception as exc: raise InstallationError( f"Cannot select requirements from pylock file {pylock_path_or_url!r}: {exc}" diff --git a/tests/data/lockfiles/pylock.onepackage.toml b/tests/data/lockfiles/pylock.onepackage.toml new file mode 100644 index 0000000000..d75d0d4a09 --- /dev/null +++ b/tests/data/lockfiles/pylock.onepackage.toml @@ -0,0 +1,19 @@ +lock-version = "1.0" +created-by = "pip" + +[[packages]] +name = "simplewheel" +version = "2.0" + +[packages.sdist] +path = "../packages/simplewheel-2.0.tar.gz" + +[packages.sdist.hashes] +sha256 = "d1e1fceaf0b5eac764843e4a16ba62394a4fd85896c87a4439aab0af087af1ed" + +[[packages.wheels]] +name = "simplewheel-2.0-1-py2.py3-none-any.whl" +path = "../packages/simplewheel-2.0-1-py2.py3-none-any.whl" + +[packages.wheels.hashes] +sha256 = "71e1ca6b16ae3382a698c284013f66504f2581099b2ce4801f60e9536236ceee" diff --git a/tests/data/packages/simplewheel-2.0.tar.gz b/tests/data/packages/simplewheel-2.0.tar.gz new file mode 100644 index 0000000000..a19138d193 Binary files /dev/null and b/tests/data/packages/simplewheel-2.0.tar.gz differ diff --git a/tests/functional/test_install_pylock_reqs.py b/tests/functional/test_install_pylock_reqs.py index 1f94754cd5..9b6bb5c2b9 100644 --- a/tests/functional/test_install_pylock_reqs.py +++ b/tests/functional/test_install_pylock_reqs.py @@ -208,6 +208,35 @@ def test_install_pylock_select_error( assert "Cannot select requirements from pylock file" in result.stderr +def test_install_pylock_no_binary_selects_sdist( + script: PipTestEnvironment, + data: TestData, + tmp_path: Path, +) -> None: + pylock_path = data.lockfiles.joinpath("pylock.onepackage.toml") + report = tmp_path / "report.json" + result = script.pip( + "install", + "--no-index", + "--find-links", + data.common_wheels, # to obtain build backend to build sdist + "--dry-run", + "-r", + pylock_path, + "--no-binary=simplewheel", + "--report", + report, + allow_stderr_warning=True, + ) + assert "experimental" in result.stderr + assert "Would install simplewheel-2.0" in result.stdout + # check that the sdist was selected + report_json = json.loads(report.read_text()) + installed = report_json["install"] + assert installed[0]["metadata"]["name"] == "simplewheel" + assert installed[0]["download_info"]["url"].endswith(".tar.gz") + + def test_install_pylock_no_binary( script: PipTestEnvironment, data: TestData,