Skip to content

Commit e8eb22e

Browse files
committed
chore: More coverage, simplify
1 parent 124706c commit e8eb22e

File tree

4 files changed

+23
-37
lines changed

4 files changed

+23
-37
lines changed

narwhals/_plan/_expansion.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ def prepare_projection(self, exprs: Collection[ExprIR], /) -> Seq[NamedIR]:
191191
raise NotImplementedError(msg)
192192
output_names.append(name)
193193
named_irs.append(ir.named_ir(name, remove_alias(target)))
194-
root_names.update(meta._expr_to_leaf_column_names_iter(e))
194+
root_names.update(meta.iter_root_names(e))
195195
if len(output_names) != len(set(output_names)):
196196
raise duplicate_error(expanded)
197197
if not (set(self.schema).issuperset(root_names)):

narwhals/_plan/meta.py

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from __future__ import annotations
88

99
from functools import lru_cache
10-
from itertools import chain
1110
from typing import TYPE_CHECKING, Literal, overload
1211

1312
from narwhals._plan import expressions as ir
@@ -19,7 +18,7 @@
1918
from narwhals.utils import Version
2019

2120
if TYPE_CHECKING:
22-
from collections.abc import Iterable, Iterator
21+
from collections.abc import Iterator
2322

2423
from narwhals._plan import Selector
2524

@@ -78,7 +77,7 @@ def output_name(self, *, raise_if_undetermined: bool = True) -> str | None:
7877

7978
def root_names(self) -> list[str]:
8079
"""Get the root column names."""
81-
return list(_expr_to_leaf_column_names_iter(self._ir))
80+
return list(iter_root_names(self._ir))
8281

8382
def as_selector(self) -> Selector:
8483
"""Try to turn this expression into a selector.
@@ -91,46 +90,17 @@ def as_selector(self) -> Selector:
9190
return self._ir.to_selector_ir().to_narwhals()
9291

9392

94-
def _expr_to_leaf_column_names_iter(expr: ir.ExprIR, /) -> Iterator[str]:
95-
for e in _expr_to_leaf_column_exprs_iter(expr):
96-
result = _expr_to_leaf_column_name(e)
97-
if isinstance(result, str):
98-
yield result
99-
100-
101-
def _expr_to_leaf_column_exprs_iter(expr: ir.ExprIR, /) -> Iterator[ir.ExprIR]:
102-
for outer in expr.iter_root_names():
103-
if isinstance(outer, ir.Column):
104-
yield outer
105-
106-
107-
def _expr_to_leaf_column_name(expr: ir.ExprIR, /) -> str | ComputeError:
108-
leaves = list(_expr_to_leaf_column_exprs_iter(expr))
109-
if not len(leaves) <= 1:
110-
msg = "found more than one root column name"
111-
return ComputeError(msg)
112-
if not leaves:
113-
msg = "no root column name found"
114-
return ComputeError(msg)
115-
leaf = leaves[0]
116-
if isinstance(leaf, ir.Column):
117-
return leaf.name
118-
msg = f"Expected unreachable, got {type(leaf).__name__!r}\n\n{leaf}"
119-
return ComputeError(msg)
93+
def iter_root_names(expr: ir.ExprIR, /) -> Iterator[str]:
94+
yield from (e.name for e in expr.iter_root_names() if isinstance(e, ir.Column))
12095

12196

12297
def root_name_first(expr: ir.ExprIR, /) -> str:
123-
it = _expr_to_leaf_column_names_iter(expr)
124-
if name := next(it, None):
98+
if name := next(iter_root_names(expr), None):
12599
return name
126-
msg = "`name.keep_name` expected at least one column name"
100+
msg = f"`name.keep_name` expected at least one column name, got `{expr!r}`"
127101
raise InvalidOperationError(msg)
128102

129103

130-
def root_names_unique(exprs: Iterable[ir.ExprIR], /) -> set[str]:
131-
return set(chain.from_iterable(_expr_to_leaf_column_names_iter(e) for e in exprs))
132-
133-
134104
@lru_cache(maxsize=32)
135105
def _expr_output_name(expr: ir.ExprIR, /) -> str | ComputeError:
136106
for e in expr.iter_output_name():

tests/plan/expr_expansion_test.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from narwhals.exceptions import (
1313
ColumnNotFoundError,
1414
DuplicateError,
15+
InvalidOperationError,
1516
MultiOutputExpressionError,
1617
)
1718
from tests.plan.utils import Frame, assert_expr_ir_equal, named_ir, re_compile
@@ -128,6 +129,14 @@ def test_special_aliases_single(expr: nwp.Expr, expected: str) -> None:
128129
df.assert_selects(expr, expected)
129130

130131

132+
def test_keep_name_no_names(df_1: Frame) -> None:
133+
with pytest.raises(
134+
InvalidOperationError,
135+
match=r"expected at least one.+name.+got.+lit.+1.+name.keep",
136+
):
137+
df_1.project(nwp.lit(1).name.keep())
138+
139+
131140
def alias_replace_guarded(name: str) -> MapIR:
132141
"""Guards against repeatedly creating the same alias."""
133142

tests/plan/meta_test.py

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

33
import datetime as dt
44
import re
5+
import string
56
from typing import Any
67

78
import pytest
@@ -56,6 +57,11 @@
5657
),
5758
(nwp.all().mean(), pl.all().mean(), []),
5859
(nwp.all().mean().sort_by("d"), pl.all().mean().sort_by("d"), ["d"]),
60+
(
61+
nwp.all_horizontal(*string.ascii_letters),
62+
pl.all_horizontal(*string.ascii_letters),
63+
list(string.ascii_letters),
64+
),
5965
],
6066
)
6167
def test_meta_root_names(
@@ -319,6 +325,7 @@ def test_literal_output_name() -> None:
319325
assert e.meta.output_name() == ""
320326

321327

328+
# NOTE: Very low-priority
322329
@pytest.mark.xfail(
323330
reason="TODO: `Expr.struct.field` influences `meta.output_name`.",
324331
raises=AssertionError,

0 commit comments

Comments
 (0)