Skip to content

Commit b32ff85

Browse files
committed
Do this all over again
1 parent c7e55ac commit b32ff85

File tree

1 file changed

+38
-72
lines changed

1 file changed

+38
-72
lines changed

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

+38-72
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,50 @@
1+
import functools
2+
import itertools
3+
14
from pip._vendor.six.moves import collections_abc # type: ignore
25

36
from pip._internal.utils.compat import lru_cache
47
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
58

69
if MYPY_CHECK_RUNNING:
7-
from typing import Callable, Iterator, Optional, Set
10+
from typing import Any, Callable, Iterator, Optional, Set
811

912
from pip._vendor.packaging.version import _BaseVersion
1013

1114
from .base import Candidate
1215

1316

14-
class _InstalledFirstCandidatesIterator(collections_abc.Iterator):
15-
"""Iterator for ``FoundCandidates``.
17+
def _deduplicated_by_version(candidates):
18+
# type: (Iterator[Candidate]) -> Iterator[Candidate]
19+
returned = set() # type: Set[_BaseVersion]
20+
for candidate in candidates:
21+
if candidate.version in returned:
22+
continue
23+
returned.add(candidate.version)
24+
yield candidate
1625

17-
This iterator is used when the resolver prefers to keep the version of an
18-
already-installed package. The already-installed candidate is always
19-
returned first. Candidates from index are accessed only when the resolver
20-
wants them, and the already-installed version is excluded from them.
21-
"""
22-
def __init__(
23-
self,
24-
get_others, # type: Callable[[], Iterator[Candidate]]
25-
installed, # type: Optional[Candidate]
26-
):
27-
self._installed = installed
28-
self._get_others = get_others
29-
self._others = None # type: Optional[Iterator[Candidate]]
30-
self._returned = set() # type: Set[_BaseVersion]
31-
32-
def __next__(self):
33-
# type: () -> Candidate
34-
if self._installed and self._installed.version not in self._returned:
35-
self._returned.add(self._installed.version)
36-
return self._installed
37-
if self._others is None:
38-
self._others = self._get_others()
39-
cand = next(self._others)
40-
while cand.version in self._returned:
41-
cand = next(self._others)
42-
self._returned.add(cand.version)
43-
return cand
44-
45-
next = __next__ # XXX: Python 2.
46-
47-
48-
class _InstalledReplacesCandidatesIterator(collections_abc.Iterator):
26+
27+
def _replaces_sort_key(installed, candidate):
28+
# type: (Candidate, Candidate) -> Any
29+
return (candidate.version, candidate is not installed)
30+
31+
32+
def _insert_installed(installed, others):
33+
# type: (Candidate, Iterator[Candidate]) -> Iterator[Candidate]
4934
"""Iterator for ``FoundCandidates``.
5035
5136
This iterator is used when the resolver prefers to upgrade an
5237
already-installed package. Candidates from index are returned in their
5338
normal ordering, except replaced when the version is already installed.
39+
40+
The `is_not` sort key puts the installed candidate before candidates of the
41+
same version from the index, so it is chosen on de-duplication.
5442
"""
55-
def __init__(
56-
self,
57-
get_others, # type: Callable[[], Iterator[Candidate]]
58-
installed, # type: Optional[Candidate]
59-
):
60-
self._installed = installed
61-
self._get_others = get_others
62-
self._others = None # type: Optional[Iterator[Candidate]]
63-
self._returned = set() # type: Set[_BaseVersion]
64-
65-
def __next__(self):
66-
# type: () -> Candidate
67-
if self._others is None:
68-
self._others = self._get_others()
69-
try:
70-
cand = next(self._others)
71-
while cand.version in self._returned:
72-
cand = next(self._others)
73-
if self._installed and cand.version == self._installed.version:
74-
cand = self._installed
75-
except StopIteration:
76-
# Return the already-installed candidate as the last item if its
77-
# version does not exist on the index.
78-
if not self._installed:
79-
raise
80-
if self._installed.version in self._returned:
81-
raise
82-
cand = self._installed
83-
self._returned.add(cand.version)
84-
return cand
85-
86-
next = __next__ # XXX: Python 2.
43+
candidates = sorted(
44+
itertools.chain(others, [installed]),
45+
key=functools.partial(_replaces_sort_key, installed),
46+
)
47+
return iter(candidates)
8748

8849

8950
class FoundCandidates(collections_abc.Sequence):
@@ -106,22 +67,27 @@ def __init__(
10667

10768
def __getitem__(self, index):
10869
# type: (int) -> Candidate
109-
# Implemented to satisfy the ABC check, This is not needed by the
70+
# Implemented to satisfy the ABC check. This is not needed by the
11071
# resolver, and should not be used by the provider either (for
11172
# performance reasons).
11273
raise NotImplementedError("don't do this")
11374

11475
def __iter__(self):
11576
# type: () -> Iterator[Candidate]
116-
if self._prefers_installed:
117-
klass = _InstalledFirstCandidatesIterator
77+
if not self._installed:
78+
candidates = self._get_others()
79+
elif self._prefers_installed:
80+
candidates = itertools.chain([self._installed], self._get_others())
11881
else:
119-
klass = _InstalledReplacesCandidatesIterator
120-
return klass(self._get_others, self._installed)
82+
candidates = _insert_installed(self._installed, self._get_others())
83+
return _deduplicated_by_version(candidates)
12184

12285
@lru_cache(maxsize=1)
12386
def __len__(self):
12487
# type: () -> int
88+
# Implement to satisfy the ABC check and used in tests. This is not
89+
# needed by the resolver, and should not be used by the provider either
90+
# (for performance reasons).
12591
return sum(1 for _ in self)
12692

12793
@lru_cache(maxsize=1)

0 commit comments

Comments
 (0)