Skip to content

Commit 16b261d

Browse files
committed
Make UnaryPredicate JSON serializable and add unit test
1 parent 40521c8 commit 16b261d

File tree

2 files changed

+35
-4
lines changed

2 files changed

+35
-4
lines changed

pyiceberg/expressions/__init__.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
literal,
4040
)
4141
from pyiceberg.schema import Accessor, Schema
42-
from pyiceberg.typedef import L, StructProtocol
42+
from pyiceberg.typedef import IcebergBaseModel, L, StructProtocol
4343
from pyiceberg.types import DoubleType, FloatType, NestedField
4444
from pyiceberg.utils.singleton import Singleton
4545

@@ -429,7 +429,20 @@ def bind(self, schema: Schema, case_sensitive: bool = True) -> BooleanExpression
429429
def as_bound(self) -> Type[BoundPredicate[L]]: ...
430430

431431

432-
class UnaryPredicate(UnboundPredicate[Any], ABC):
432+
class UnaryPredicate(UnboundPredicate[Any], IcebergBaseModel, ABC):
433+
type: str
434+
column: str
435+
436+
def __init__(self, term: Union[str, UnboundTerm[Any]]):
437+
if isinstance(term, Reference):
438+
term_name = term.name
439+
elif isinstance(term, str):
440+
term_name = term
441+
else:
442+
raise ValueError("term must be a string or Reference")
443+
super().__init__(term=Reference(term_name))
444+
self.column = term_name
445+
433446
def bind(self, schema: Schema, case_sensitive: bool = True) -> BoundUnaryPredicate[Any]:
434447
bound_term = self.term.bind(schema, case_sensitive)
435448
return self.as_bound(bound_term)
@@ -488,6 +501,8 @@ def as_unbound(self) -> Type[NotNull]:
488501

489502

490503
class IsNull(UnaryPredicate):
504+
type: str = "is-null"
505+
491506
def __invert__(self) -> NotNull:
492507
"""Transform the Expression into its negated version."""
493508
return NotNull(self.term)
@@ -498,6 +513,8 @@ def as_bound(self) -> Type[BoundIsNull[L]]:
498513

499514

500515
class NotNull(UnaryPredicate):
516+
type: str = "not-null"
517+
501518
def __invert__(self) -> IsNull:
502519
"""Transform the Expression into its negated version."""
503520
return IsNull(self.term)
@@ -540,6 +557,8 @@ def as_unbound(self) -> Type[NotNaN]:
540557

541558

542559
class IsNaN(UnaryPredicate):
560+
type: str = "is-nan"
561+
543562
def __invert__(self) -> NotNaN:
544563
"""Transform the Expression into its negated version."""
545564
return NotNaN(self.term)
@@ -550,6 +569,8 @@ def as_bound(self) -> Type[BoundIsNaN[L]]:
550569

551570

552571
class NotNaN(UnaryPredicate):
572+
type: str = "not-nan"
573+
553574
def __invert__(self) -> IsNaN:
554575
"""Transform the Expression into its negated version."""
555576
return IsNaN(self.term)

tests/expressions/test_expressions.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,7 @@ def test_and() -> None:
707707
assert and_ == pickle.loads(pickle.dumps(and_))
708708

709709
with pytest.raises(ValueError, match="Expected BooleanExpression, got: abc"):
710-
null & "abc" # type: ignore
710+
null & "abc"
711711

712712

713713
def test_or() -> None:
@@ -724,7 +724,7 @@ def test_or() -> None:
724724
assert or_ == pickle.loads(pickle.dumps(or_))
725725

726726
with pytest.raises(ValueError, match="Expected BooleanExpression, got: abc"):
727-
null | "abc" # type: ignore
727+
null | "abc"
728728

729729

730730
def test_not() -> None:
@@ -791,6 +791,16 @@ def test_not_null() -> None:
791791
assert non_null == pickle.loads(pickle.dumps(non_null))
792792

793793

794+
def test_serialize_is_null() -> None:
795+
pred = IsNull(term="foo")
796+
assert pred.model_dump_json() == '{"type":"is-null","term":"foo"}'
797+
798+
799+
def test_serialize_not_null() -> None:
800+
pred = NotNull(term="foo")
801+
assert pred.model_dump_json() == '{"type":"not-null","term":"foo"}'
802+
803+
794804
def test_bound_is_nan(accessor: Accessor) -> None:
795805
# We need a FloatType here
796806
term = BoundReference[float](

0 commit comments

Comments
 (0)