Skip to content

Commit

Permalink
Prefer conflicting causes
Browse files Browse the repository at this point in the history
  • Loading branch information
notatallshaw committed Jan 3, 2024
1 parent 954075f commit 76f34f7
Showing 1 changed file with 102 additions and 1 deletion.
103 changes: 102 additions & 1 deletion src/pip/_internal/resolution/resolvelib/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,100 @@ def _get_with_identifier(
return default


def conflicting_causes(
causes: Sequence["PreferenceInformation"],
) -> Sequence["PreferenceInformation"]:
"""Given causes return which causes conflict with each other
For each cause check one of two things:
1. If it's specifier conflicts with another causes parent version
2. If it's specifier conflicts with another causes specifier
Any causes which match this criteria are returned as conflicting causes
"""
conflicting_ids: set[int] = set()

# Build a relationship between causes, cause ids, and cause parent names
causes_id_and_parents_by_name: dict[
str, list[tuple[int, Candidate]]
] = collections.defaultdict(list)
causes_by_id = {id(c): c for c in causes}
for cause_id, cause in causes_by_id.items():
if cause.parent:
causes_id_and_parents_by_name[cause.parent.name].append(
(cause_id, cause.parent)
)

# From 1, check if each cause's specifier conflicts
# with another causes parent's version
for cause_id, cause in causes_by_id.items():
if cause_id in conflicting_ids:
continue

cause_id_and_parents = causes_id_and_parents_by_name.get(cause.requirement.name)
if not cause_id_and_parents:
continue

conflicting_alternative_cause_ids: set[int] = set()
for alternative_cause_id, parent in cause_id_and_parents:
if not cause.requirement.is_satisfied_by(parent):
conflicting_alternative_cause_ids.add(alternative_cause_id)

if conflicting_alternative_cause_ids:
conflicting_ids.add(cause_id)
conflicting_ids.update(conflicting_alternative_cause_ids)

# For comparing if two specifiers conflict first group causes
# by name, as comparing specifiers is O(n^2) so comparing the
# smaller groups is more efficent
causes_by_name: dict[str, list["PreferenceInformation"]] = collections.defaultdict(
list
)
for cause in causes:
causes_by_name[cause.requirement.name].append(cause)

# From 2, check if each cause's specifier conflicts
# with another cause specifier
for causes_list in causes_by_name.values():
if len(causes_list) < 2:
continue

while causes_list:
cause = causes_list.pop()
for i, alternative_cause in enumerate(causes_list):
candidate = cause.requirement.get_candidate_lookup()[1]
if candidate is None:
continue
specifier = candidate.specifier

# Specifiers which provide no restrictions can be skipped
if len(specifier) == 0:
continue

alternative_candidate = (
alternative_cause.requirement.get_candidate_lookup()[1]
)
if alternative_candidate is None:
continue

alternative_specifier = alternative_candidate.specifier

# Alternative specifiers which provide no
# restrictions can be skipped
if len(alternative_specifier) == 0:
continue

# If intersection of specifiers are empty they are
# impossibe to fill and therefore conflicting
specifier_intersection = specifier and alternative_specifier
if len(specifier_intersection) == 0:
conflicting_ids.add(id(cause))
conflicting_ids.add(id(causes_list.pop(i)))

return [
cause for cause_id, cause in causes_by_id.items() if cause_id in conflicting_ids
]


class PipProvider(_ProviderBase):
"""Pip's provider implementation for resolvelib.
Expand Down Expand Up @@ -243,11 +337,18 @@ def filter_unsatisfied_names(
causes: Sequence["PreferenceInformation"],
) -> Iterable[str]:
"""
Prefer backtracking on unsatisfied names that are causes
Prefer backtracking on unsatisfied names that are conficting
causes, or secondly are causes
"""
if not causes:
return unsatisfied_names

# Check if backtrack causes are conflicting and prefer them
if len(causes) > 2:
_conflicting_causes = conflicting_causes(causes)
if len(_conflicting_causes) > 1:
causes = _conflicting_causes

# Extract the causes and parents names
causes_names = set()
for cause in causes:
Expand Down

0 comments on commit 76f34f7

Please sign in to comment.