Skip to content

Commit 4d27c83

Browse files
committed
Fix deepcopy for BooleanExpression subclasses
Add __deepcopy__ to BooleanExpression that reconstructs the expression via model_fields, and returns self for Singleton subclasses. Fixes TypeError from Pydantic v2's BaseModel.__deepcopy__ calling cls.__new__(cls) with no args on And, Or, and Not which require positional arguments in __new__.
1 parent 9991c6b commit 4d27c83

2 files changed

Lines changed: 19 additions & 0 deletions

File tree

pyiceberg/expressions/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
from __future__ import annotations
1919

20+
import copy
2021
from abc import ABC, abstractmethod
2122
from collections.abc import Callable, Iterable, Sequence
2223
from functools import cached_property
@@ -51,6 +52,12 @@ def _to_literal(value: L | Literal[L]) -> Literal[L]:
5152
class BooleanExpression(IcebergBaseModel, ABC):
5253
"""An expression that evaluates to a boolean."""
5354

55+
def __deepcopy__(self, memo: dict[int, Any]) -> BooleanExpression:
56+
if isinstance(self, Singleton):
57+
return self
58+
fields = {name: copy.deepcopy(getattr(self, name), memo) for name in type(self).model_fields}
59+
return type(self)(**fields)
60+
5461
@abstractmethod
5562
def __invert__(self) -> BooleanExpression:
5663
"""Transform the Expression into its negated version."""

tests/expressions/test_expressions.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,6 +1336,18 @@ def test_deepcopy_always_true_then_pickle() -> None:
13361336
assert restored is AlwaysTrue()
13371337

13381338

1339+
def test_deepcopy_balanced_and() -> None:
1340+
expr = And(EqualTo("a", 1), EqualTo("b", 2), EqualTo("c", 3), EqualTo("d", 4))
1341+
copied = copy.deepcopy(expr)
1342+
assert copied == expr
1343+
1344+
1345+
def test_deepcopy_balanced_or() -> None:
1346+
expr = Or(EqualTo("a", 1), EqualTo("b", 2), EqualTo("c", 3), EqualTo("d", 4))
1347+
copied = copy.deepcopy(expr)
1348+
assert copied == expr
1349+
1350+
13391351
def test_deepcopy_nested_expression() -> None:
13401352
expr = And(Or(EqualTo("a", 1), EqualTo("b", 2)), Not(EqualTo("c", 3)))
13411353
copied = copy.deepcopy(expr)

0 commit comments

Comments
 (0)