11import collections
2+ import itertools
23
34from .providers import AbstractResolver
45from .structs import DirectedGraph , build_iter_view
@@ -143,6 +144,7 @@ def __init__(self, provider, reporter):
143144 self ._p = provider
144145 self ._r = reporter
145146 self ._states = []
147+ self ._known_failures = []
146148
147149 @property
148150 def state (self ):
@@ -199,6 +201,22 @@ def _get_criteria_to_update(self, candidate):
199201 criteria [name ] = crit
200202 return criteria
201203
204+ def _match_known_failure_causes (self , updating_criteria ):
205+ criteria = self .state .criteria .copy ()
206+ criteria .update (updating_criteria )
207+ for state in self ._known_failures :
208+ identical = self ._p .match_identically (
209+ itertools .chain .from_iterable (
210+ crit .iter_requirement () for crit in criteria .values ()
211+ ),
212+ itertools .chain .from_iterable (
213+ crit .iter_requirement () for crit in state .criteria .values ()
214+ ),
215+ )
216+ if identical :
217+ return True
218+ return False
219+
202220 def _attempt_to_pin_criterion (self , name , criterion ):
203221 causes = []
204222 for candidate in criterion .candidates :
@@ -208,6 +226,9 @@ def _attempt_to_pin_criterion(self, name, criterion):
208226 causes .append (e .criterion )
209227 continue
210228
229+ if self ._match_known_failure_causes (criteria ):
230+ continue
231+
211232 # Check the newly-pinned candidate actually works. This should
212233 # always pass under normal circumstances, but in the case of a
213234 # faulty provider, we will raise an error to notify the implementer
@@ -226,7 +247,7 @@ def _attempt_to_pin_criterion(self, name, criterion):
226247 self .state .mapping [name ] = candidate
227248 self .state .criteria .update (criteria )
228249
229- return []
250+ return None
230251
231252 # All candidates tried, nothing works. This criterion is a dead
232253 # end, signal for backtracking.
@@ -260,7 +281,7 @@ def _backtrack(self):
260281 """
261282 while len (self ._states ) >= 3 :
262283 # Remove the state that triggered backtracking.
263- del self ._states [ - 1 ]
284+ self ._known_failures . append ( self . _states . pop ())
264285
265286 # Retrieve the last candidate pin and known incompatibilities.
266287 broken_state = self ._states .pop ()
@@ -345,7 +366,7 @@ def resolve(self, requirements, max_rounds):
345366 )
346367 failure_causes = self ._attempt_to_pin_criterion (name , criterion )
347368
348- if failure_causes :
369+ if failure_causes is not None :
349370 # Backtrack if pinning fails. The backtrack process puts us in
350371 # an unpinned state, so we can work on it in the next round.
351372 success = self ._backtrack ()
0 commit comments