|
25 | 25 | from narwhals._utils import Implementation, not_implemented |
26 | 26 |
|
27 | 27 | if TYPE_CHECKING: |
| 28 | + from typing import Union |
| 29 | + |
28 | 30 | import ibis.expr.types as ir |
29 | | - from typing_extensions import Self |
| 31 | + from typing_extensions import Self, TypeAlias |
30 | 32 |
|
31 | 33 | from narwhals._compliant.typing import ( |
32 | 34 | AliasNames, |
|
44 | 46 | IbisWindowFunction = WindowFunction[IbisLazyFrame, ir.Value] |
45 | 47 | IbisWindowInputs = WindowInputs[ir.Value] |
46 | 48 |
|
| 49 | + # NOTE: Needed to avoid `ibis` metaclass shenanigans breaking `__or__` |
| 50 | + # > Expected class but received "UnionType` Pylance(reportGeneralTypeIssues) |
| 51 | + LegacyWindow: TypeAlias = Union[ir.bl.LegacyWindowBuilder, None] |
| 52 | + |
47 | 53 |
|
48 | 54 | class IbisExpr(LazyExpr["IbisLazyFrame", "ir.Column"]): |
49 | 55 | _implementation = Implementation.IBIS |
@@ -374,15 +380,22 @@ def sum(self) -> Self: |
374 | 380 | return self._with_callable(lambda expr: expr.sum().fill_null(lit(0))) |
375 | 381 |
|
376 | 382 | def first(self) -> Self: |
377 | | - def fn(inputs: IbisWindowInputs) -> ir.Value: |
378 | | - order_by = [ibis.asc(by, nulls_first=True) for by in inputs.order_by] |
379 | | - expr = cast("ir.Column", inputs.expr) |
380 | | - if partition_by := inputs.partition_by: # pragma: no cover |
381 | | - window = ibis.window(group_by=list(partition_by), order_by=order_by) |
382 | | - return expr.first(include_null=True).over(window) |
| 383 | + def window( |
| 384 | + expr: ir.Value, order_by: Sequence[ir.Column], legacy: LegacyWindow |
| 385 | + ) -> ir.Value: |
| 386 | + expr = cast("ir.Column", expr) |
| 387 | + if legacy: # pragma: no cover |
| 388 | + return expr.first(include_null=True).over(legacy) |
383 | 389 | else: |
384 | 390 | return expr.first(order_by=order_by, include_null=True) |
385 | 391 |
|
| 392 | + def fn(df: IbisLazyFrame, inputs: IbisWindowInputs) -> Sequence[ir.Value]: |
| 393 | + legacy: LegacyWindow = None |
| 394 | + order_by = list(self._sort(*inputs.order_by)) |
| 395 | + if group_by := inputs.partition_by: # pragma: no cover |
| 396 | + legacy = ibis.window(group_by=list(group_by), order_by=order_by) |
| 397 | + return [window(expr, order_by, legacy) for expr in self(df)] |
| 398 | + |
386 | 399 | return self._with_window_function(fn) |
387 | 400 |
|
388 | 401 | def n_unique(self) -> Self: |
|
0 commit comments