Skip to content

Commit a4307db

Browse files
add support for operators in/not in to generic constraint (python-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 <[email protected]>
1 parent 06d72bc commit a4307db

File tree

2 files changed

+375
-34
lines changed

2 files changed

+375
-34
lines changed

src/poetry/core/constraints/generic/constraint.py

Lines changed: 71 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import operator
44

5-
from typing import Any
65
from typing import Callable
76
from typing import ClassVar
87

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

1312

14-
OperatorType = Callable[[object, object], Any]
13+
OperatorType = Callable[[object, object], bool]
14+
15+
16+
def contains(a: object, b: object, /) -> bool:
17+
return operator.contains(a, b) # type: ignore[arg-type]
18+
19+
20+
def not_contains(a: object, b: object, /) -> bool:
21+
return not contains(a, b)
1522

1623

1724
class Constraint(BaseConstraint):
1825
OP_EQ = operator.eq
1926
OP_NE = operator.ne
27+
OP_IN = contains
28+
OP_NC = not_contains
2029

2130
_trans_op_str: ClassVar[dict[str, OperatorType]] = {
2231
"=": OP_EQ,
2332
"==": OP_EQ,
2433
"!=": OP_NE,
34+
"in": OP_IN,
35+
"not in": OP_NC,
2536
}
2637

27-
_trans_op_int: ClassVar[dict[OperatorType, str]] = {OP_EQ: "==", OP_NE: "!="}
38+
_trans_op_int: ClassVar[dict[OperatorType, str]] = {
39+
OP_EQ: "==",
40+
OP_NE: "!=",
41+
OP_IN: "in",
42+
OP_NC: "not in",
43+
}
44+
45+
_trans_op_inv: ClassVar[dict[str, str]] = {
46+
"!=": "==",
47+
"==": "!=",
48+
"not in": "in",
49+
"in": "not in",
50+
}
2851

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

52-
is_equal_op = self._operator == "=="
53-
is_non_equal_op = self._operator == "!="
54-
55-
if is_equal_op:
56-
return self._value == other.value
57-
58-
if is_non_equal_op:
59-
return self._value != other.value
75+
if op := self._trans_op_str.get(self._operator):
76+
return op(other.value, self._value)
6077

6178
return False
6279

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

88+
if other.operator == "in" and self._operator == "in":
89+
return self.value in other.value
90+
91+
if other.operator == "not in":
92+
if self._operator == "not in":
93+
return other.value in self.value
94+
if self._operator == "!=":
95+
return self.value not in other.value
96+
7197
return self == other
7298

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

85-
is_equal_op = self._operator == "=="
86-
is_non_equal_op = self._operator == "!="
87-
88-
if is_equal_op:
111+
if self._operator == "==":
89112
return other.allows(self)
90113

91114
if isinstance(other, Constraint):
92-
is_other_equal_op = other.operator == "=="
93-
is_other_non_equal_op = other.operator == "!="
94-
95-
if is_other_equal_op:
115+
if other.operator == "==":
96116
return self.allows(other)
97117

98-
if is_equal_op and is_other_non_equal_op:
118+
if other.operator == "!=" and self._operator == "==":
99119
return self._value != other.value
100120

101-
return is_non_equal_op and is_other_non_equal_op
121+
if other.operator == "not in" and self._operator == "in":
122+
return other.value not in self.value
123+
124+
if other.operator == "in" and self._operator == "not in":
125+
return self.value not in other.value
126+
127+
return True
102128

103129
elif isinstance(other, MultiConstraint):
104-
return is_non_equal_op
130+
return self._operator == "!="
105131

106132
elif isinstance(other, UnionConstraint):
107-
return is_non_equal_op and any(
133+
return self._operator == "!=" and any(
108134
self.allows_any(c) for c in other.constraints
109135
)
110136

111137
return other.is_any()
112138

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

116142
def difference(self, other: BaseConstraint) -> Constraint | EmptyConstraint:
117143
if other.allows(self):
@@ -126,16 +152,16 @@ def intersect(self, other: BaseConstraint) -> BaseConstraint:
126152
if other == self:
127153
return self
128154

129-
if self.operator == "!=" and other.operator == "==" and self.allows(other):
155+
if self.allows_all(other):
130156
return other
131157

132-
if other.operator == "!=" and self.operator == "==" and other.allows(self):
158+
if other.allows_all(self):
133159
return self
134160

135-
if other.operator == "!=" and self.operator == "!=":
136-
return MultiConstraint(self, other)
161+
if not self.allows_any(other) or not other.allows_any(self):
162+
return EmptyConstraint()
137163

138-
return EmptyConstraint()
164+
return MultiConstraint(self, other)
139165

140166
return other.intersect(self)
141167

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

149-
if self.operator == "!=" and other.operator == "==" and self.allows(other):
175+
if self.allows_all(other):
150176
return self
151177

152-
if other.operator == "!=" and self.operator == "==" and other.allows(self):
178+
if other.allows_all(self):
153179
return other
154180

155-
if other.operator == "==" and self.operator == "==":
156-
return UnionConstraint(self, other)
181+
ops = {self.operator, other.operator}
182+
if (
183+
(ops in ({"!="}, {"not in"}))
184+
or (
185+
ops in ({"in", "!="}, {"in", "not in"})
186+
and (self.operator == "in" and self.value in other.value)
187+
or (other.operator == "in" and other.value in self.value)
188+
)
189+
or self.invert() == other
190+
):
191+
return AnyConstraint()
157192

158-
return AnyConstraint()
193+
return UnionConstraint(self, other)
159194

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

181216
def __str__(self) -> str:
217+
if self._operator in {"in", "not in"}:
218+
return f"'{self._value}' {self._operator}"
182219
op = self._operator if self._operator != "==" else ""
183220
return f"{op}{self._value}"

0 commit comments

Comments
 (0)