Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion pyiceberg/expressions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,20 @@ def bind(self, schema: Schema, case_sensitive: bool = True) -> BooleanExpression
def as_bound(self) -> Type[BoundPredicate[L]]: ...


class UnaryPredicate(UnboundPredicate[Any], ABC):
class UnaryPredicate(IcebergBaseModel, UnboundPredicate[Any], ABC):
type: str

model_config = {"arbitrary_types_allowed": True}

def __init__(self, term: Union[str, UnboundTerm[Any]]):
unbound = _to_unbound_term(term)
super().__init__(term=unbound)

def __str__(self) -> str:
"""Return the string representation of the UnaryPredicate class."""
# Sort to make it deterministic
return f"{str(self.__class__.__name__)}(term={str(self.term)})"

def bind(self, schema: Schema, case_sensitive: bool = True) -> BoundUnaryPredicate[Any]:
bound_term = self.term.bind(schema, case_sensitive)
return self.as_bound(bound_term)
Expand Down Expand Up @@ -506,6 +519,8 @@ def as_unbound(self) -> Type[NotNull]:


class IsNull(UnaryPredicate):
type: str = "is-null"

def __invert__(self) -> NotNull:
"""Transform the Expression into its negated version."""
return NotNull(self.term)
Expand All @@ -516,6 +531,8 @@ def as_bound(self) -> Type[BoundIsNull[L]]:


class NotNull(UnaryPredicate):
type: str = "not-null"

def __invert__(self) -> IsNull:
"""Transform the Expression into its negated version."""
return IsNull(self.term)
Expand Down Expand Up @@ -558,6 +575,8 @@ def as_unbound(self) -> Type[NotNaN]:


class IsNaN(UnaryPredicate):
type: str = "is-nan"

def __invert__(self) -> NotNaN:
"""Transform the Expression into its negated version."""
return NotNaN(self.term)
Expand All @@ -568,6 +587,8 @@ def as_bound(self) -> Type[BoundIsNaN[L]]:


class NotNaN(UnaryPredicate):
type: str = "not-nan"

def __invert__(self) -> IsNaN:
"""Transform the Expression into its negated version."""
return IsNaN(self.term)
Expand Down
14 changes: 12 additions & 2 deletions tests/expressions/test_expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ def test_and() -> None:
assert and_ == pickle.loads(pickle.dumps(and_))

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


def test_or() -> None:
Expand All @@ -711,7 +711,7 @@ def test_or() -> None:
assert or_ == pickle.loads(pickle.dumps(or_))

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


def test_not() -> None:
Expand Down Expand Up @@ -780,6 +780,16 @@ def test_not_null() -> None:
assert non_null == pickle.loads(pickle.dumps(non_null))


def test_serialize_is_null() -> None:
pred = IsNull(term="foo")
assert pred.model_dump_json() == '{"term":"foo","type":"is-null"}'


def test_serialize_not_null() -> None:
pred = NotNull(term="foo")
assert pred.model_dump_json() == '{"term":"foo","type":"not-null"}'


def test_bound_is_nan(accessor: Accessor) -> None:
# We need a FloatType here
term = BoundReference[float](
Expand Down