1
+ import functools
2
+ import itertools
3
+
1
4
from pip ._vendor .six .moves import collections_abc # type: ignore
2
5
3
6
from pip ._internal .utils .compat import lru_cache
4
7
from pip ._internal .utils .typing import MYPY_CHECK_RUNNING
5
8
6
9
if MYPY_CHECK_RUNNING :
7
- from typing import Callable , Iterator , Optional , Set
10
+ from typing import Any , Callable , Iterator , Optional , Set
8
11
9
12
from pip ._vendor .packaging .version import _BaseVersion
10
13
11
14
from .base import Candidate
12
15
13
16
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
16
25
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]
49
34
"""Iterator for ``FoundCandidates``.
50
35
51
36
This iterator is used when the resolver prefers to upgrade an
52
37
already-installed package. Candidates from index are returned in their
53
38
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.
54
42
"""
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 )
87
48
88
49
89
50
class FoundCandidates (collections_abc .Sequence ):
@@ -106,22 +67,27 @@ def __init__(
106
67
107
68
def __getitem__ (self , index ):
108
69
# 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
110
71
# resolver, and should not be used by the provider either (for
111
72
# performance reasons).
112
73
raise NotImplementedError ("don't do this" )
113
74
114
75
def __iter__ (self ):
115
76
# 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 ())
118
81
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 )
121
84
122
85
@lru_cache (maxsize = 1 )
123
86
def __len__ (self ):
124
87
# 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).
125
91
return sum (1 for _ in self )
126
92
127
93
@lru_cache (maxsize = 1 )
0 commit comments