Skip to content

Commit d5fc364

Browse files
committed
feat(api): allow transmute-style select method
1 parent 2bd075f commit d5fc364

File tree

2 files changed

+43
-14
lines changed

2 files changed

+43
-14
lines changed

ibis/expr/types/relations.py

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import functools
55
import itertools
66
import operator
7+
import warnings
78
from functools import cached_property
89
from typing import IO, TYPE_CHECKING, Any, Iterable, Literal, Mapping, Sequence
910

@@ -112,13 +113,13 @@ def __getitem__(self, what):
112113
return what._to_semi_join(self)[self]
113114
elif isinstance(what, (list, tuple, Table)):
114115
# Projection case
115-
return self.projection(what)
116+
return self.select(what)
116117
elif isinstance(what, BooleanColumn):
117118
# Boolean predicate
118119
return self.filter([what])
119120
elif isinstance(what, Column):
120121
# Projection convenience
121-
return self.projection(what)
122+
return self.select(what)
122123
else:
123124
raise NotImplementedError(
124125
'Selection rows or columns with {} objects is not '
@@ -572,11 +573,12 @@ def mutate(
572573
exprs.append(value.name(name))
573574

574575
mutation_exprs = an.get_mutation_exprs(exprs, self)
575-
return self.projection(mutation_exprs)
576+
return self.select(mutation_exprs)
576577

577578
def select(
578579
self,
579-
exprs: ir.Value | str | Sequence[ir.Value | str],
580+
*exprs: ir.Value | str | Iterable[ir.Value | str],
581+
**named_exprs: ir.Value | str,
580582
) -> Table:
581583
"""Compute a new table expression using `exprs`.
582584
@@ -585,6 +587,10 @@ def select(
585587
automatically constructs a window function expression. See the examples
586588
section for more details.
587589
590+
For backwards compatibility the keyword argument `exprs` is reserved
591+
and cannot be used to name an expression. This behavior will be removed
592+
in v4.
593+
588594
Parameters
589595
----------
590596
exprs
@@ -601,9 +607,8 @@ def select(
601607
Simple projection
602608
603609
>>> import ibis
604-
>>> fields = [('a', 'int64'), ('b', 'double')]
605-
>>> t = ibis.table(fields, name='t')
606-
>>> proj = t.projection([t.a, (t.b + 1).name('b_plus_1')])
610+
>>> t = ibis.table(dict(a="int64", b="double"), name='t')
611+
>>> proj = t.select(t.a, b_plus_1=t.b + 1)
607612
>>> proj
608613
r0 := UnboundTable[t]
609614
a int64
@@ -612,13 +617,13 @@ def select(
612617
selections:
613618
a: r0.a
614619
b_plus_1: r0.b + 1
615-
>>> proj2 = t[t.a, (t.b + 1).name('b_plus_1')]
620+
>>> proj2 = t.select("a", b_plus_1=t.b + 1)
616621
>>> proj.equals(proj2)
617622
True
618623
619624
Aggregate projection
620625
621-
>>> agg_proj = t[t.a.sum().name('sum_a'), t.b.mean().name('mean_b')]
626+
>>> agg_proj = t.select(sum_a=t.a.sum(), mean_b=t.b.mean())
622627
>>> agg_proj
623628
r0 := UnboundTable[t]
624629
a int64
@@ -635,7 +640,7 @@ def select(
635640
The purpose of this expression rewrite is to make it easy to write
636641
column/scalar-aggregate operations like
637642
638-
>>> t[(t.a - t.a.mean()).name('demeaned_a')]
643+
>>> t.select(demeaned_a=t.a - t.a.mean())
639644
r0 := UnboundTable[t]
640645
a int64
641646
b float64
@@ -645,8 +650,24 @@ def select(
645650
"""
646651
import ibis.expr.analysis as an
647652

648-
if isinstance(exprs, (Expr, str)):
649-
exprs = [exprs]
653+
if backcompat_exprs := named_exprs.pop("exprs", []):
654+
warnings.warn(
655+
"Passing `exprs` as a keyword argument is deprecated"
656+
" and will be removed in 4.0. Pass the value(s) as"
657+
" positional arguments.",
658+
FutureWarning,
659+
)
660+
661+
exprs = list(
662+
itertools.chain(
663+
itertools.chain.from_iterable(map(util.promote_list, exprs)),
664+
util.promote_list(backcompat_exprs),
665+
(
666+
self._ensure_expr(expr).name(name)
667+
for name, expr in named_exprs.items()
668+
),
669+
)
670+
)
650671

651672
projector = an.Projector(self, exprs)
652673
op = projector.get_result()
@@ -681,7 +702,7 @@ def relabel(self, substitutions: Mapping[str, str]) -> Table:
681702
if c not in observed:
682703
raise KeyError(f'{c!r} is not an existing column')
683704

684-
return self.projection(exprs)
705+
return self.select(exprs)
685706

686707
def drop(self, fields: str | Sequence[str]) -> Table:
687708
"""Remove fields from a table.
@@ -905,7 +926,7 @@ def set_column(self, name: str, expr: ir.Value) -> Table:
905926
else:
906927
proj_exprs.append(self[key])
907928

908-
return self.projection(proj_exprs)
929+
return self.select(proj_exprs)
909930

910931
def join(
911932
left: Table,

ibis/tests/expr/test_table.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1518,3 +1518,11 @@ def test_materialize_no_op():
15181518
expr = left.inner_join(right, "id")
15191519
with pytest.warns(FutureWarning):
15201520
expr.materialize()
1521+
1522+
1523+
def test_exprs_to_select():
1524+
t = ibis.table(dict(a="string"))
1525+
exprs = [t.a.length().name("len")]
1526+
with pytest.warns(FutureWarning, match="Passing `exprs`"):
1527+
result = t.select(exprs=exprs)
1528+
assert result.equals(t.select(len=t.a.length()))

0 commit comments

Comments
 (0)