Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
971258c
Make SetPredicate and subclasses JSON serializable with Pydantic
Aniketsy Oct 2, 2025
8211bd0
Make SetPredicate and subclasses JSON serializable with Pydantic
Aniketsy Oct 2, 2025
4392e29
Update pyiceberg/expressions/__init__.py
Aniketsy Oct 2, 2025
5ed46a8
Update pyiceberg/expressions/__init__.py
Aniketsy Oct 2, 2025
22edad4
Update pyiceberg/expressions/__init__.py
Aniketsy Oct 2, 2025
75532f9
Update pyiceberg/expressions/__init__.py
Aniketsy Oct 2, 2025
6913b11
Update pyiceberg/expressions/__init__.py
Aniketsy Oct 2, 2025
52768e2
Update pyiceberg/expressions/__init__.py
Aniketsy Oct 2, 2025
0db2562
Update pyiceberg/expressions/__init__.py
Aniketsy Oct 2, 2025
ed61130
Update tests/expressions/test_expressions.py
Aniketsy Oct 2, 2025
ebaf94f
Update tests/expressions/test_expressions.py
Aniketsy Oct 2, 2025
77e00b2
Update pyiceberg/expressions/__init__.py
Aniketsy Oct 2, 2025
92cec6f
Update pyiceberg/expressions/__init__.py
Aniketsy Oct 2, 2025
f09ba02
Update pyiceberg/expressions/__init__.py
Aniketsy Oct 2, 2025
f196a72
Update pyiceberg/expressions/__init__.py
Aniketsy Oct 2, 2025
3146904
Update pyiceberg/expressions/__init__.py
Aniketsy Oct 3, 2025
f922d17
Apply Ruff formatting fixes
Aniketsy Oct 3, 2025
45f07e4
Merge branch 'main' into fix/setpredicate-json-serialization
Aniketsy Oct 5, 2025
6482846
Fixed import error
Aniketsy Oct 6, 2025
110e4e3
Make SetPredicate and subclasses JSON serializable
Aniketsy Oct 8, 2025
d6d6423
Revert unrelated change
Fokko Oct 9, 2025
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
22 changes: 17 additions & 5 deletions pyiceberg/expressions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
TypeVar,
Union,
)
from typing import Literal as TypingLiteral

from pydantic import Field

Expand All @@ -41,10 +42,15 @@
literal,
)
from pyiceberg.schema import Accessor, Schema
from pyiceberg.typedef import IcebergRootModel, L, StructProtocol
from pyiceberg.typedef import IcebergBaseModel, IcebergRootModel, L, StructProtocol
from pyiceberg.types import DoubleType, FloatType, NestedField
from pyiceberg.utils.singleton import Singleton

try:
from pydantic import ConfigDict
except ImportError:
ConfigDict = dict


def _to_unbound_term(term: Union[str, UnboundTerm[Any]]) -> UnboundTerm[Any]:
return Reference(term) if isinstance(term, str) else term
Expand Down Expand Up @@ -571,12 +577,14 @@ def as_bound(self) -> Type[BoundNotNaN[L]]:
return BoundNotNaN[L]


class SetPredicate(UnboundPredicate[L], ABC):
literals: Set[Literal[L]]
class SetPredicate(IcebergBaseModel, UnboundPredicate[L], ABC):
model_config = ConfigDict(arbitrary_types_allowed=True)

type: TypingLiteral["in", "not-in"] = Field(default="in")
literals: Set[Literal[L]] = Field(alias="items")

def __init__(self, term: Union[str, UnboundTerm[Any]], literals: Union[Iterable[L], Iterable[Literal[L]]]):
super().__init__(term)
self.literals = _to_literal_set(literals)
super().__init__(term=_to_unbound_term(term), items=_to_literal_set(literals)) # type: ignore

def bind(self, schema: Schema, case_sensitive: bool = True) -> BoundSetPredicate[L]:
bound_term = self.term.bind(schema, case_sensitive)
Expand Down Expand Up @@ -688,6 +696,8 @@ def as_unbound(self) -> Type[NotIn[L]]:


class In(SetPredicate[L]):
type: TypingLiteral["in"] = Field(default="in", alias="type")

def __new__( # type: ignore # pylint: disable=W0221
cls, term: Union[str, UnboundTerm[Any]], literals: Union[Iterable[L], Iterable[Literal[L]]]
) -> BooleanExpression:
Expand All @@ -710,6 +720,8 @@ def as_bound(self) -> Type[BoundIn[L]]:


class NotIn(SetPredicate[L], ABC):
type: TypingLiteral["not-in"] = Field(default="not-in", alias="type")

def __new__( # type: ignore # pylint: disable=W0221
cls, term: Union[str, UnboundTerm[Any]], literals: Union[Iterable[L], Iterable[Literal[L]]]
) -> BooleanExpression:
Expand Down
10 changes: 10 additions & 0 deletions tests/expressions/test_expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,16 @@ def test_not_in() -> None:
assert not_in == pickle.loads(pickle.dumps(not_in))


def test_serialize_in() -> None:
pred = In(term="foo", literals=[1, 2, 3])
assert pred.model_dump_json() == '{"term":"foo","type":"in","items":[1,2,3]}'


def test_serialize_not_in() -> None:
pred = NotIn(term="foo", literals=[1, 2, 3])
assert pred.model_dump_json() == '{"term":"foo","type":"not-in","items":[1,2,3]}'


def test_bound_equal_to(term: BoundReference[Any]) -> None:
bound_equal_to = BoundEqualTo(term, literal("a"))
assert str(bound_equal_to) == f"BoundEqualTo(term={str(term)}, literal=literal('a'))"
Expand Down
3 changes: 2 additions & 1 deletion tests/integration/test_writes/test_writes.py
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,8 @@ def test_summaries_with_only_nulls(
@pytest.mark.integration
def test_duckdb_url_import(warehouse: Path, arrow_table_with_null: pa.Table) -> None:
os.environ["TZ"] = "Etc/UTC"
time.tzset()
if hasattr(time, "tzset"):
time.tzset()
tz = pytz.timezone(os.environ["TZ"])

catalog = SqlCatalog("test_sql_catalog", uri="sqlite:///:memory:", warehouse=f"/{warehouse}")
Expand Down