Skip to content

Commit 997c5a7

Browse files
authored
Merge pull request #10625 from albertosottile/ignore_yanked
2 parents 79e6237 + 7057423 commit 997c5a7

4 files changed

Lines changed: 44 additions & 4 deletions

File tree

news/10617.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Prevent pip from installing yanked releases unless
2+
explicitely pinned via the ``==`` or ``===`` operators.

src/pip/_internal/resolution/resolvelib/factory.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,14 +273,27 @@ def iter_index_candidate_infos() -> Iterator[IndexCandidateInfo]:
273273
)
274274
icans = list(result.iter_applicable())
275275

276-
# PEP 592: Yanked releases must be ignored unless only yanked
277-
# releases can satisfy the version range. So if this is false,
278-
# all yanked icans need to be skipped.
276+
# PEP 592: Yanked releases are ignored unless the specifier
277+
# explicitely pins a version (via '==' or '===') that can be
278+
# solely satisfied by a yanked release.
279279
all_yanked = all(ican.link.is_yanked for ican in icans)
280280

281+
def is_pinned(specifier: SpecifierSet) -> bool:
282+
for sp in specifier:
283+
if sp.operator == "===":
284+
return True
285+
if sp.operator != "==":
286+
continue
287+
if sp.version.endswith(".*"):
288+
continue
289+
return True
290+
return False
291+
292+
pinned = is_pinned(specifier)
293+
281294
# PackageFinder returns earlier versions first, so we reverse.
282295
for ican in reversed(icans):
283-
if not all_yanked and ican.link.is_yanked:
296+
if not (all_yanked and pinned) and ican.link.is_yanked:
284297
continue
285298
func = functools.partial(
286299
self._make_candidate_from_link,
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<html>
2+
<body>
3+
<a data-yanked="test reason message" href="../../../packages/simple-1.0.tar.gz">simple-1.0.tar.gz</a>
4+
<a data-yanked="test reason message" href="../../../packages/simple-2.0.tar.gz">simple-2.0.tar.gz</a>
5+
<a data-yanked="test reason message" href="../../../packages/simple-3.0.tar.gz">simple-3.0.tar.gz</a>
6+
</body>
7+
</html>

tests/functional/test_install.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2172,6 +2172,24 @@ def test_install_yanked_file_and_print_warning(
21722172
assert "Successfully installed simple-3.0\n" in result.stdout, str(result)
21732173

21742174

2175+
def test_error_all_yanked_files_and_no_pin(script, data):
2176+
"""
2177+
Test raising an error if there are only "yanked" files available and no pin
2178+
"""
2179+
result = script.pip(
2180+
"install",
2181+
"simple",
2182+
"--index-url",
2183+
data.index_url("yanked_all"),
2184+
expect_error=True,
2185+
)
2186+
# Make sure an error is raised
2187+
assert (
2188+
result.returncode == 1
2189+
and "ERROR: No matching distribution found for simple\n" in result.stderr
2190+
), str(result)
2191+
2192+
21752193
@pytest.mark.parametrize(
21762194
"install_args",
21772195
[

0 commit comments

Comments
 (0)