Skip to content

Commit a8726b6

Browse files
committed
Widen term field type for mypy compatibility without pydantic plugin
Without the pydantic mypy plugin, mypy generates __init__ signatures from field declarations rather than from explicit __init__ overrides. This caused EqualTo(term="col", value=42) to produce: error: Argument "term" has incompatible type "str"; expected "UnboundTerm" Fix: widen field type from UnboundTerm to str | UnboundTerm with a BeforeValidator that coerces str to Reference (matching the existing _to_unbound_term helper). The validator ensures the stored value is always UnboundTerm at runtime. Closes #3101
1 parent d99e463 commit a8726b6

1 file changed

Lines changed: 7 additions & 7 deletions

File tree

pyiceberg/expressions/__init__.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121
from abc import ABC, abstractmethod
2222
from collections.abc import Callable, Iterable, Sequence
2323
from functools import cached_property
24-
from typing import Any, TypeAlias
24+
from typing import Annotated, Any, TypeAlias
2525
from typing import Literal as TypingLiteral
2626

27-
from pydantic import ConfigDict, Field, SerializeAsAny, model_validator
27+
from pydantic import BeforeValidator, ConfigDict, Field, SerializeAsAny, model_validator
2828
from pydantic_core.core_schema import ValidatorFunctionWrapHandler
2929

3030
from pyiceberg.expressions.literals import AboveMax, BelowMin, Literal, literal
@@ -508,7 +508,7 @@ def as_unbound(self) -> type[UnboundPredicate]: ...
508508
class UnboundPredicate(Unbound, BooleanExpression, ABC):
509509
model_config = ConfigDict(arbitrary_types_allowed=True)
510510

511-
term: UnboundTerm
511+
term: Annotated[str | UnboundTerm, BeforeValidator(_to_unbound_term)]
512512

513513
def __init__(self, term: str | UnboundTerm, **kwargs: Any) -> None:
514514
super().__init__(term=_to_unbound_term(term), **kwargs)
@@ -540,7 +540,7 @@ def __str__(self) -> str:
540540
return f"{str(self.__class__.__name__)}(term={str(self.term)})"
541541

542542
def bind(self, schema: Schema, case_sensitive: bool = True) -> BoundUnaryPredicate:
543-
bound_term = self.term.bind(schema, case_sensitive)
543+
bound_term = self.term.bind(schema, case_sensitive) # type: ignore[union-attr]
544544
bound_type = self.as_bound
545545
return bound_type(bound_term) # type: ignore[misc]
546546

@@ -696,7 +696,7 @@ def __init__(
696696
super().__init__(term=_to_unbound_term(term), values=literal_set)
697697

698698
def bind(self, schema: Schema, case_sensitive: bool = True) -> BoundSetPredicate:
699-
bound_term = self.term.bind(schema, case_sensitive)
699+
bound_term = self.term.bind(schema, case_sensitive) # type: ignore[union-attr]
700700
literal_set = self.literals
701701
return self.as_bound(bound_term, {lit.to(bound_term.ref().field.field_type) for lit in literal_set}) # type: ignore
702702

@@ -870,7 +870,7 @@ def as_bound(self) -> type[BoundNotIn]: # type: ignore
870870

871871
class LiteralPredicate(UnboundPredicate, ABC):
872872
type: TypingLiteral["lt", "lt-eq", "gt", "gt-eq", "eq", "not-eq", "starts-with", "not-starts-with"] = Field(alias="type")
873-
term: UnboundTerm
873+
term: Annotated[str | UnboundTerm, BeforeValidator(_to_unbound_term)]
874874
value: LiteralValue = Field()
875875
model_config = ConfigDict(populate_by_name=True, frozen=True, arbitrary_types_allowed=True)
876876

@@ -885,7 +885,7 @@ def literal(self) -> LiteralValue:
885885
return self.value
886886

887887
def bind(self, schema: Schema, case_sensitive: bool = True) -> BoundLiteralPredicate:
888-
bound_term = self.term.bind(schema, case_sensitive)
888+
bound_term = self.term.bind(schema, case_sensitive) # type: ignore[union-attr]
889889
lit = self.literal.to(bound_term.ref().field.field_type)
890890

891891
if isinstance(lit, AboveMax):

0 commit comments

Comments
 (0)