Skip to content

Commit

Permalink
Correctly handle initially empty candidates
Browse files Browse the repository at this point in the history
Criterion.from_requirement() also should check for candidates, since
provider.find_matches() can return an empty list. It should raise
RequirementsConflicted just like other functions constructing a new
Criterion.
  • Loading branch information
uranusjr committed Feb 3, 2020
1 parent 12c5d6a commit fe8f0dc
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 10 deletions.
23 changes: 15 additions & 8 deletions src/resolvelib/resolvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class ResolverException(Exception):

class RequirementsConflicted(ResolverException):
def __init__(self, criterion):
super(RequirementsConflicted, self).__init__()
super(RequirementsConflicted, self).__init__(criterion)
self.criterion = criterion


Expand All @@ -35,7 +35,8 @@ class Criterion(object):
to exclude from consideration.
* `candidates` is a collection containing all possible candidates deducted
from the union of contributing requirements and known incompatibilities.
It should never be empty.
It should never be empty, except when the criterion is an attribute of a
raised `RequirementsConflicted` (in which case it is always empty).
.. note::
This class is intended to be externally immutable. **Do not** mutate
Expand All @@ -51,11 +52,15 @@ def __init__(self, candidates, information, incompatibilities):
def from_requirement(cls, provider, requirement, parent):
"""Build an instance from a requirement.
"""
return cls(
candidates=provider.find_matches(requirement),
candidates = provider.find_matches(requirement)
criterion = cls(
candidates=candidates,
information=[RequirementInformation(requirement, parent)],
incompatibilities=[],
)
if not candidates:
raise RequirementsConflicted(criterion)
return criterion

def iter_requirement(self):
return (i.requirement for i in self.information)
Expand All @@ -73,19 +78,21 @@ def merged_with(self, provider, requirement, parent):
for c in self.candidates
if provider.is_satisfied_by(requirement, c)
]
criterion = type(self)(candidates, infos, list(self.incompatibilities))
if not candidates:
raise RequirementsConflicted(self)
return type(self)(candidates, infos, list(self.incompatibilities))
raise RequirementsConflicted(criterion)
return criterion

def excluded_of(self, candidate):
"""Build a new instance from this, but excluding specified candidate.
"""
incompats = list(self.incompatibilities)
incompats.append(candidate)
candidates = [c for c in self.candidates if c != candidate]
criterion = type(self)(candidates, list(self.information), incompats)
if not candidates:
raise RequirementsConflicted(self)
return type(self)(candidates, list(self.information), incompats)
raise RequirementsConflicted(criterion)
return criterion


class ResolutionError(ResolverException):
Expand Down
3 changes: 1 addition & 2 deletions tests/functional/cocoapods/test_resolvers_cocoapods.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ def _iter_matches(self, requirement):

def find_matches(self, requirement):
mapping = {c.ver: c for c in self._iter_matches(requirement)}
print(requirement.name, list(mapping))
try:
version = self.pinned_versions[requirement.name]
except KeyError:
Expand All @@ -143,8 +144,6 @@ def get_dependencies(self, candidate):
# No right or wrong here, just a design decision.
"circular.json": "circular dependencies works for us, no conflicts",
"fixed_circular.json": "circular dependencies works for us, no backtracks",
# This needs a deeper look.
"complex_conflict.json": "ResolutionImpossible",
}


Expand Down

0 comments on commit fe8f0dc

Please sign in to comment.