diff --git a/pyiceberg/expressions/__init__.py b/pyiceberg/expressions/__init__.py index 16210bae68..339fd9cd6e 100644 --- a/pyiceberg/expressions/__init__.py +++ b/pyiceberg/expressions/__init__.py @@ -33,7 +33,7 @@ ) from typing import Literal as TypingLiteral -from pydantic import Field +from pydantic import ConfigDict, Field from pyiceberg.expressions.literals import ( AboveMax, @@ -302,12 +302,19 @@ def __getnewargs__(self) -> Tuple[BooleanExpression, BooleanExpression]: return (self.left, self.right) -class Or(BooleanExpression): +class Or(IcebergBaseModel, BooleanExpression): """OR operation expression - logical disjunction.""" + model_config = ConfigDict(arbitrary_types_allowed=True) + + type: TypingLiteral["or"] = Field(default="or", alias="type") left: BooleanExpression right: BooleanExpression + def __init__(self, left: BooleanExpression, right: BooleanExpression, *rest: BooleanExpression) -> None: + if isinstance(self, Or) and not hasattr(self, "left") and not hasattr(self, "right"): + super().__init__(left=left, right=right) + def __new__(cls, left: BooleanExpression, right: BooleanExpression, *rest: BooleanExpression) -> BooleanExpression: # type: ignore if rest: return _build_balanced_tree(Or, (left, right, *rest)) @@ -319,10 +326,12 @@ def __new__(cls, left: BooleanExpression, right: BooleanExpression, *rest: Boole return left else: obj = super().__new__(cls) - obj.left = left - obj.right = right return obj + def __str__(self) -> str: + """Return the string representation of the Or class.""" + return f"{str(self.__class__.__name__)}(left={repr(self.left)}, right={repr(self.right)})" + def __eq__(self, other: Any) -> bool: """Return the equality of two instances of the Or class.""" return self.left == other.left and self.right == other.right if isinstance(other, Or) else False diff --git a/tests/expressions/test_expressions.py b/tests/expressions/test_expressions.py index 63673fdaeb..2a57fb6693 100644 --- a/tests/expressions/test_expressions.py +++ b/tests/expressions/test_expressions.py @@ -714,6 +714,17 @@ def test_or() -> None: null | "abc" # type: ignore +def test_or_serialization() -> None: + left = EqualTo("a", 10) + right = EqualTo("b", 20) + or_ = Or(left, right) + + assert ( + or_.model_dump_json() + == '{"type":"or","left":{"term":"a","type":"eq","value":10},"right":{"term":"b","type":"eq","value":20}}' + ) + + def test_not() -> None: null = IsNull(Reference("a")) not_ = Not(null)