Skip to content

Commit

Permalink
add support for operators in/not in to generic constraint (python…
Browse files Browse the repository at this point in the history
…-poetry#722)

 This is a precondition to allow markers like `"tegra" in platform_release`.

Attention: In contrast to other operators the value comes before the operator (and the marker name after the operator).

Co-authored-by: Randy Döring <30527984+radoering@users.noreply.github.com>
  • Loading branch information
npapapietro and radoering committed Jul 27, 2024
1 parent 06d72bc commit a4307db
Show file tree
Hide file tree
Showing 2 changed files with 375 additions and 34 deletions.
105 changes: 71 additions & 34 deletions src/poetry/core/constraints/generic/constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import operator

from typing import Any
from typing import Callable
from typing import ClassVar

Expand All @@ -11,20 +10,44 @@
from poetry.core.constraints.generic.empty_constraint import EmptyConstraint


OperatorType = Callable[[object, object], Any]
OperatorType = Callable[[object, object], bool]


def contains(a: object, b: object, /) -> bool:
return operator.contains(a, b) # type: ignore[arg-type]


def not_contains(a: object, b: object, /) -> bool:
return not contains(a, b)


class Constraint(BaseConstraint):
OP_EQ = operator.eq
OP_NE = operator.ne
OP_IN = contains
OP_NC = not_contains

_trans_op_str: ClassVar[dict[str, OperatorType]] = {
"=": OP_EQ,
"==": OP_EQ,
"!=": OP_NE,
"in": OP_IN,
"not in": OP_NC,
}

_trans_op_int: ClassVar[dict[OperatorType, str]] = {OP_EQ: "==", OP_NE: "!="}
_trans_op_int: ClassVar[dict[OperatorType, str]] = {
OP_EQ: "==",
OP_NE: "!=",
OP_IN: "in",
OP_NC: "not in",
}

_trans_op_inv: ClassVar[dict[str, str]] = {
"!=": "==",
"==": "!=",
"not in": "in",
"in": "not in",
}

def __init__(self, value: str, operator: str = "==") -> None:
if operator == "=":
Expand All @@ -49,14 +72,8 @@ def allows(self, other: BaseConstraint) -> bool:
f' ("other" must be a constraint with operator "=="): {other}'
)

is_equal_op = self._operator == "=="
is_non_equal_op = self._operator == "!="

if is_equal_op:
return self._value == other.value

if is_non_equal_op:
return self._value != other.value
if op := self._trans_op_str.get(self._operator):
return op(other.value, self._value)

return False

Expand All @@ -68,6 +85,15 @@ def allows_all(self, other: BaseConstraint) -> bool:
if other.operator == "==":
return self.allows(other)

if other.operator == "in" and self._operator == "in":
return self.value in other.value

if other.operator == "not in":
if self._operator == "not in":
return other.value in self.value
if self._operator == "!=":
return self.value not in other.value

return self == other

if isinstance(other, MultiConstraint):
Expand All @@ -82,36 +108,36 @@ def allows_any(self, other: BaseConstraint) -> bool:
from poetry.core.constraints.generic import MultiConstraint
from poetry.core.constraints.generic import UnionConstraint

is_equal_op = self._operator == "=="
is_non_equal_op = self._operator == "!="

if is_equal_op:
if self._operator == "==":
return other.allows(self)

if isinstance(other, Constraint):
is_other_equal_op = other.operator == "=="
is_other_non_equal_op = other.operator == "!="

if is_other_equal_op:
if other.operator == "==":
return self.allows(other)

if is_equal_op and is_other_non_equal_op:
if other.operator == "!=" and self._operator == "==":
return self._value != other.value

return is_non_equal_op and is_other_non_equal_op
if other.operator == "not in" and self._operator == "in":
return other.value not in self.value

if other.operator == "in" and self._operator == "not in":
return self.value not in other.value

return True

elif isinstance(other, MultiConstraint):
return is_non_equal_op
return self._operator == "!="

elif isinstance(other, UnionConstraint):
return is_non_equal_op and any(
return self._operator == "!=" and any(
self.allows_any(c) for c in other.constraints
)

return other.is_any()

def invert(self) -> Constraint:
return Constraint(self._value, "!=" if self._operator == "==" else "==")
return Constraint(self._value, self._trans_op_inv[self.operator])

def difference(self, other: BaseConstraint) -> Constraint | EmptyConstraint:
if other.allows(self):
Expand All @@ -126,16 +152,16 @@ def intersect(self, other: BaseConstraint) -> BaseConstraint:
if other == self:
return self

if self.operator == "!=" and other.operator == "==" and self.allows(other):
if self.allows_all(other):
return other

if other.operator == "!=" and self.operator == "==" and other.allows(self):
if other.allows_all(self):
return self

if other.operator == "!=" and self.operator == "!=":
return MultiConstraint(self, other)
if not self.allows_any(other) or not other.allows_any(self):
return EmptyConstraint()

return EmptyConstraint()
return MultiConstraint(self, other)

return other.intersect(self)

Expand All @@ -146,16 +172,25 @@ def union(self, other: BaseConstraint) -> BaseConstraint:
if other == self:
return self

if self.operator == "!=" and other.operator == "==" and self.allows(other):
if self.allows_all(other):
return self

if other.operator == "!=" and self.operator == "==" and other.allows(self):
if other.allows_all(self):
return other

if other.operator == "==" and self.operator == "==":
return UnionConstraint(self, other)
ops = {self.operator, other.operator}
if (
(ops in ({"!="}, {"not in"}))
or (
ops in ({"in", "!="}, {"in", "not in"})
and (self.operator == "in" and self.value in other.value)
or (other.operator == "in" and other.value in self.value)
)
or self.invert() == other
):
return AnyConstraint()

return AnyConstraint()
return UnionConstraint(self, other)

# to preserve order (functionally not necessary)
if isinstance(other, UnionConstraint):
Expand All @@ -179,5 +214,7 @@ def __hash__(self) -> int:
return hash((self._operator, self._value))

def __str__(self) -> str:
if self._operator in {"in", "not in"}:
return f"'{self._value}' {self._operator}"
op = self._operator if self._operator != "==" else ""
return f"{op}{self._value}"
Loading

0 comments on commit a4307db

Please sign in to comment.