@@ -75,6 +75,100 @@ def _get_with_identifier(
7575 return default
7676
7777
78+ def conflicting_causes (
79+ causes : Sequence ["PreferenceInformation" ],
80+ ) -> Sequence ["PreferenceInformation" ]:
81+ """Given causes return which causes conflict with each other
82+ For each cause check one of two things:
83+ 1. If it's specifier conflicts with another causes parent version
84+ 2. If it's specifier conflicts with another causes specifier
85+
86+ Any causes which match this criteria are returned as conflicting causes
87+ """
88+ conflicting_ids : set [int ] = set ()
89+
90+ # Build a relationship between causes, cause ids, and cause parent names
91+ causes_id_and_parents_by_name : dict [
92+ str , list [tuple [int , Candidate ]]
93+ ] = collections .defaultdict (list )
94+ causes_by_id = {id (c ): c for c in causes }
95+ for cause_id , cause in causes_by_id .items ():
96+ if cause .parent :
97+ causes_id_and_parents_by_name [cause .parent .name ].append (
98+ (cause_id , cause .parent )
99+ )
100+
101+ # From 1, check if each cause's specifier conflicts
102+ # with another causes parent's version
103+ for cause_id , cause in causes_by_id .items ():
104+ if cause_id in conflicting_ids :
105+ continue
106+
107+ cause_id_and_parents = causes_id_and_parents_by_name .get (cause .requirement .name )
108+ if not cause_id_and_parents :
109+ continue
110+
111+ conflicting_alternative_cause_ids : set [int ] = set ()
112+ for alternative_cause_id , parent in cause_id_and_parents :
113+ if not cause .requirement .is_satisfied_by (parent ):
114+ conflicting_alternative_cause_ids .add (alternative_cause_id )
115+
116+ if conflicting_alternative_cause_ids :
117+ conflicting_ids .add (cause_id )
118+ conflicting_ids .update (conflicting_alternative_cause_ids )
119+
120+ # For comparing if two specifiers conflict first group causes
121+ # by name, as comparing specifiers is O(n^2) so comparing the
122+ # smaller groups is more efficent
123+ causes_by_name : dict [str , list ["PreferenceInformation" ]] = collections .defaultdict (
124+ list
125+ )
126+ for cause in causes :
127+ causes_by_name [cause .requirement .name ].append (cause )
128+
129+ # From 2, check if each cause's specifier conflicts
130+ # with another cause specifier
131+ for causes_list in causes_by_name .values ():
132+ if len (causes_list ) < 2 :
133+ continue
134+
135+ while causes_list :
136+ cause = causes_list .pop ()
137+ for i , alternative_cause in enumerate (causes_list ):
138+ candidate = cause .requirement .get_candidate_lookup ()[1 ]
139+ if candidate is None :
140+ continue
141+ specifier = candidate .specifier
142+
143+ # Specifiers which provide no restrictions can be skipped
144+ if len (specifier ) == 0 :
145+ continue
146+
147+ alternative_candidate = (
148+ alternative_cause .requirement .get_candidate_lookup ()[1 ]
149+ )
150+ if alternative_candidate is None :
151+ continue
152+
153+ alternative_specifier = alternative_candidate .specifier
154+
155+ # Alternative specifiers which provide no
156+ # restrictions can be skipped
157+ if len (alternative_specifier ) == 0 :
158+ continue
159+
160+ # If intersection of specifiers are empty they are
161+ # impossibe to fill and therefore conflicting
162+ specifier_intersection = specifier and alternative_specifier
163+ if len (specifier_intersection ) == 0 :
164+ conflicting_ids .add (id (cause ))
165+ conflicting_ids .add (id (causes_list .pop (i )))
166+
167+ return [
168+ cause for cause_id , cause in causes_by_id .items () if cause_id in conflicting_ids
169+ ]
170+
171+
78172class PipProvider (_ProviderBase ):
79173 """Pip's provider implementation for resolvelib.
80174
@@ -243,11 +337,18 @@ def filter_unsatisfied_names(
243337 causes : Sequence ["PreferenceInformation" ],
244338 ) -> Iterable [str ]:
245339 """
246- Prefer backtracking on unsatisfied names that are causes
340+ Prefer backtracking on unsatisfied names that are conficting
341+ causes, or secondly are causes
247342 """
248343 if not causes :
249344 return unsatisfied_names
250345
346+ # Check if backtrack causes are conflicting and prefer them
347+ if len (causes ) > 2 :
348+ _conflicting_causes = conflicting_causes (causes )
349+ if len (_conflicting_causes ) > 1 :
350+ causes = _conflicting_causes
351+
251352 # Extract the causes and parents names
252353 causes_names = set ()
253354 for cause in causes :
0 commit comments