From 9dfa8ba623a84665608ce90f5c81140df6461872 Mon Sep 17 00:00:00 2001 From: Nathan Date: Sun, 30 Jun 2024 21:50:00 -0500 Subject: [PATCH] union, intersect, allows_any, allows_all logic --- .../core/constraints/generic/constraint.py | 168 ++++++++++------- tests/constraints/generic/test_constraint.py | 177 +++++++++++++----- 2 files changed, 239 insertions(+), 106 deletions(-) diff --git a/src/poetry/core/constraints/generic/constraint.py b/src/poetry/core/constraints/generic/constraint.py index 315966c47..643ff9e66 100644 --- a/src/poetry/core/constraints/generic/constraint.py +++ b/src/poetry/core/constraints/generic/constraint.py @@ -2,7 +2,6 @@ import operator -from typing import Any from typing import Callable from typing import ClassVar @@ -11,7 +10,7 @@ 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: @@ -67,57 +66,22 @@ def operator(self) -> str: return self._operator def allows(self, other: BaseConstraint) -> bool: - """Logic table to help - - || != | == | in | not in - --------||--------|--------|--------|-------- - != || != | != | not in | not in - == || != | == | in | not in - in || not in | in | in | not in - not in || not in | not in | not in | false - - """ - - if not isinstance(other, Constraint) or other.operator not in { - "==", - "in", - "not in", - }: + if not isinstance(other, Constraint) or other.operator != "==": raise ValueError( f"Invalid argument for allows" f' ("other" must be a constraint with operator "=="): {other}' ) - is_equal_op = self._operator == "==" - is_non_equal_op = self._operator == "!=" - is_other_equal_op = other.operator == "==" - is_other_non_equal_op = other.operator == "!=" - is_in_op = self._operator == "in" - is_not_in_op = self._operator == "not in" - is_other_in_op = other.operator == "in" - is_other_not_in_op = other.operator == "not in" - - if is_equal_op: + if self._operator == "==": return self._value == other.value - if is_in_op and is_other_in_op or is_in_op and is_other_equal_op: + if self._operator == "in": return bool(self._trans_op_str["in"](other.value, self._value)) - if is_non_equal_op and not (is_other_in_op or is_other_not_in_op): + if self._operator == "!=": return self._value != other.value - if ( - is_in_op - and is_other_non_equal_op - or is_in_op - and is_other_not_in_op - or is_not_in_op - and is_other_non_equal_op - or is_not_in_op - and is_other_equal_op - or is_non_equal_op - and is_other_not_in_op - ): + if self._operator == "not in": return bool(self._trans_op_str["not in"](other.value, self._value)) return False @@ -127,9 +91,22 @@ def allows_all(self, other: BaseConstraint) -> bool: from poetry.core.constraints.generic import UnionConstraint if isinstance(other, Constraint): - if other.operator == "==": + is_in_op = self._operator == "in" + is_not_in_op = self._operator == "not in" + + is_other_equal_op = other.operator == "==" + is_other_in_op = other.operator == "in" + is_other_not_in_op = other.operator == "not in" + + if is_other_equal_op: return self.allows(other) + if is_other_in_op and is_in_op: + return self._op(self.value, other.value) + + if is_other_not_in_op and not is_not_in_op: + return self._trans_op_str["not in"](other.value, self.value) + return self == other if isinstance(other, MultiConstraint): @@ -146,6 +123,8 @@ def allows_any(self, other: BaseConstraint) -> bool: is_equal_op = self._operator == "==" is_non_equal_op = self._operator == "!=" + is_in_op = self._operator == "in" + is_not_in_op = self._operator == "not in" if is_equal_op: return other.allows(self) @@ -153,6 +132,8 @@ def allows_any(self, other: BaseConstraint) -> bool: if isinstance(other, Constraint): is_other_equal_op = other.operator == "==" is_other_non_equal_op = other.operator == "!=" + is_other_in_op = other.operator == "in" + is_other_not_in_op = other.operator == "not in" if is_other_equal_op: return self.allows(other) @@ -160,7 +141,14 @@ def allows_any(self, other: BaseConstraint) -> bool: if is_equal_op and is_other_non_equal_op: return self._value != other.value - return is_non_equal_op and is_other_non_equal_op + return ( + is_in_op + and is_other_in_op + or is_not_in_op + and is_other_not_in_op + or is_non_equal_op + and other.operator in {"!=", "in", "not in"} + ) elif isinstance(other, MultiConstraint): return is_non_equal_op @@ -188,23 +176,51 @@ def intersect(self, other: BaseConstraint) -> BaseConstraint: if other == self: return self - if ( - self.operator in {"!=", "not in", "in"} - and other.operator in {"==", "in", "not in"} - and self.allows(other) - ): + if self.operator == "!=" and other.operator == "==" and self.allows(other): return other - if ( - other.operator in {"!=", "not in", "in"} - and self.operator in {"==", "in", "not in"} - and other.allows(self) - ): + if other.operator == "!=" and self.operator == "==" and other.allows(self): return self - if other.operator in {"!=", "not in"} and self.operator in {"!=", "not in"}: + if ( + other.operator == "!=" + and self.operator == "!=" + or self.operator == "not in" + and other.operator == "not in" + ): return MultiConstraint(self, other) + other_in_self = self._trans_op_str["in"](self.value, other.value) + self_in_other = self._trans_op_str["in"](other.value, self.value) + is_in_op = self._operator == "in" + is_other_in_op = other.operator == "in" + + if is_in_op or other.operator == "not in": + # If self is a subset of other, return self + if is_other_in_op and other_in_self: + return self + # If neither are subsets of each other then its a MC + if (is_other_in_op and not self_in_other) or ( + other.operator == "!=" and self_in_other + ): + return MultiConstraint(self, other) + # if it allows any of other, return other + if self.allows_any(other): + return other + + if is_other_in_op or self.operator == "not in": + # If other is a subset of self, return other + if is_in_op and self_in_other: + return other + # If neither are subsets of each other then its a MC + if (is_in_op and not other_in_self) or ( + self.operator == "!=" and other_in_self + ): + return MultiConstraint(self, other) + # if other allows any of self, return self + if other.allows_any(self): + return self + return EmptyConstraint() return other.intersect(self) @@ -216,21 +232,45 @@ def union(self, other: BaseConstraint) -> BaseConstraint: if other == self: return self - if ( - self.operator in {"!=", "not in", "in"} - and other.operator in {"==", "in", "not in"} - ) and self.allows(other): + if self.operator == "!=" and other.operator == "==" and self.allows(other): return self - if ( - other.operator in {"!=", "not in", "in"} - and self.operator in {"==", "in", "not in"} - ) and other.allows(self): + if other.operator == "!=" and self.operator == "==" and other.allows(self): return other - if other.operator in {"==", "in"} and self.operator in {"==", "in"}: + if ( + other.operator == "==" + and self.operator == "==" + or self.operator == "not in" + and other.operator == "not in" + ): return UnionConstraint(self, other) + other_in_self = self._trans_op_str["in"](self.value, other.value) + self_in_other = self._trans_op_str["in"](other.value, self.value) + is_in_op = self._operator == "in" + is_other_in_op = other.operator == "in" + + if is_in_op or other.operator == "not in": + if is_other_in_op and self_in_other: + return self + if (is_other_in_op and not other_in_self) or ( + other.operator == "!=" and other_in_self + ): + return UnionConstraint(self, other) + if other.allows_all(self): + return other + + if is_other_in_op or self._operator == "not in": + if is_in_op and other_in_self: + return other + if (is_in_op and not self_in_other) or ( + self.operator == "!=" and self_in_other + ): + return UnionConstraint(self, other) + if self.allows_all(other): + return self + return AnyConstraint() # to preserve order (functionally not necessary) diff --git a/tests/constraints/generic/test_constraint.py b/tests/constraints/generic/test_constraint.py index 57208dd20..2b9abb9dc 100644 --- a/tests/constraints/generic/test_constraint.py +++ b/tests/constraints/generic/test_constraint.py @@ -127,6 +127,60 @@ def test_allows( True, False, ), + ( + Constraint("tegra", "not in"), + Constraint("tegra", "not in"), + True, + True, + ), + ( + Constraint("tegra", "in"), + Constraint("tegra", "not in"), + False, + False, + ), + ( + Constraint("tegra", "in"), + Constraint("tegra", "in"), + True, + True, + ), + ( + Constraint("tegra", "in"), + Constraint("teg", "in"), + True, + True, + ), + ( + Constraint("teg", "in"), + Constraint("tegra", "in"), + True, + False, + ), + ( + Constraint("teg", "not in"), + Constraint("tegra", "in"), + False, + False, + ), + ( + Constraint("tegra", "in"), + Constraint("rpi", "in"), + True, + False, + ), + ( + Constraint("1.2.3-tegra", "!="), + Constraint("tegra", "in"), + True, + False, + ), + ( + Constraint("1.2.3-tegra", "!="), + Constraint("tegra", "not in"), + True, + True, + ), ], ) def test_allows_any_and_allows_all( @@ -326,61 +380,78 @@ def test_invert(constraint: BaseConstraint, inverted: BaseConstraint) -> None: ), ), ), + ( + Constraint("tegra", "not in"), + Constraint("tegra", "not in"), + Constraint("tegra", "not in"), + ), + ( + Constraint("tegra", "in"), + Constraint("tegra", "not in"), + EmptyConstraint(), + ), ( Constraint("tegra", "in"), Constraint("tegra", "in"), Constraint("tegra", "in"), ), ( + Constraint("teg", "in"), + Constraint("tegra", "in"), Constraint("tegra", "in"), - Constraint("tegra", "not in"), - EmptyConstraint(), ), ( - Constraint("tegra", "not in"), - Constraint("tegra", "not in"), - Constraint("tegra", "not in"), + Constraint("teg", "not in"), + Constraint("tegra", "in"), + EmptyConstraint(), ), ( - Constraint("tegra", "not in"), - Constraint("rpi-v8", "not in"), + Constraint("tegra", "in"), + Constraint("rpi", "in"), ( + MultiConstraint(Constraint("tegra", "in"), Constraint("rpi", "in")), MultiConstraint( - Constraint("tegra", "not in"), Constraint("rpi-v8", "not in") - ), - MultiConstraint( - Constraint("rpi-v8", "not in"), Constraint("tegra", "not in") + Constraint("rpi", "in"), + Constraint("tegra", "in"), ), ), ), ( - Constraint("tegra", "not in"), - MultiConstraint( - Constraint("tegra", "not in"), Constraint("rpi-v8", "not in") - ), - MultiConstraint( - Constraint("tegra", "not in"), Constraint("rpi-v8", "not in") - ), - ), - ( - Constraint("5.10.123-tegra", "!="), - Constraint("tegra", "not in"), - Constraint("tegra", "not in"), + Constraint("tegra", "in"), + Constraint("1.2.3-tegra", "=="), + Constraint("1.2.3-tegra", "=="), ), ( - Constraint("5.10.123-tegra", "!="), Constraint("tegra", "in"), - EmptyConstraint(), + Constraint("1.2.3-tegra", "!="), + ( + MultiConstraint( + Constraint("tegra", "in"), Constraint("1.2.3-tegra", "!=") + ), + MultiConstraint( + Constraint("1.2.3-tegra", "!="), + Constraint("tegra", "in"), + ), + ), ), ( - Constraint("5.10.123-tegra", "=="), - Constraint("tegra", "in"), - Constraint("5.10.123-tegra"), + Constraint("tegra", "not in"), + Constraint("1.2.3-tegra", "!="), + Constraint("tegra", "not in"), ), ( - Constraint("5.10.123", "=="), - Constraint("tegra", "in"), - EmptyConstraint(), + Constraint("tegra", "not in"), + Constraint("rpi", "not in"), + ( + MultiConstraint( + Constraint("tegra", "not in"), + Constraint("rpi", "not in"), + ), + MultiConstraint( + Constraint("rpi", "not in"), + Constraint("tegra", "not in"), + ), + ), ), ], ) @@ -585,12 +656,9 @@ def test_intersect( MultiConstraint(Constraint("win32", "!=")), ), ( - Constraint("tegra", "in"), - Constraint("rpi-v8", "in"), - ( - UnionConstraint(Constraint("tegra", "in"), Constraint("rpi-v8", "in")), - UnionConstraint(Constraint("rpi-v8", "in"), Constraint("tegra", "in")), - ), + Constraint("tegra", "not in"), + Constraint("tegra", "not in"), + Constraint("tegra", "not in"), ), ( Constraint("tegra", "in"), @@ -598,19 +666,44 @@ def test_intersect( AnyConstraint(), ), ( - Constraint("5.10.123-tegra", "!="), Constraint("tegra", "in"), - AnyConstraint(), + Constraint("tegra", "in"), + Constraint("tegra", "in"), ), ( - Constraint("5.10.123", "!="), + Constraint("teg", "in"), Constraint("tegra", "in"), + Constraint("teg", "in"), + ), + ( + Constraint("tegra", "in"), + Constraint("rpi", "in"), + ( + UnionConstraint(Constraint("tegra", "in"), Constraint("rpi", "in")), + UnionConstraint( + Constraint("rpi", "in"), + Constraint("tegra", "in"), + ), + ), + ), + ( + Constraint("tegra", "in"), + Constraint("1.2.3-tegra", "!="), AnyConstraint(), ), ( - Constraint("5.10.123-tegra", "!="), Constraint("tegra", "not in"), - Constraint("5.10.123-tegra", "!="), + Constraint("rpi", "not in"), + ( + UnionConstraint( + Constraint("tegra", "not in"), + Constraint("rpi", "not in"), + ), + UnionConstraint( + Constraint("rpi", "not in"), + Constraint("tegra", "not in"), + ), + ), ), ], )