This is an automated email from the ASF dual-hosted git repository.
kevinjqliu 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 d9bdf8e7 fix: Serialize AlwaysTrue/AlwaysFalse as boolean literals
(#2780)
d9bdf8e7 is described below
commit d9bdf8e76049e98bed426b29a79681fcdf114c01
Author: Drew Gallardo <[email protected]>
AuthorDate: Mon Nov 24 17:23:07 2025 -0800
fix: Serialize AlwaysTrue/AlwaysFalse as boolean literals (#2780)
related to #2775
# Rationale for this change
This PR aligns the `AlwaysTrue` and `AlwaysFalse` expression
serialization to use the boolean primitives `true`/`false` instead of
string representation, matching the Iceberg
[ExpressionParser](https://github.com/apache/iceberg/blob/main/core/src/main/java/org/apache/iceberg/expressions/ExpressionParser.java#L101-L108).
I know the spec defines the true/false expressions as string but today
we can't hit the scan planning client endpoint following these models.
I know the spec defines the true/false expressions as a string
representation, but currently we can't successfully use the scan
planning client endpoint with those models. Java's actual implementation
serializes these as boolean literals.
For instance, with what we have today we would throw an illegal argument
exception:
```
{
"snapshot-id": 2540284336700708540,
"filter": "true",
"case-sensitive": true
}
```
Throws: `java.lang.IllegalArgumentException: argument
"content" is null`
But, this works just fine (notice no quotes):
```
{
"snapshot-id": 2540284336700708540,
"filter": true,
"case-sensitive": true
}
```
## Are these changes tested?
Yes
## Are there any user-facing changes?
No, this only affects the JSON serialization format to match Java Cores
behavior.
---
pyiceberg/expressions/__init__.py | 8 ++++----
tests/expressions/test_expressions.py | 4 ++--
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/pyiceberg/expressions/__init__.py
b/pyiceberg/expressions/__init__.py
index f0dc4094..4871da3e 100644
--- a/pyiceberg/expressions/__init__.py
+++ b/pyiceberg/expressions/__init__.py
@@ -370,10 +370,10 @@ class Not(IcebergBaseModel, BooleanExpression):
return (self.child,)
-class AlwaysTrue(BooleanExpression, Singleton, IcebergRootModel[str]):
+class AlwaysTrue(BooleanExpression, Singleton, IcebergRootModel[bool]):
"""TRUE expression."""
- root: str = "true"
+ root: bool = True
def __invert__(self) -> AlwaysFalse:
"""Transform the Expression into its negated version."""
@@ -388,10 +388,10 @@ class AlwaysTrue(BooleanExpression, Singleton,
IcebergRootModel[str]):
return "AlwaysTrue()"
-class AlwaysFalse(BooleanExpression, Singleton, IcebergRootModel[str]):
+class AlwaysFalse(BooleanExpression, Singleton, IcebergRootModel[bool]):
"""FALSE expression."""
- root: str = "false"
+ root: bool = False
def __invert__(self) -> AlwaysTrue:
"""Transform the Expression into its negated version."""
diff --git a/tests/expressions/test_expressions.py
b/tests/expressions/test_expressions.py
index 1fbe8d7a..47c26c8e 100644
--- a/tests/expressions/test_expressions.py
+++ b/tests/expressions/test_expressions.py
@@ -770,7 +770,7 @@ def test_not_json_serialization_and_deserialization() ->
None:
def test_always_true() -> None:
always_true = AlwaysTrue()
- assert always_true.model_dump_json() == '"true"'
+ assert always_true.model_dump_json() == "true"
assert str(always_true) == "AlwaysTrue()"
assert repr(always_true) == "AlwaysTrue()"
assert always_true == eval(repr(always_true))
@@ -779,7 +779,7 @@ def test_always_true() -> None:
def test_always_false() -> None:
always_false = AlwaysFalse()
- assert always_false.model_dump_json() == '"false"'
+ assert always_false.model_dump_json() == "false"
assert str(always_false) == "AlwaysFalse()"
assert repr(always_false) == "AlwaysFalse()"
assert always_false == eval(repr(always_false))