From 971258cd224aa9d7ca51fc80d8cc98d8ddea8636 Mon Sep 17 00:00:00 2001 From: Aniket Singh Yadav Date: Thu, 2 Oct 2025 10:00:54 +0530 Subject: [PATCH 01/20] Make SetPredicate and subclasses JSON serializable with Pydantic --- pyiceberg/expressions/__init__.py | 23 ++++++++++++++++++----- tests/expressions/test_expressions.py | 7 +++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/pyiceberg/expressions/__init__.py b/pyiceberg/expressions/__init__.py index 2adf898fea..03846a21d3 100644 --- a/pyiceberg/expressions/__init__.py +++ b/pyiceberg/expressions/__init__.py @@ -42,6 +42,8 @@ from pyiceberg.typedef import L, StructProtocol from pyiceberg.types import DoubleType, FloatType, NestedField from pyiceberg.utils.singleton import Singleton +from pyiceberg.utils.pydantic import IcebergBaseModel +from pydantic import Field def _to_unbound_term(term: Union[str, UnboundTerm[Any]]) -> UnboundTerm[Any]: @@ -559,12 +561,19 @@ def as_bound(self) -> Type[BoundNotNaN[L]]: return BoundNotNaN[L] -class SetPredicate(UnboundPredicate[L], ABC): - literals: Set[Literal[L]] +class SetPredicate(UnboundPredicate[L], IcebergBaseModel, ABC): + type: str = Field(default="in", alias="type") + term: str + value: list[Any] def __init__(self, term: Union[str, UnboundTerm[Any]], literals: Union[Iterable[L], Iterable[Literal[L]]]): - super().__init__(term) - self.literals = _to_literal_set(literals) + # Convert term to string for serialization + term_str = term.name if isinstance(term, Reference) else str(term) + literals_set = _to_literal_set(literals) + value_list = [lit.value for lit in literals_set] + super().__init__(term=term_str, value=value_list) + self.literals = literals_set + self.term_obj = term def bind(self, schema: Schema, case_sensitive: bool = True) -> BoundSetPredicate[L]: bound_term = self.term.bind(schema, case_sensitive) @@ -676,6 +685,8 @@ def as_unbound(self) -> Type[NotIn[L]]: class In(SetPredicate[L]): + type: str = 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: @@ -698,6 +709,8 @@ def as_bound(self) -> Type[BoundIn[L]]: class NotIn(SetPredicate[L], ABC): + type: str = 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: @@ -712,7 +725,7 @@ def __new__( # type: ignore # pylint: disable=W0221 def __invert__(self) -> In[L]: """Transform the Expression into its negated version.""" - return In[L](self.term, self.literals) + return In[L](self.term, self._literals) @property def as_bound(self) -> Type[BoundNotIn[L]]: diff --git a/tests/expressions/test_expressions.py b/tests/expressions/test_expressions.py index 828d32704a..8a169328b5 100644 --- a/tests/expressions/test_expressions.py +++ b/tests/expressions/test_expressions.py @@ -868,6 +868,13 @@ def test_not_in() -> None: assert not_in == eval(repr(not_in)) assert not_in == pickle.loads(pickle.dumps(not_in)) +def test_serialize_in(): + pred = In(term="foo", literals=[1, 2, 3]) + assert pred.model_dump_json() == '{"type":"in","term":"foo","value":[1,2,3]}' + +def test_serialize_not_in(): + pred = NotIn(term="foo", literals=[1, 2, 3]) + assert pred.model_dump_json() == '{"type":"not-in","term":"foo","value":[1,2,3]}' def test_bound_equal_to(term: BoundReference[Any]) -> None: bound_equal_to = BoundEqualTo(term, literal("a")) From 8211bd0d62579d948e0015ec69b1fa73ee4cb9a9 Mon Sep 17 00:00:00 2001 From: Aniket Singh Yadav Date: Thu, 2 Oct 2025 10:12:22 +0530 Subject: [PATCH 02/20] Make SetPredicate and subclasses JSON serializable with Pydantic --- pyiceberg/expressions/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiceberg/expressions/__init__.py b/pyiceberg/expressions/__init__.py index 03846a21d3..8135d15526 100644 --- a/pyiceberg/expressions/__init__.py +++ b/pyiceberg/expressions/__init__.py @@ -701,7 +701,7 @@ def __new__( # type: ignore # pylint: disable=W0221 def __invert__(self) -> NotIn[L]: """Transform the Expression into its negated version.""" - return NotIn[L](self.term, self.literals) + return NotIn[L](self.term, self._literals) @property def as_bound(self) -> Type[BoundIn[L]]: From 4392e29547acf5bd9a0f7ebb611f3ec5d54d46cb Mon Sep 17 00:00:00 2001 From: Aniket <148300120+Aniketsy@users.noreply.github.com> Date: Thu, 2 Oct 2025 12:35:46 +0530 Subject: [PATCH 03/20] Update pyiceberg/expressions/__init__.py Co-authored-by: Fokko Driesprong --- pyiceberg/expressions/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiceberg/expressions/__init__.py b/pyiceberg/expressions/__init__.py index 8135d15526..f80b00fe75 100644 --- a/pyiceberg/expressions/__init__.py +++ b/pyiceberg/expressions/__init__.py @@ -685,7 +685,7 @@ def as_unbound(self) -> Type[NotIn[L]]: class In(SetPredicate[L]): - type: str = Field(default="in", alias="type") + type: Literal["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]]] From 5ed46a82b4550a954abe9477f88458dcc49dc886 Mon Sep 17 00:00:00 2001 From: Aniket <148300120+Aniketsy@users.noreply.github.com> Date: Thu, 2 Oct 2025 12:36:05 +0530 Subject: [PATCH 04/20] Update pyiceberg/expressions/__init__.py Co-authored-by: Fokko Driesprong --- pyiceberg/expressions/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiceberg/expressions/__init__.py b/pyiceberg/expressions/__init__.py index f80b00fe75..5389ebe65c 100644 --- a/pyiceberg/expressions/__init__.py +++ b/pyiceberg/expressions/__init__.py @@ -564,7 +564,7 @@ def as_bound(self) -> Type[BoundNotNaN[L]]: class SetPredicate(UnboundPredicate[L], IcebergBaseModel, ABC): type: str = Field(default="in", alias="type") term: str - value: list[Any] + literals: Set[Literal[L]] = Field(alias="items") def __init__(self, term: Union[str, UnboundTerm[Any]], literals: Union[Iterable[L], Iterable[Literal[L]]]): # Convert term to string for serialization From 22edad401fe94fc9ec084f5b8000491fdaf13fbc Mon Sep 17 00:00:00 2001 From: Aniket <148300120+Aniketsy@users.noreply.github.com> Date: Fri, 3 Oct 2025 00:54:24 +0530 Subject: [PATCH 05/20] Update pyiceberg/expressions/__init__.py Co-authored-by: Fokko Driesprong --- pyiceberg/expressions/__init__.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/pyiceberg/expressions/__init__.py b/pyiceberg/expressions/__init__.py index 5389ebe65c..90655ceb45 100644 --- a/pyiceberg/expressions/__init__.py +++ b/pyiceberg/expressions/__init__.py @@ -567,13 +567,8 @@ class SetPredicate(UnboundPredicate[L], IcebergBaseModel, ABC): literals: Set[Literal[L]] = Field(alias="items") def __init__(self, term: Union[str, UnboundTerm[Any]], literals: Union[Iterable[L], Iterable[Literal[L]]]): - # Convert term to string for serialization - term_str = term.name if isinstance(term, Reference) else str(term) - literals_set = _to_literal_set(literals) - value_list = [lit.value for lit in literals_set] - super().__init__(term=term_str, value=value_list) - self.literals = literals_set - self.term_obj = term + super().__init__(term) + self.literals = _to_literal_set(literals) def bind(self, schema: Schema, case_sensitive: bool = True) -> BoundSetPredicate[L]: bound_term = self.term.bind(schema, case_sensitive) From 75532f94d65c50f0fa97aebb7417b64b670c6260 Mon Sep 17 00:00:00 2001 From: Aniket <148300120+Aniketsy@users.noreply.github.com> Date: Fri, 3 Oct 2025 00:54:57 +0530 Subject: [PATCH 06/20] Update pyiceberg/expressions/__init__.py Co-authored-by: Fokko Driesprong --- pyiceberg/expressions/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiceberg/expressions/__init__.py b/pyiceberg/expressions/__init__.py index 90655ceb45..94f319069f 100644 --- a/pyiceberg/expressions/__init__.py +++ b/pyiceberg/expressions/__init__.py @@ -696,7 +696,7 @@ def __new__( # type: ignore # pylint: disable=W0221 def __invert__(self) -> NotIn[L]: """Transform the Expression into its negated version.""" - return NotIn[L](self.term, self._literals) + return NotIn[L](self.term, self.literals) @property def as_bound(self) -> Type[BoundIn[L]]: From 6913b11ae1abbe0cfb05bfb0232fe66f6de437be Mon Sep 17 00:00:00 2001 From: Aniket <148300120+Aniketsy@users.noreply.github.com> Date: Fri, 3 Oct 2025 00:55:12 +0530 Subject: [PATCH 07/20] Update pyiceberg/expressions/__init__.py Co-authored-by: Fokko Driesprong --- pyiceberg/expressions/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiceberg/expressions/__init__.py b/pyiceberg/expressions/__init__.py index 94f319069f..e20fe5cd49 100644 --- a/pyiceberg/expressions/__init__.py +++ b/pyiceberg/expressions/__init__.py @@ -720,7 +720,7 @@ def __new__( # type: ignore # pylint: disable=W0221 def __invert__(self) -> In[L]: """Transform the Expression into its negated version.""" - return In[L](self.term, self._literals) + return NotIn[L](self.term, self.literals) @property def as_bound(self) -> Type[BoundNotIn[L]]: From 52768e26a3c43eb6a695920b173bf5493365cdf2 Mon Sep 17 00:00:00 2001 From: Aniket <148300120+Aniketsy@users.noreply.github.com> Date: Fri, 3 Oct 2025 01:26:17 +0530 Subject: [PATCH 08/20] Update pyiceberg/expressions/__init__.py Co-authored-by: Fokko Driesprong --- pyiceberg/expressions/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiceberg/expressions/__init__.py b/pyiceberg/expressions/__init__.py index e20fe5cd49..f28eca5b6f 100644 --- a/pyiceberg/expressions/__init__.py +++ b/pyiceberg/expressions/__init__.py @@ -42,7 +42,7 @@ from pyiceberg.typedef import L, StructProtocol from pyiceberg.types import DoubleType, FloatType, NestedField from pyiceberg.utils.singleton import Singleton -from pyiceberg.utils.pydantic import IcebergBaseModel +from pyiceberg.typedef import IcebergBaseModel from pydantic import Field From 0db2562b0dadfeea3d41fa420daeda439a600b42 Mon Sep 17 00:00:00 2001 From: Aniket <148300120+Aniketsy@users.noreply.github.com> Date: Fri, 3 Oct 2025 01:26:49 +0530 Subject: [PATCH 09/20] Update pyiceberg/expressions/__init__.py Co-authored-by: Fokko Driesprong --- pyiceberg/expressions/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyiceberg/expressions/__init__.py b/pyiceberg/expressions/__init__.py index f28eca5b6f..342369514d 100644 --- a/pyiceberg/expressions/__init__.py +++ b/pyiceberg/expressions/__init__.py @@ -563,7 +563,6 @@ def as_bound(self) -> Type[BoundNotNaN[L]]: class SetPredicate(UnboundPredicate[L], IcebergBaseModel, ABC): type: str = Field(default="in", alias="type") - term: str literals: Set[Literal[L]] = Field(alias="items") def __init__(self, term: Union[str, UnboundTerm[Any]], literals: Union[Iterable[L], Iterable[Literal[L]]]): From ed611309de8fc7e31cfad92d0d7c116739808bee Mon Sep 17 00:00:00 2001 From: Aniket <148300120+Aniketsy@users.noreply.github.com> Date: Fri, 3 Oct 2025 01:27:15 +0530 Subject: [PATCH 10/20] Update tests/expressions/test_expressions.py Co-authored-by: Fokko Driesprong --- tests/expressions/test_expressions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/expressions/test_expressions.py b/tests/expressions/test_expressions.py index 8a169328b5..cc39b0420a 100644 --- a/tests/expressions/test_expressions.py +++ b/tests/expressions/test_expressions.py @@ -868,7 +868,7 @@ def test_not_in() -> None: assert not_in == eval(repr(not_in)) assert not_in == pickle.loads(pickle.dumps(not_in)) -def test_serialize_in(): +def test_serialize_in() -> None: pred = In(term="foo", literals=[1, 2, 3]) assert pred.model_dump_json() == '{"type":"in","term":"foo","value":[1,2,3]}' From ebaf94fe748dc092cc891ad86e2b71167728b6e9 Mon Sep 17 00:00:00 2001 From: Aniket <148300120+Aniketsy@users.noreply.github.com> Date: Fri, 3 Oct 2025 01:27:44 +0530 Subject: [PATCH 11/20] Update tests/expressions/test_expressions.py Co-authored-by: Fokko Driesprong --- tests/expressions/test_expressions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/expressions/test_expressions.py b/tests/expressions/test_expressions.py index cc39b0420a..049828d72f 100644 --- a/tests/expressions/test_expressions.py +++ b/tests/expressions/test_expressions.py @@ -872,7 +872,7 @@ def test_serialize_in() -> None: pred = In(term="foo", literals=[1, 2, 3]) assert pred.model_dump_json() == '{"type":"in","term":"foo","value":[1,2,3]}' -def test_serialize_not_in(): +def test_serialize_not_in() -> None: pred = NotIn(term="foo", literals=[1, 2, 3]) assert pred.model_dump_json() == '{"type":"not-in","term":"foo","value":[1,2,3]}' From 77e00b2bc704dac2310405074706d4b447a385a8 Mon Sep 17 00:00:00 2001 From: Aniket <148300120+Aniketsy@users.noreply.github.com> Date: Fri, 3 Oct 2025 01:44:21 +0530 Subject: [PATCH 12/20] Update pyiceberg/expressions/__init__.py Co-authored-by: Fokko Driesprong --- pyiceberg/expressions/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiceberg/expressions/__init__.py b/pyiceberg/expressions/__init__.py index 342369514d..3cc58bbe02 100644 --- a/pyiceberg/expressions/__init__.py +++ b/pyiceberg/expressions/__init__.py @@ -44,7 +44,7 @@ from pyiceberg.utils.singleton import Singleton from pyiceberg.typedef import IcebergBaseModel from pydantic import Field - +from typing import Literal as TypingLiteral def _to_unbound_term(term: Union[str, UnboundTerm[Any]]) -> UnboundTerm[Any]: return Reference(term) if isinstance(term, str) else term From 92cec6fb1e034450d942cedce580d003bf9d54d6 Mon Sep 17 00:00:00 2001 From: Aniket <148300120+Aniketsy@users.noreply.github.com> Date: Fri, 3 Oct 2025 01:44:58 +0530 Subject: [PATCH 13/20] Update pyiceberg/expressions/__init__.py Co-authored-by: Fokko Driesprong --- pyiceberg/expressions/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiceberg/expressions/__init__.py b/pyiceberg/expressions/__init__.py index 3cc58bbe02..d5aa994351 100644 --- a/pyiceberg/expressions/__init__.py +++ b/pyiceberg/expressions/__init__.py @@ -562,7 +562,7 @@ def as_bound(self) -> Type[BoundNotNaN[L]]: class SetPredicate(UnboundPredicate[L], IcebergBaseModel, ABC): - type: str = Field(default="in", alias="type") + type: TypingLiteral["in", "not-in"] = Field(default="in", alias="type") literals: Set[Literal[L]] = Field(alias="items") def __init__(self, term: Union[str, UnboundTerm[Any]], literals: Union[Iterable[L], Iterable[Literal[L]]]): From f09ba0214750ff40046b719d6f37eb4b434b5f22 Mon Sep 17 00:00:00 2001 From: Aniket <148300120+Aniketsy@users.noreply.github.com> Date: Fri, 3 Oct 2025 01:45:35 +0530 Subject: [PATCH 14/20] Update pyiceberg/expressions/__init__.py Co-authored-by: Fokko Driesprong --- pyiceberg/expressions/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiceberg/expressions/__init__.py b/pyiceberg/expressions/__init__.py index d5aa994351..f16649e727 100644 --- a/pyiceberg/expressions/__init__.py +++ b/pyiceberg/expressions/__init__.py @@ -679,7 +679,7 @@ def as_unbound(self) -> Type[NotIn[L]]: class In(SetPredicate[L]): - type: Literal["in"] = Field(default="in", alias="type") + 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]]] From f196a722ae55f95ab45f3f39a972ad1f1c94b5ee Mon Sep 17 00:00:00 2001 From: Aniket <148300120+Aniketsy@users.noreply.github.com> Date: Fri, 3 Oct 2025 01:45:58 +0530 Subject: [PATCH 15/20] Update pyiceberg/expressions/__init__.py Co-authored-by: Fokko Driesprong --- pyiceberg/expressions/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyiceberg/expressions/__init__.py b/pyiceberg/expressions/__init__.py index f16649e727..8d3909e256 100644 --- a/pyiceberg/expressions/__init__.py +++ b/pyiceberg/expressions/__init__.py @@ -703,7 +703,7 @@ def as_bound(self) -> Type[BoundIn[L]]: class NotIn(SetPredicate[L], ABC): - type: str = Field(default="not-in", alias="type") + 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]]] From 31469046d9cea1890e07f5376b0879bf001f3579 Mon Sep 17 00:00:00 2001 From: Aniket <148300120+Aniketsy@users.noreply.github.com> Date: Fri, 3 Oct 2025 09:04:20 +0530 Subject: [PATCH 16/20] Update pyiceberg/expressions/__init__.py Co-authored-by: Fokko Driesprong --- pyiceberg/expressions/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyiceberg/expressions/__init__.py b/pyiceberg/expressions/__init__.py index 8d3909e256..c273cf3d98 100644 --- a/pyiceberg/expressions/__init__.py +++ b/pyiceberg/expressions/__init__.py @@ -562,6 +562,8 @@ def as_bound(self) -> Type[BoundNotNaN[L]]: class SetPredicate(UnboundPredicate[L], IcebergBaseModel, ABC): + model_config = ConfigDict(arbitrary_types_allowed=True) + type: TypingLiteral["in", "not-in"] = Field(default="in", alias="type") literals: Set[Literal[L]] = Field(alias="items") From f922d178948087d760f1826b93be8bf3603e3ad8 Mon Sep 17 00:00:00 2001 From: Aniket Singh Yadav Date: Fri, 3 Oct 2025 09:39:21 +0530 Subject: [PATCH 17/20] Apply Ruff formatting fixes --- pyiceberg/expressions/__init__.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pyiceberg/expressions/__init__.py b/pyiceberg/expressions/__init__.py index c273cf3d98..8b6fbe4e4f 100644 --- a/pyiceberg/expressions/__init__.py +++ b/pyiceberg/expressions/__init__.py @@ -31,6 +31,9 @@ TypeVar, Union, ) +from typing import Literal as TypingLiteral + +from pydantic import Field from pyiceberg.expressions.literals import ( AboveMax, @@ -39,12 +42,15 @@ literal, ) from pyiceberg.schema import Accessor, Schema -from pyiceberg.typedef import L, StructProtocol +from pyiceberg.typedef import IcebergBaseModel, L, StructProtocol from pyiceberg.types import DoubleType, FloatType, NestedField from pyiceberg.utils.singleton import Singleton -from pyiceberg.typedef import IcebergBaseModel -from pydantic import Field -from typing import Literal as TypingLiteral + +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 From 6482846a77ce40816ae873ed213f25e5c954a67c Mon Sep 17 00:00:00 2001 From: Aniket Singh Yadav Date: Mon, 6 Oct 2025 12:48:54 +0530 Subject: [PATCH 18/20] Fixed import error --- pyiceberg/expressions/__init__.py | 4 +--- tests/expressions/test_expressions.py | 3 +++ tests/integration/test_writes/test_writes.py | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pyiceberg/expressions/__init__.py b/pyiceberg/expressions/__init__.py index 9c5a55eeec..7aedb4125b 100644 --- a/pyiceberg/expressions/__init__.py +++ b/pyiceberg/expressions/__init__.py @@ -35,8 +35,6 @@ from pydantic import Field -from pydantic import Field - from pyiceberg.expressions.literals import ( AboveMax, BelowMin, @@ -44,7 +42,7 @@ 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 diff --git a/tests/expressions/test_expressions.py b/tests/expressions/test_expressions.py index b870112d02..5123a62ea6 100644 --- a/tests/expressions/test_expressions.py +++ b/tests/expressions/test_expressions.py @@ -872,14 +872,17 @@ def test_not_in() -> None: assert not_in == eval(repr(not_in)) 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() == '{"type":"in","term":"foo","value":[1,2,3]}' + def test_serialize_not_in() -> None: pred = NotIn(term="foo", literals=[1, 2, 3]) assert pred.model_dump_json() == '{"type":"not-in","term":"foo","value":[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'))" diff --git a/tests/integration/test_writes/test_writes.py b/tests/integration/test_writes/test_writes.py index c7d79f2c37..6d80e7d26c 100644 --- a/tests/integration/test_writes/test_writes.py +++ b/tests/integration/test_writes/test_writes.py @@ -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}") From 110e4e3aa0e4fa0a8a69a2453c7f547a6d73d1f0 Mon Sep 17 00:00:00 2001 From: Aniket Singh Yadav Date: Wed, 8 Oct 2025 15:37:20 +0530 Subject: [PATCH 19/20] Make SetPredicate and subclasses JSON serializable --- pyiceberg/expressions/__init__.py | 9 ++++----- tests/expressions/test_expressions.py | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/pyiceberg/expressions/__init__.py b/pyiceberg/expressions/__init__.py index 7aedb4125b..3bf646ef69 100644 --- a/pyiceberg/expressions/__init__.py +++ b/pyiceberg/expressions/__init__.py @@ -577,15 +577,14 @@ def as_bound(self) -> Type[BoundNotNaN[L]]: return BoundNotNaN[L] -class SetPredicate(UnboundPredicate[L], IcebergBaseModel, ABC): +class SetPredicate(IcebergBaseModel, UnboundPredicate[L], ABC): model_config = ConfigDict(arbitrary_types_allowed=True) - type: TypingLiteral["in", "not-in"] = Field(default="in", alias="type") + 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) @@ -737,7 +736,7 @@ def __new__( # type: ignore # pylint: disable=W0221 def __invert__(self) -> In[L]: """Transform the Expression into its negated version.""" - return NotIn[L](self.term, self.literals) + return In[L](self.term, self.literals) @property def as_bound(self) -> Type[BoundNotIn[L]]: diff --git a/tests/expressions/test_expressions.py b/tests/expressions/test_expressions.py index 5123a62ea6..5a0c8c9241 100644 --- a/tests/expressions/test_expressions.py +++ b/tests/expressions/test_expressions.py @@ -875,12 +875,12 @@ def test_not_in() -> None: def test_serialize_in() -> None: pred = In(term="foo", literals=[1, 2, 3]) - assert pred.model_dump_json() == '{"type":"in","term":"foo","value":[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() == '{"type":"not-in","term":"foo","value":[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: From d6d6423882b3d64d15052a5a9904837d3095ebde Mon Sep 17 00:00:00 2001 From: Fokko Driesprong Date: Thu, 9 Oct 2025 17:08:28 +0200 Subject: [PATCH 20/20] Revert unrelated change --- tests/integration/test_writes/test_writes.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/integration/test_writes/test_writes.py b/tests/integration/test_writes/test_writes.py index 6d80e7d26c..c7d79f2c37 100644 --- a/tests/integration/test_writes/test_writes.py +++ b/tests/integration/test_writes/test_writes.py @@ -866,8 +866,7 @@ 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" - if hasattr(time, "tzset"): - time.tzset() + time.tzset() tz = pytz.timezone(os.environ["TZ"]) catalog = SqlCatalog("test_sql_catalog", uri="sqlite:///:memory:", warehouse=f"/{warehouse}")