diff --git a/pyiceberg/expressions/__init__.py b/pyiceberg/expressions/__init__.py index a928b988fd..71ee7cd98d 100644 --- a/pyiceberg/expressions/__init__.py +++ b/pyiceberg/expressions/__init__.py @@ -237,12 +237,19 @@ def as_bound(self) -> type[BoundReference]: return BoundReference -class And(BooleanExpression): +class And(IcebergBaseModel, BooleanExpression): """AND operation expression - logical conjunction.""" + model_config = ConfigDict(arbitrary_types_allowed=True) + + type: TypingLiteral["and"] = Field(default="and", alias="type") left: BooleanExpression right: BooleanExpression + def __init__(self, left: BooleanExpression, right: BooleanExpression, *rest: BooleanExpression) -> None: + if isinstance(self, And) 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(And, (left, right, *rest)) @@ -254,8 +261,6 @@ 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 __eq__(self, other: Any) -> bool: diff --git a/tests/expressions/test_expressions.py b/tests/expressions/test_expressions.py index f0d6cdbce2..252da478d8 100644 --- a/tests/expressions/test_expressions.py +++ b/tests/expressions/test_expressions.py @@ -725,6 +725,15 @@ def test_and() -> None: null & "abc" +def test_and_serialization() -> None: + expr = And(EqualTo("x", 1), GreaterThan("y", 2)) + + assert ( + expr.model_dump_json() + == '{"type":"and","left":{"term":"x","type":"eq","value":1},"right":{"term":"y","type":"gt","value":2}}' + ) + + def test_or() -> None: null = IsNull(Reference("a")) nan = IsNaN(Reference("b"))