Skip to content

Commit d829677

Browse files
committed
test: Port over upstream test_meta.py
Anything not covered by these tests can be removed (#3233 (comment))
1 parent 2847cbb commit d829677

File tree

2 files changed

+161
-0
lines changed

2 files changed

+161
-0
lines changed

narwhals/_plan/series.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
if TYPE_CHECKING:
1010
from collections.abc import Iterable, Iterator
1111

12+
from typing_extensions import Self
13+
1214
from narwhals._plan.compliant.series import CompliantSeries
1315
from narwhals._typing import EagerAllowed, IntoBackend
1416
from narwhals.dtypes import DType
@@ -77,6 +79,9 @@ def to_list(self) -> list[Any]:
7779
def __iter__(self) -> Iterator[Any]:
7880
yield from self.to_native()
7981

82+
def alias(self, name: str) -> Self:
83+
return type(self)(self._compliant.alias(name))
84+
8085

8186
class SeriesV1(Series[NativeSeriesT_co]):
8287
_version: ClassVar[Version] = Version.V1

tests/plan/meta_test.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
from __future__ import annotations
22

3+
import datetime as dt
4+
import re
5+
from typing import Any
6+
37
import pytest
48

9+
import narwhals._plan.selectors as ncs
510
from narwhals import _plan as nwp
11+
from narwhals.exceptions import ComputeError
12+
from tests.plan.utils import series
613
from tests.utils import POLARS_VERSION
714

815
pytest.importorskip("polars")
@@ -23,6 +30,11 @@
2330
LEN_CASE = (nwp.len().alias("count"), pl.count(), "count")
2431

2532

33+
XFAIL_LITERAL_LIST = pytest.mark.xfail(
34+
reason="'list' is not supported in `nw.lit`", raises=TypeError
35+
)
36+
37+
2638
@pytest.mark.parametrize(
2739
("nw_expr", "pl_expr", "expected"),
2840
[
@@ -181,3 +193,147 @@ def test_meta_output_name(nw_expr: nwp.Expr, pl_expr: pl.Expr, expected: str) ->
181193
nw_result = nw_expr.meta.output_name()
182194
assert nw_result == expected
183195
assert nw_result == pl_result
196+
197+
198+
def test_root_and_output_names() -> None:
199+
e = nwp.col("foo") * nwp.col("bar")
200+
assert e.meta.output_name() == "foo"
201+
assert e.meta.root_names() == ["foo", "bar"]
202+
203+
e = nwp.col("foo").filter(bar=13)
204+
assert e.meta.output_name() == "foo"
205+
assert e.meta.root_names() == ["foo", "bar"]
206+
207+
e = nwp.sum("foo").over("groups")
208+
assert e.meta.output_name() == "foo"
209+
assert e.meta.root_names() == ["foo", "groups"]
210+
211+
e = nwp.sum("foo").is_between(nwp.len() - 10, nwp.col("bar"))
212+
assert e.meta.output_name() == "foo"
213+
assert e.meta.root_names() == ["foo", "bar"]
214+
215+
e = nwp.len()
216+
assert e.meta.output_name() == "len"
217+
218+
with pytest.raises(
219+
ComputeError,
220+
match=re.escape(
221+
"unable to find root column name for expr 'ncs.all()' when calling 'output_name'"
222+
),
223+
):
224+
nwp.all().name.suffix("_").meta.output_name()
225+
226+
assert (
227+
nwp.all().name.suffix("_").meta.output_name(raise_if_undetermined=False) is None
228+
)
229+
230+
231+
def test_meta_has_multiple_outputs() -> None:
232+
e = nwp.col(["a", "b"]).name.suffix("_foo")
233+
assert e.meta.has_multiple_outputs()
234+
235+
236+
def test_is_column() -> None:
237+
e = nwp.col("foo")
238+
assert e.meta.is_column()
239+
240+
e = nwp.col("foo").alias("bar")
241+
assert not e.meta.is_column()
242+
243+
e = nwp.col("foo") * nwp.col("bar")
244+
assert not e.meta.is_column()
245+
246+
247+
# TODO @dangotbanned: Uncomment the cases as they're added
248+
@pytest.mark.parametrize(
249+
("expr", "is_column_selection"),
250+
[
251+
# columns
252+
(nwp.col("foo"), True),
253+
(nwp.col("foo", "bar"), True),
254+
# column expressions
255+
(nwp.col("foo") + 100, False),
256+
(nwp.col("foo").__floordiv__(10), False),
257+
(nwp.col("foo") * nwp.col("bar"), False),
258+
# selectors / expressions
259+
(ncs.numeric() * 100, False),
260+
# (ncs.temporal() - ncs.time(), True), # noqa: ERA001
261+
(ncs.numeric().exclude("value"), True),
262+
# ((ncs.temporal() - ncs.time()).exclude("dt"), True), # noqa: ERA001
263+
# top-level selection funcs
264+
(nwp.nth(2), True),
265+
# (nwp.first(), True), # noqa: ERA001
266+
# (nwp.last(), True), # noqa: ERA001
267+
],
268+
)
269+
def test_is_column_selection(expr: nwp.Expr, *, is_column_selection: bool) -> None:
270+
if is_column_selection:
271+
assert expr.meta.is_column_selection()
272+
assert expr.meta.is_column_selection(allow_aliasing=True)
273+
expr = (
274+
expr.name.suffix("!") if expr.meta.has_multiple_outputs() else expr.alias("!")
275+
)
276+
assert not expr.meta.is_column_selection()
277+
assert expr.meta.is_column_selection(allow_aliasing=True)
278+
else:
279+
assert not expr.meta.is_column_selection()
280+
281+
282+
@pytest.mark.parametrize(
283+
"value",
284+
[
285+
None,
286+
1234,
287+
567.89,
288+
float("inf"),
289+
dt.date(2000, 1, 1),
290+
dt.datetime(1974, 1, 1, 12, 45, 1),
291+
dt.time(10, 30, 45),
292+
dt.timedelta(hours=-24),
293+
pytest.param(["x", "y", "z"], marks=XFAIL_LITERAL_LIST),
294+
series([None, None]),
295+
pytest.param([[10, 20], [30, 40]], marks=XFAIL_LITERAL_LIST),
296+
"this is the way",
297+
],
298+
)
299+
def test_is_literal(value: Any) -> None:
300+
e = nwp.lit(value)
301+
assert e.meta.is_literal()
302+
303+
e = nwp.lit(value).alias("foo")
304+
assert not e.meta.is_literal()
305+
306+
e = nwp.lit(value).alias("foo")
307+
assert e.meta.is_literal(allow_aliasing=True)
308+
309+
310+
def test_literal_output_name() -> None:
311+
e = nwp.lit(1)
312+
data = 1, 2, 3
313+
assert e.meta.output_name() == "literal"
314+
315+
e = nwp.lit(series(data).alias("abc"))
316+
assert e.meta.output_name() == "abc"
317+
318+
e = nwp.lit(series(data))
319+
assert e.meta.output_name() == ""
320+
321+
322+
@pytest.mark.xfail(
323+
reason="TODO: `Expr.struct.field` influences `meta.output_name`.",
324+
raises=AssertionError,
325+
)
326+
def test_struct_field_output_name_24003() -> None:
327+
assert nwp.col("ball").struct.field("radius").meta.output_name() == "radius"
328+
329+
330+
@pytest.mark.xfail(
331+
reason="TODO: Single `cs.by_name` can return the name.", raises=ComputeError
332+
)
333+
def test_selector_by_name_single() -> None:
334+
assert ncs.by_name("foo").meta.output_name() == "foo"
335+
336+
337+
def test_selector_by_name_multiple() -> None:
338+
with pytest.raises(ComputeError):
339+
ncs.by_name(["foo", "bar"]).meta.output_name()

0 commit comments

Comments
 (0)