Skip to content

Commit 137686a

Browse files
committed
test(expr-ir): Add __dict__ test for Immutable
Bug discovered by @FBruzzesi #3194 (comment) Now I need to fix 200/222 failures 😂
1 parent 1a433a9 commit 137686a

File tree

2 files changed

+65
-1
lines changed

2 files changed

+65
-1
lines changed

narwhals/_plan/expressions/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,14 @@
1010
from narwhals._plan.expressions import (
1111
aggregation,
1212
boolean,
13+
categorical,
1314
functions,
15+
lists,
1416
operators,
1517
selectors,
18+
strings,
19+
struct,
20+
temporal,
1621
)
1722
from narwhals._plan.expressions.aggregation import AggExpr, OrderableAggExpr, max, min
1823
from narwhals._plan.expressions.expr import (
@@ -85,10 +90,12 @@
8590
"_ColumnSelection",
8691
"aggregation",
8792
"boolean",
93+
"categorical",
8894
"col",
8995
"cols",
9096
"functions",
9197
"index_columns",
98+
"lists",
9299
"max",
93100
"min",
94101
"named_ir",
@@ -97,4 +104,7 @@
97104
"over",
98105
"over_ordered",
99106
"selectors",
107+
"strings",
108+
"struct",
109+
"temporal",
100110
]

tests/plan/immutable_test.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
from __future__ import annotations
22

3+
import re
34
import string
45
from itertools import repeat
5-
from typing import Any
6+
from typing import TYPE_CHECKING, Any, TypeVar
67

78
import pytest
89

910
from narwhals._plan._immutable import Immutable
1011

12+
if TYPE_CHECKING:
13+
from collections.abc import Iterator
14+
T_co = TypeVar("T_co", covariant=True)
15+
1116

1217
class Empty(Immutable): ...
1318

@@ -147,3 +152,52 @@ def test_immutable_hash_cache() -> None:
147152
cached = obj.__immutable_hash_value__
148153
hash_cache_hit = hash(obj)
149154
assert hash_cache_miss == cached == hash_cache_hit
155+
156+
157+
def _collect_immutable_descendants() -> list[type[Immutable]]:
158+
# NOTE: Will populate `__subclasses__` by bringing the defs into scope
159+
from narwhals._plan import (
160+
_expansion,
161+
_expr_ir,
162+
_function,
163+
expressions,
164+
options,
165+
schema,
166+
when_then,
167+
)
168+
169+
_ = expressions, schema, options, _expansion, _expr_ir, _function, when_then
170+
return sorted(set(_iter_descendants(Immutable)), key=repr)
171+
172+
173+
def _iter_descendants(*bases: type[T_co]) -> Iterator[type[T_co]]:
174+
seen = set[T_co]()
175+
for base in bases:
176+
yield base
177+
if (children := (base.__subclasses__())) and (
178+
unseen := set(children).difference(seen)
179+
):
180+
yield from _iter_descendants(*unseen)
181+
182+
183+
@pytest.fixture(
184+
params=_collect_immutable_descendants(), ids=lambda tp: tp.__name__, scope="session"
185+
)
186+
def immutable_type(request: pytest.FixtureRequest) -> type[Immutable]:
187+
return request.param # type: ignore[no-any-return]
188+
189+
190+
def test_immutable___slots___(immutable_type: type[Immutable]) -> None:
191+
featureless_instance = object.__new__(immutable_type)
192+
193+
# NOTE: If this fails, `__setattr__` has been overriden
194+
with pytest.raises(AttributeError, match=r"immutable"):
195+
featureless_instance.i_dont_exist = 999 # type: ignore[assignment]
196+
197+
# NOTE: If this fails, `__slots__` lose the size benefit
198+
with pytest.raises(AttributeError, match=re.escape("has no attribute '__dict__'")):
199+
_ = featureless_instance.__dict__
200+
201+
slots = immutable_type.__slots__
202+
if slots:
203+
assert len(slots) != 0, slots

0 commit comments

Comments
 (0)