term.py (poetry-1.1.15) | : | term.py (poetry-1.2.0) | ||
---|---|---|---|---|
# -*- coding: utf-8 -*- | from __future__ import annotations | |||
from typing import Union | ||||
from poetry.core.packages import Dependency | import functools | |||
from .set_relation import SetRelation | from typing import TYPE_CHECKING | |||
class Term(object): | from poetry.mixology.set_relation import SetRelation | |||
if TYPE_CHECKING: | ||||
from poetry.core.packages.dependency import Dependency | ||||
from poetry.core.semver.version_constraint import VersionConstraint | ||||
class Term: | ||||
""" | """ | |||
A statement about a package which is true or false for a given selection of | A statement about a package which is true or false for a given selection of | |||
package versions. | package versions. | |||
See https://github.com/dart-lang/pub/tree/master/doc/solver.md#term. | See https://github.com/dart-lang/pub/tree/master/doc/solver.md#term. | |||
""" | """ | |||
def __init__(self, dependency, is_positive): # type: (Dependency, bool) -> None | def __init__(self, dependency: Dependency, is_positive: bool) -> None: | |||
self._dependency = dependency | self._dependency = dependency | |||
self._positive = is_positive | self._positive = is_positive | |||
self.relation = functools.lru_cache(maxsize=None)(self._relation) | ||||
self.intersect = functools.lru_cache(maxsize=None)(self._intersect) | ||||
@property | @property | |||
def inverse(self): # type: () -> Term | def inverse(self) -> Term: | |||
return Term(self._dependency, not self.is_positive()) | return Term(self._dependency, not self.is_positive()) | |||
@property | @property | |||
def dependency(self): | def dependency(self) -> Dependency: | |||
return self._dependency | return self._dependency | |||
@property | @property | |||
def constraint(self): | def constraint(self) -> VersionConstraint: | |||
return self._dependency.constraint | return self._dependency.constraint | |||
def is_positive(self): # type: () -> bool | def is_positive(self) -> bool: | |||
return self._positive | return self._positive | |||
def satisfies(self, other): # type: (Term) -> bool | def satisfies(self, other: Term) -> bool: | |||
""" | """ | |||
Returns whether this term satisfies another. | Returns whether this term satisfies another. | |||
""" | """ | |||
return ( | return ( | |||
self.dependency.complete_name == other.dependency.complete_name | self.dependency.complete_name == other.dependency.complete_name | |||
and self.relation(other) == SetRelation.SUBSET | and self.relation(other) == SetRelation.SUBSET | |||
) | ) | |||
def relation(self, other): # type: (Term) -> int | def _relation(self, other: Term) -> str: | |||
""" | """ | |||
Returns the relationship between the package versions | Returns the relationship between the package versions | |||
allowed by this term and another. | allowed by this term and another. | |||
""" | """ | |||
if self.dependency.complete_name != other.dependency.complete_name: | if self.dependency.complete_name != other.dependency.complete_name: | |||
raise ValueError( | raise ValueError(f"{other} should refer to {self.dependency.complete | |||
"{} should refer to {}".format(other, self.dependency.complete_n | _name}") | |||
ame) | ||||
) | ||||
other_constraint = other.constraint | other_constraint = other.constraint | |||
if other.is_positive(): | if other.is_positive(): | |||
if self.is_positive(): | if self.is_positive(): | |||
if not self._compatible_dependency(other.dependency): | if not self._compatible_dependency(other.dependency): | |||
return SetRelation.DISJOINT | return SetRelation.DISJOINT | |||
# foo ^1.5.0 is a subset of foo ^1.0.0 | # foo ^1.5.0 is a subset of foo ^1.0.0 | |||
if other_constraint.allows_all(self.constraint): | if other_constraint.allows_all(self.constraint): | |||
skipping to change at line 108 | skipping to change at line 113 | |||
return SetRelation.OVERLAPPING | return SetRelation.OVERLAPPING | |||
# not foo ^1.0.0 is a subset of not foo ^1.5.0 | # not foo ^1.0.0 is a subset of not foo ^1.5.0 | |||
if self.constraint.allows_all(other_constraint): | if self.constraint.allows_all(other_constraint): | |||
return SetRelation.SUBSET | return SetRelation.SUBSET | |||
# not foo ^2.0.0 overlaps not foo ^1.0.0 | # not foo ^2.0.0 overlaps not foo ^1.0.0 | |||
# not foo ^1.5.0 is a superset of not foo ^1.0.0 | # not foo ^1.5.0 is a superset of not foo ^1.0.0 | |||
return SetRelation.OVERLAPPING | return SetRelation.OVERLAPPING | |||
def intersect(self, other): # type: (Term) -> Union[Term, None] | def _intersect(self, other: Term) -> Term | None: | |||
""" | """ | |||
Returns a Term that represents the packages | Returns a Term that represents the packages | |||
allowed by both this term and another | allowed by both this term and another | |||
""" | """ | |||
if self.dependency.complete_name != other.dependency.complete_name: | if self.dependency.complete_name != other.dependency.complete_name: | |||
raise ValueError( | raise ValueError(f"{other} should refer to {self.dependency.complete | |||
"{} should refer to {}".format(other, self.dependency.complete_n | _name}") | |||
ame) | ||||
) | ||||
if self._compatible_dependency(other.dependency): | if self._compatible_dependency(other.dependency): | |||
if self.is_positive() != other.is_positive(): | if self.is_positive() != other.is_positive(): | |||
# foo ^1.0.0 ∩ not foo ^1.5.0 → foo >=1.0.0 <1.5.0 | # foo ^1.0.0 ∩ not foo ^1.5.0 → foo >=1.0.0 <1.5.0 | |||
positive = self if self.is_positive() else other | positive = self if self.is_positive() else other | |||
negative = other if self.is_positive() else self | negative = other if self.is_positive() else self | |||
return self._non_empty_term( | return self._non_empty_term( | |||
positive.constraint.difference(negative.constraint), True | positive.constraint.difference(negative.constraint), True, o ther | |||
) | ) | |||
elif self.is_positive(): | elif self.is_positive(): | |||
# foo ^1.0.0 ∩ foo >=1.5.0 <3.0.0 → foo ^1.5.0 | # foo ^1.0.0 ∩ foo >=1.5.0 <3.0.0 → foo ^1.5.0 | |||
return self._non_empty_term( | return self._non_empty_term( | |||
self.constraint.intersect(other.constraint), True | self.constraint.intersect(other.constraint), True, other | |||
) | ) | |||
else: | else: | |||
# not foo ^1.0.0 ∩ not foo >=1.5.0 <3.0.0 → not foo >=1.0.0 <3.0 .0 | # not foo ^1.0.0 ∩ not foo >=1.5.0 <3.0.0 → not foo >=1.0.0 <3.0 .0 | |||
return self._non_empty_term( | return self._non_empty_term( | |||
self.constraint.union(other.constraint), False | self.constraint.union(other.constraint), False, other | |||
) | ) | |||
elif self.is_positive() != other.is_positive(): | elif self.is_positive() != other.is_positive(): | |||
return self if self.is_positive() else other | return self if self.is_positive() else other | |||
else: | else: | |||
return | return None | |||
def difference(self, other): # type: (Term) -> Term | def difference(self, other: Term) -> Term | None: | |||
""" | """ | |||
Returns a Term that represents packages | Returns a Term that represents packages | |||
allowed by this term and not by the other | allowed by this term and not by the other | |||
""" | """ | |||
return self.intersect(other.inverse) | return self.intersect(other.inverse) | |||
def _compatible_dependency(self, other): | def _compatible_dependency(self, other: Dependency) -> bool: | |||
return ( | return ( | |||
self.dependency.is_root | self.dependency.is_root | |||
or other.is_root | or other.is_root | |||
or other.is_same_package_as(self.dependency) | or other.is_same_package_as(self.dependency) | |||
or ( | ||||
# we do this here to indicate direct origin dependencies are | ||||
# compatible with NVR dependencies | ||||
self.dependency.complete_name == other.complete_name | ||||
and self.dependency.is_direct_origin() != other.is_direct_origin | ||||
() | ||||
) | ||||
) | ) | |||
def _non_empty_term(self, constraint, is_positive): | def _non_empty_term( | |||
self, constraint: VersionConstraint, is_positive: bool, other: Term | ||||
) -> Term | None: | ||||
if constraint.is_empty(): | if constraint.is_empty(): | |||
return | return None | |||
return Term(self.dependency.with_constraint(constraint), is_positive) | ||||
def __str__(self): | # when creating a new term prefer direct-reference dependencies | |||
return "{} {} ({})".format( | dependency = ( | |||
"not " if not self.is_positive() else "", | other.dependency | |||
self._dependency.pretty_name, | if not self.dependency.is_direct_origin() | |||
self._dependency.pretty_constraint, | and other.dependency.is_direct_origin() | |||
else self.dependency | ||||
) | ) | |||
return Term(dependency.with_constraint(constraint), is_positive) | ||||
def __str__(self) -> str: | ||||
prefix = "not " if not self.is_positive() else "" | ||||
return f"{prefix}{self._dependency}" | ||||
def __repr__(self): | def __repr__(self) -> str: | |||
return "<Term {}>".format(str(self)) | return f"<Term {self!s}>" | |||
End of changes. 27 change blocks. | ||||
36 lines changed or deleted | 52 lines changed or added |