3333)
3434from typing import Literal as TypingLiteral
3535
36- from pydantic import Field
36+ from pydantic import ConfigDict , Field
3737
3838from pyiceberg .expressions .literals import (
3939 AboveMax ,
@@ -302,12 +302,19 @@ def __getnewargs__(self) -> Tuple[BooleanExpression, BooleanExpression]:
302302 return (self .left , self .right )
303303
304304
305- class Or (BooleanExpression ):
305+ class Or (IcebergBaseModel , BooleanExpression ):
306306 """OR operation expression - logical disjunction."""
307307
308+ model_config = ConfigDict (arbitrary_types_allowed = True )
309+
310+ type : TypingLiteral ["or" ] = Field (default = "or" , alias = "type" )
308311 left : BooleanExpression
309312 right : BooleanExpression
310313
314+ def __init__ (self , left : BooleanExpression , right : BooleanExpression , * rest : BooleanExpression ) -> None :
315+ if isinstance (self , Or ) and not hasattr (self , "left" ) and not hasattr (self , "right" ):
316+ super ().__init__ (left = left , right = right )
317+
311318 def __new__ (cls , left : BooleanExpression , right : BooleanExpression , * rest : BooleanExpression ) -> BooleanExpression : # type: ignore
312319 if rest :
313320 return _build_balanced_tree (Or , (left , right , * rest ))
@@ -319,10 +326,12 @@ def __new__(cls, left: BooleanExpression, right: BooleanExpression, *rest: Boole
319326 return left
320327 else :
321328 obj = super ().__new__ (cls )
322- obj .left = left
323- obj .right = right
324329 return obj
325330
331+ def __str__ (self ) -> str :
332+ """Return the string representation of the Or class."""
333+ return f"{ str (self .__class__ .__name__ )} (left={ repr (self .left )} , right={ repr (self .right )} )"
334+
326335 def __eq__ (self , other : Any ) -> bool :
327336 """Return the equality of two instances of the Or class."""
328337 return self .left == other .left and self .right == other .right if isinstance (other , Or ) else False
@@ -341,22 +350,31 @@ def __getnewargs__(self) -> Tuple[BooleanExpression, BooleanExpression]:
341350 return (self .left , self .right )
342351
343352
344- class Not (BooleanExpression ):
353+ class Not (IcebergBaseModel , BooleanExpression ):
345354 """NOT operation expression - logical negation."""
346355
347- child : BooleanExpression
356+ model_config = ConfigDict (arbitrary_types_allowed = True )
357+
358+ type : TypingLiteral ["not" ] = Field (default = "not" )
359+ child : BooleanExpression = Field ()
360+
361+ def __init__ (self , child : BooleanExpression , ** _ : Any ) -> None :
362+ super ().__init__ (child = child )
348363
349- def __new__ (cls , child : BooleanExpression ) -> BooleanExpression : # type: ignore
364+ def __new__ (cls , child : BooleanExpression , ** _ : Any ) -> BooleanExpression : # type: ignore
350365 if child is AlwaysTrue ():
351366 return AlwaysFalse ()
352367 elif child is AlwaysFalse ():
353368 return AlwaysTrue ()
354369 elif isinstance (child , Not ):
355370 return child .child
356371 obj = super ().__new__ (cls )
357- obj .child = child
358372 return obj
359373
374+ def __str__ (self ) -> str :
375+ """Return the string representation of the Not class."""
376+ return f"Not(child={ self .child } )"
377+
360378 def __repr__ (self ) -> str :
361379 """Return the string representation of the Not class."""
362380 return f"Not(child={ repr (self .child )} )"
@@ -373,8 +391,6 @@ def __getnewargs__(self) -> Tuple[BooleanExpression]:
373391 """Pickle the Not class."""
374392 return (self .child ,)
375393
376- """TRUE expression."""
377-
378394
379395class AlwaysTrue (BooleanExpression , Singleton , IcebergRootModel [str ]):
380396 """TRUE expression."""
@@ -447,7 +463,20 @@ def bind(self, schema: Schema, case_sensitive: bool = True) -> BooleanExpression
447463 def as_bound (self ) -> Type [BoundPredicate [L ]]: ...
448464
449465
450- class UnaryPredicate (UnboundPredicate [Any ], ABC ):
466+ class UnaryPredicate (IcebergBaseModel , UnboundPredicate [Any ], ABC ):
467+ type : str
468+
469+ model_config = {"arbitrary_types_allowed" : True }
470+
471+ def __init__ (self , term : Union [str , UnboundTerm [Any ]]):
472+ unbound = _to_unbound_term (term )
473+ super ().__init__ (term = unbound )
474+
475+ def __str__ (self ) -> str :
476+ """Return the string representation of the UnaryPredicate class."""
477+ # Sort to make it deterministic
478+ return f"{ str (self .__class__ .__name__ )} (term={ str (self .term )} )"
479+
451480 def bind (self , schema : Schema , case_sensitive : bool = True ) -> BoundUnaryPredicate [Any ]:
452481 bound_term = self .term .bind (schema , case_sensitive )
453482 return self .as_bound (bound_term )
@@ -506,6 +535,8 @@ def as_unbound(self) -> Type[NotNull]:
506535
507536
508537class IsNull (UnaryPredicate ):
538+ type : str = "is-null"
539+
509540 def __invert__ (self ) -> NotNull :
510541 """Transform the Expression into its negated version."""
511542 return NotNull (self .term )
@@ -516,6 +547,8 @@ def as_bound(self) -> Type[BoundIsNull[L]]:
516547
517548
518549class NotNull (UnaryPredicate ):
550+ type : str = "not-null"
551+
519552 def __invert__ (self ) -> IsNull :
520553 """Transform the Expression into its negated version."""
521554 return IsNull (self .term )
@@ -558,6 +591,8 @@ def as_unbound(self) -> Type[NotNaN]:
558591
559592
560593class IsNaN (UnaryPredicate ):
594+ type : str = "is-nan"
595+
561596 def __invert__ (self ) -> NotNaN :
562597 """Transform the Expression into its negated version."""
563598 return NotNaN (self .term )
@@ -568,6 +603,8 @@ def as_bound(self) -> Type[BoundIsNaN[L]]:
568603
569604
570605class NotNaN (UnaryPredicate ):
606+ type : str = "not-nan"
607+
571608 def __invert__ (self ) -> IsNaN :
572609 """Transform the Expression into its negated version."""
573610 return IsNaN (self .term )
0 commit comments