Skip to content

Commit ff8dbb4

Browse files
authored
Merge pull request #10867 from mauritsvanrees/maurits-topoligical-weights-requirements-only-issue-10851
2 parents cf4655f + b3f5cad commit ff8dbb4

File tree

3 files changed

+81
-22
lines changed

3 files changed

+81
-22
lines changed

news/10851.bugfix.rst

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Only calculate topological installation order, for packages that are going to be installed/upgraded.
2+
3+
This fixes an `AssertionError` that occured when determining installation order, for a very specific combination of upgrading-already-installed-package + change of dependencies + fetching some packages from a package index. This combination was especially common in Read the Docs' builds.

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

+14-8
Original file line numberDiff line numberDiff line change
@@ -185,10 +185,7 @@ def get_installation_order(
185185
return []
186186

187187
graph = self._result.graph
188-
weights = get_topological_weights(
189-
graph,
190-
expected_node_count=len(self._result.mapping) + 1,
191-
)
188+
weights = get_topological_weights(graph, set(req_set.requirements.keys()))
192189

193190
sorted_items = sorted(
194191
req_set.requirements.items(),
@@ -199,7 +196,7 @@ def get_installation_order(
199196

200197

201198
def get_topological_weights(
202-
graph: "DirectedGraph[Optional[str]]", expected_node_count: int
199+
graph: "DirectedGraph[Optional[str]]", requirement_keys: Set[str]
203200
) -> Dict[Optional[str], int]:
204201
"""Assign weights to each node based on how "deep" they are.
205202
@@ -222,6 +219,9 @@ def get_topological_weights(
222219
don't get stuck in a cycle.
223220
224221
When assigning weight, the longer path (i.e. larger length) is preferred.
222+
223+
We are only interested in the weights of packages that are in the
224+
requirement_keys.
225225
"""
226226
path: Set[Optional[str]] = set()
227227
weights: Dict[Optional[str], int] = {}
@@ -237,6 +237,9 @@ def visit(node: Optional[str]) -> None:
237237
visit(child)
238238
path.remove(node)
239239

240+
if node not in requirement_keys:
241+
return
242+
240243
last_known_parent_count = weights.get(node, 0)
241244
weights[node] = max(last_known_parent_count, len(path))
242245

@@ -262,6 +265,8 @@ def visit(node: Optional[str]) -> None:
262265
# Calculate the weight for the leaves.
263266
weight = len(graph) - 1
264267
for leaf in leaves:
268+
if leaf not in requirement_keys:
269+
continue
265270
weights[leaf] = weight
266271
# Remove the leaves from the graph, making it simpler.
267272
for leaf in leaves:
@@ -271,9 +276,10 @@ def visit(node: Optional[str]) -> None:
271276
# `None` is guaranteed to be the root node by resolvelib.
272277
visit(None)
273278

274-
# Sanity checks
275-
assert weights[None] == 0
276-
assert len(weights) == expected_node_count
279+
# Sanity check: all requirement keys should be in the weights,
280+
# and no other keys should be in the weights.
281+
difference = set(weights.keys()).difference(requirement_keys)
282+
assert not difference, difference
277283

278284
return weights
279285

tests/unit/resolution_resolvelib/test_resolver.py

+64-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Dict, List, Optional, Tuple, cast
1+
from typing import Dict, List, Optional, Set, Tuple, cast
22
from unittest import mock
33

44
import pytest
@@ -103,7 +103,7 @@ def test_new_resolver_get_installation_order(
103103

104104

105105
@pytest.mark.parametrize(
106-
"name, edges, expected_weights",
106+
"name, edges, requirement_keys, expected_weights",
107107
[
108108
(
109109
# From https://github.com/pypa/pip/pull/8127#discussion_r414564664
@@ -116,7 +116,8 @@ def test_new_resolver_get_installation_order(
116116
("three", "four"),
117117
("four", "five"),
118118
],
119-
{None: 0, "five": 5, "four": 4, "one": 4, "three": 2, "two": 1},
119+
{"one", "two", "three", "four", "five"},
120+
{"five": 5, "four": 4, "one": 4, "three": 2, "two": 1},
120121
),
121122
(
122123
"linear",
@@ -127,7 +128,20 @@ def test_new_resolver_get_installation_order(
127128
("three", "four"),
128129
("four", "five"),
129130
],
130-
{None: 0, "one": 1, "two": 2, "three": 3, "four": 4, "five": 5},
131+
{"one", "two", "three", "four", "five"},
132+
{"one": 1, "two": 2, "three": 3, "four": 4, "five": 5},
133+
),
134+
(
135+
"linear AND restricted",
136+
[
137+
(None, "one"),
138+
("one", "two"),
139+
("two", "three"),
140+
("three", "four"),
141+
("four", "five"),
142+
],
143+
{"one", "three", "five"},
144+
{"one": 1, "three": 3, "five": 5},
131145
),
132146
(
133147
"linear AND root -> two",
@@ -139,7 +153,8 @@ def test_new_resolver_get_installation_order(
139153
("four", "five"),
140154
(None, "two"),
141155
],
142-
{None: 0, "one": 1, "two": 2, "three": 3, "four": 4, "five": 5},
156+
{"one", "two", "three", "four", "five"},
157+
{"one": 1, "two": 2, "three": 3, "four": 4, "five": 5},
143158
),
144159
(
145160
"linear AND root -> three",
@@ -151,7 +166,8 @@ def test_new_resolver_get_installation_order(
151166
("four", "five"),
152167
(None, "three"),
153168
],
154-
{None: 0, "one": 1, "two": 2, "three": 3, "four": 4, "five": 5},
169+
{"one", "two", "three", "four", "five"},
170+
{"one": 1, "two": 2, "three": 3, "four": 4, "five": 5},
155171
),
156172
(
157173
"linear AND root -> four",
@@ -163,7 +179,8 @@ def test_new_resolver_get_installation_order(
163179
("four", "five"),
164180
(None, "four"),
165181
],
166-
{None: 0, "one": 1, "two": 2, "three": 3, "four": 4, "five": 5},
182+
{"one", "two", "three", "four", "five"},
183+
{"one": 1, "two": 2, "three": 3, "four": 4, "five": 5},
167184
),
168185
(
169186
"linear AND root -> five",
@@ -175,7 +192,8 @@ def test_new_resolver_get_installation_order(
175192
("four", "five"),
176193
(None, "five"),
177194
],
178-
{None: 0, "one": 1, "two": 2, "three": 3, "four": 4, "five": 5},
195+
{"one", "two", "three", "four", "five"},
196+
{"one": 1, "two": 2, "three": 3, "four": 4, "five": 5},
179197
),
180198
(
181199
"linear AND one -> four",
@@ -187,7 +205,8 @@ def test_new_resolver_get_installation_order(
187205
("four", "five"),
188206
("one", "four"),
189207
],
190-
{None: 0, "one": 1, "two": 2, "three": 3, "four": 4, "five": 5},
208+
{"one", "two", "three", "four", "five"},
209+
{"one": 1, "two": 2, "three": 3, "four": 4, "five": 5},
191210
),
192211
(
193212
"linear AND two -> four",
@@ -199,7 +218,8 @@ def test_new_resolver_get_installation_order(
199218
("four", "five"),
200219
("two", "four"),
201220
],
202-
{None: 0, "one": 1, "two": 2, "three": 3, "four": 4, "five": 5},
221+
{"one", "two", "three", "four", "five"},
222+
{"one": 1, "two": 2, "three": 3, "four": 4, "five": 5},
203223
),
204224
(
205225
"linear AND four -> one (cycle)",
@@ -211,7 +231,8 @@ def test_new_resolver_get_installation_order(
211231
("four", "five"),
212232
("four", "one"),
213233
],
214-
{None: 0, "one": 1, "two": 2, "three": 3, "four": 4, "five": 5},
234+
{"one", "two", "three", "four", "five"},
235+
{"one": 1, "two": 2, "three": 3, "four": 4, "five": 5},
215236
),
216237
(
217238
"linear AND four -> two (cycle)",
@@ -223,7 +244,8 @@ def test_new_resolver_get_installation_order(
223244
("four", "five"),
224245
("four", "two"),
225246
],
226-
{None: 0, "one": 1, "two": 2, "three": 3, "four": 4, "five": 5},
247+
{"one", "two", "three", "four", "five"},
248+
{"one": 1, "two": 2, "three": 3, "four": 4, "five": 5},
227249
),
228250
(
229251
"linear AND four -> three (cycle)",
@@ -235,16 +257,44 @@ def test_new_resolver_get_installation_order(
235257
("four", "five"),
236258
("four", "three"),
237259
],
238-
{None: 0, "one": 1, "two": 2, "three": 3, "four": 4, "five": 5},
260+
{"one", "two", "three", "four", "five"},
261+
{"one": 1, "two": 2, "three": 3, "four": 4, "five": 5},
262+
),
263+
(
264+
"linear AND four -> three (cycle) AND restricted 1-2-3",
265+
[
266+
(None, "one"),
267+
("one", "two"),
268+
("two", "three"),
269+
("three", "four"),
270+
("four", "five"),
271+
("four", "three"),
272+
],
273+
{"one", "two", "three"},
274+
{"one": 1, "two": 2, "three": 3},
275+
),
276+
(
277+
"linear AND four -> three (cycle) AND restricted 4-5",
278+
[
279+
(None, "one"),
280+
("one", "two"),
281+
("two", "three"),
282+
("three", "four"),
283+
("four", "five"),
284+
("four", "three"),
285+
],
286+
{"four", "five"},
287+
{"four": 4, "five": 5},
239288
),
240289
],
241290
)
242291
def test_new_resolver_topological_weights(
243292
name: str,
244293
edges: List[Tuple[Optional[str], Optional[str]]],
294+
requirement_keys: Set[str],
245295
expected_weights: Dict[Optional[str], int],
246296
) -> None:
247297
graph = _make_graph(edges)
248298

249-
weights = get_topological_weights(graph, len(expected_weights))
299+
weights = get_topological_weights(graph, requirement_keys)
250300
assert weights == expected_weights

0 commit comments

Comments
 (0)