This is an automated email from the ASF dual-hosted git repository.
fokko pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-python.git
The following commit(s) were added to refs/heads/main by this push:
new d1826f11 feat: Make `and` expression JSON serializable (#2784)
d1826f11 is described below
commit d1826f116d86a5ba5519695f1498a9112cd2babb
Author: Drew Gallardo <[email protected]>
AuthorDate: Wed Nov 26 00:26:08 2025 -0800
feat: Make `and` expression JSON serializable (#2784)
Related to: #2518, #2775
# Rationale for this change
This work was done by @Aniketsy, I just opened this to get the tests
passing, and we can merge for scan planning.
But, this PR allows `And` expressions to be deserialized from JSON
through Pydantic.
This PR aligns the `And` expression with the `Or`/`Not` pattern by
adding `IcebergBaseModel` as an inherited class. This gets teh And
expression into a proven serializable state, preparing it for the full
expression tree [de]serializability work in #2783.
## Are these changes tested?
Yes added a test and ensure that they align with EpressionParser in
Iceberg Java
## Are there any user-facing changes?
No this is just serialization
cc: @kevinjqliu @Fokko
---------
Co-authored-by: Aniket Singh Yadav <[email protected]>
---
pyiceberg/expressions/__init__.py | 11 ++++++++---
tests/expressions/test_expressions.py | 9 +++++++++
2 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/pyiceberg/expressions/__init__.py
b/pyiceberg/expressions/__init__.py
index a928b988..71ee7cd9 100644
--- a/pyiceberg/expressions/__init__.py
+++ b/pyiceberg/expressions/__init__.py
@@ -237,12 +237,19 @@ class Reference(UnboundTerm, IcebergRootModel[str]):
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 @@ class And(BooleanExpression):
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 f0d6cdbc..252da478 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"))