Skip to content

Commit 92694ce

Browse files
committed
feat(DRAFT): Mock up to_compliant as an adapter
An experiment towards (#2572 (comment))
1 parent e5a1237 commit 92694ce

File tree

7 files changed

+91
-17
lines changed

7 files changed

+91
-17
lines changed

narwhals/_namespace.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,9 @@ def from_backend(cls, backend: EagerAllowed, /) -> EagerAllowedNamespace: ...
203203

204204
@overload
205205
@classmethod
206-
def from_backend(cls, backend: ModuleType, /) -> Namespace[CompliantNamespaceAny]: ...
206+
def from_backend(
207+
cls, backend: IntoBackend, /
208+
) -> Namespace[CompliantNamespaceAny]: ...
207209

208210
@classmethod
209211
def from_backend(

narwhals/_plan/common.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,15 @@
44
from decimal import Decimal
55
from typing import TYPE_CHECKING, Generic, TypeVar
66

7-
from narwhals._plan.typing import IRNamespaceT
7+
from narwhals._plan.typing import ExprT, IRNamespaceT, Ns
88
from narwhals.utils import Version
99

1010
if TYPE_CHECKING:
1111
from typing import Any, Callable, Iterator
1212

1313
from typing_extensions import Never, Self, TypeAlias, TypeIs, dataclass_transform
1414

15-
from narwhals._plan.dummy import (
16-
DummyCompliantExpr,
17-
DummyExpr,
18-
DummySelector,
19-
DummySeries,
20-
)
15+
from narwhals._plan.dummy import DummyExpr, DummySelector, DummySeries
2116
from narwhals._plan.expr import FunctionExpr
2217
from narwhals._plan.lists import IRListNamespace
2318
from narwhals._plan.meta import IRMetaNamespace
@@ -146,10 +141,8 @@ def to_narwhals(self, version: Version = Version.MAIN) -> DummyExpr:
146141
return dummy.DummyExpr._from_ir(self)
147142
return dummy.DummyExprV1._from_ir(self)
148143

149-
def to_compliant(self, version: Version = Version.MAIN) -> DummyCompliantExpr:
150-
from narwhals._plan.dummy import DummyCompliantExpr
151-
152-
return DummyCompliantExpr._from_ir(self, version)
144+
def to_compliant(self, plx: Ns[ExprT], /) -> ExprT:
145+
raise NotImplementedError
153146

154147
@property
155148
def is_scalar(self) -> bool:

narwhals/_plan/dummy.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from narwhals._plan.strings import ExprStringNamespace
3838
from narwhals._plan.struct import ExprStructNamespace
3939
from narwhals._plan.temporal import ExprDateTimeNamespace
40+
from narwhals._plan.typing import ExprT, Ns
4041
from narwhals.typing import (
4142
FillNullStrategy,
4243
NativeSeries,
@@ -62,6 +63,9 @@ def _from_ir(cls, ir: ExprIR, /) -> Self:
6263
obj._ir = ir
6364
return obj
6465

66+
def _to_compliant(self, plx: Ns[ExprT], /) -> ExprT:
67+
return self._ir.to_compliant(plx)
68+
6569
@property
6670
def version(self) -> Version:
6771
return self._version

narwhals/_plan/expr.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
from narwhals._plan.common import ExprIR, SelectorIR, _field_str
1717
from narwhals._plan.name import KeepName, RenameAlias
1818
from narwhals._plan.typing import (
19+
ExprT,
1920
FunctionT,
2021
LeftSelectorT,
2122
LeftT,
23+
Ns,
2224
OperatorT,
2325
RightSelectorT,
2426
RightT,
@@ -97,6 +99,9 @@ class Column(ExprIR):
9799
def __repr__(self) -> str:
98100
return f"col({self.name!r})"
99101

102+
def to_compliant(self, plx: Ns[ExprT], /) -> ExprT:
103+
return plx.col(self.name)
104+
100105

101106
class Columns(ExprIR):
102107
__slots__ = ("names",)
@@ -106,6 +111,9 @@ class Columns(ExprIR):
106111
def __repr__(self) -> str:
107112
return f"cols({list(self.names)!r})"
108113

114+
def to_compliant(self, plx: Ns[ExprT], /) -> ExprT:
115+
return plx.col(*self.names)
116+
109117

110118
class Literal(ExprIR):
111119
"""https://github.com/pola-rs/polars/blob/dafd0a2d0e32b52bcfa4273bffdd6071a0d5977a/crates/polars-plan/src/dsl/expr.rs#L81."""
@@ -129,6 +137,9 @@ def name(self) -> str:
129137
def __repr__(self) -> str:
130138
return f"lit({self.value!r})"
131139

140+
def to_compliant(self, plx: Ns[ExprT], /) -> ExprT:
141+
return plx.lit(self.value.unwrap(), self.dtype)
142+
132143

133144
class _BinaryOp(ExprIR, t.Generic[LeftT, OperatorT, RightT]):
134145
__slots__ = ("left", "op", "right")

narwhals/_plan/literal.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22

3-
from typing import TYPE_CHECKING
3+
from typing import TYPE_CHECKING, Any, Generic
44

55
from narwhals._plan.common import Immutable
66

@@ -10,8 +10,15 @@
1010
from narwhals.dtypes import DType
1111
from narwhals.typing import NonNestedLiteral
1212

13+
from narwhals._typing_compat import TypeVar
1314

14-
class LiteralValue(Immutable):
15+
T = TypeVar("T", default=Any)
16+
NonNestedLiteralT = TypeVar(
17+
"NonNestedLiteralT", bound="NonNestedLiteral", default="NonNestedLiteral"
18+
)
19+
20+
21+
class LiteralValue(Immutable, Generic[T]):
1522
"""https://github.com/pola-rs/polars/blob/dafd0a2d0e32b52bcfa4273bffdd6071a0d5977a/crates/polars-plan/src/plans/lit.rs#L67-L73."""
1623

1724
@property
@@ -31,11 +38,14 @@ def to_literal(self) -> Literal:
3138

3239
return Literal(value=self)
3340

41+
def unwrap(self) -> T:
42+
raise NotImplementedError
43+
3444

35-
class ScalarLiteral(LiteralValue):
45+
class ScalarLiteral(LiteralValue[NonNestedLiteralT]):
3646
__slots__ = ("dtype", "value")
3747

38-
value: NonNestedLiteral
48+
value: NonNestedLiteralT
3949
dtype: DType
4050

4151
@property
@@ -47,8 +57,11 @@ def __repr__(self) -> str:
4757
return f"{type(self.value).__name__}: {self.value!s}"
4858
return "null"
4959

60+
def unwrap(self) -> NonNestedLiteralT:
61+
return self.value
62+
5063

51-
class SeriesLiteral(LiteralValue):
64+
class SeriesLiteral(LiteralValue["DummySeries"]):
5265
"""We already need this.
5366
5467
https://github.com/narwhals-dev/narwhals/blob/e51eba891719a5eb1f7ce91c02a477af39c0baee/narwhals/_expression_parsing.py#L96-L97
@@ -69,6 +82,9 @@ def name(self) -> str:
6982
def __repr__(self) -> str:
7083
return "Series"
7184

85+
def unwrap(self) -> DummySeries:
86+
return self.value
87+
7288

7389
class RangeLiteral(LiteralValue):
7490
"""Don't need yet, but might push forward the discussions.

narwhals/_plan/typing.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
from narwhals._typing_compat import TypeVar
66

77
if t.TYPE_CHECKING:
8+
from typing_extensions import TypeAlias
9+
10+
from narwhals._compliant import CompliantNamespace as Namespace
11+
from narwhals._compliant.typing import CompliantExprAny
812
from narwhals._plan import operators as ops
913
from narwhals._plan.common import ExprIR, Function, IRNamespace, SelectorIR
1014
from narwhals._plan.functions import RollingWindow
@@ -24,3 +28,10 @@
2428
"SelectorOperatorT", bound="ops.SelectorOperator", default="ops.SelectorOperator"
2529
)
2630
IRNamespaceT = TypeVar("IRNamespaceT", bound="IRNamespace")
31+
# NOTE: Shorter aliases of `_compliant.typing`
32+
# - Aiming to try and preserve the types as much as possible
33+
# - Recursion between `Expr` and `Frame` is an issue
34+
Expr: TypeAlias = "CompliantExprAny"
35+
ExprT = TypeVar("ExprT", bound="Expr")
36+
Ns: TypeAlias = "Namespace[t.Any, ExprT]"
37+
"""A `CompliantNamespace`, ignoring the `Frame` type."""

tests/plan/to_compliant_test.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
import pytest
6+
7+
import narwhals as nw
8+
import narwhals._plan.demo as nwd
9+
from narwhals.utils import Version
10+
from tests.namespace_test import backends
11+
12+
if TYPE_CHECKING:
13+
from narwhals._namespace import BackendName
14+
from narwhals._plan.dummy import DummyExpr
15+
16+
17+
def _ids_ir(expr: DummyExpr) -> str:
18+
return repr(expr._ir)
19+
20+
21+
@pytest.mark.parametrize(
22+
("expr"),
23+
[
24+
nwd.col("a"),
25+
nwd.col("a", "b"),
26+
nwd.lit(1),
27+
nwd.lit(2.0),
28+
nwd.lit(None, nw.String()),
29+
],
30+
ids=_ids_ir,
31+
)
32+
@backends
33+
def test_to_compliant(backend: BackendName, expr: DummyExpr) -> None:
34+
pytest.importorskip(backend)
35+
namespace = Version.MAIN.namespace.from_backend(backend).compliant
36+
compliant_expr = expr._to_compliant(namespace)
37+
assert isinstance(compliant_expr, namespace._expr)

0 commit comments

Comments
 (0)