Skip to content

Commit 72c33ce

Browse files
committed
feat: Add str namespace
- Not fully convinced on this namespace abstraction - At the very least, something to handle the `Function` -> `FunctionExpr` would reduce the boilerplate a lot
1 parent cc9f9b4 commit 72c33ce

File tree

2 files changed

+160
-21
lines changed

2 files changed

+160
-21
lines changed

narwhals/_plan/dummy.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
from narwhals._plan.lists import ExprListNamespace
3535
from narwhals._plan.meta import IRMetaNamespace
3636
from narwhals._plan.name import ExprNameNamespace
37+
from narwhals._plan.strings import ExprStringNamespace
3738
from narwhals._plan.struct import ExprStructNamespace
3839
from narwhals._plan.temporal import ExprDateTimeNamespace
3940
from narwhals.typing import (
@@ -494,6 +495,12 @@ def list(self) -> ExprListNamespace:
494495

495496
return ExprListNamespace(_expr=self)
496497

498+
@property
499+
def str(self) -> ExprStringNamespace:
500+
from narwhals._plan.strings import ExprStringNamespace
501+
502+
return ExprStringNamespace(_expr=self)
503+
497504

498505
class DummySelector(DummyExpr):
499506
"""Selectors placeholder.

narwhals/_plan/strings.py

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

3-
from narwhals._plan.common import Function
3+
from typing import TYPE_CHECKING
4+
5+
from narwhals._plan.common import ExprNamespace, Function, IRNamespace
46
from narwhals._plan.options import FunctionFlags, FunctionOptions
57

8+
if TYPE_CHECKING:
9+
from narwhals._plan.dummy import DummyExpr
10+
611

712
class StringFunction(Function):
813
@property
@@ -30,15 +35,20 @@ def __repr__(self) -> str:
3035

3136

3237
class Contains(StringFunction):
33-
__slots__ = ("literal",)
38+
__slots__ = ("literal", "pattern")
3439

40+
pattern: str
3541
literal: bool
3642

3743
def __repr__(self) -> str:
3844
return "str.contains"
3945

4046

4147
class EndsWith(StringFunction):
48+
__slots__ = ("suffix",)
49+
50+
suffix: str
51+
4252
def __repr__(self) -> str:
4353
return "str.ends_with"
4454

@@ -49,9 +59,12 @@ def __repr__(self) -> str:
4959

5060

5161
class Replace(StringFunction):
52-
__slots__ = ("literal",)
62+
__slots__ = ("literal", "n", "pattern", "value")
5363

64+
pattern: str
65+
value: str
5466
literal: bool
67+
n: int
5568

5669
def __repr__(self) -> str:
5770
return "str.replace"
@@ -63,8 +76,10 @@ class ReplaceAll(StringFunction):
6376
https://github.com/pola-rs/polars/blob/dafd0a2d0e32b52bcfa4273bffdd6071a0d5977a/crates/polars-plan/src/dsl/function_expr/strings.rs#L65-L70
6477
"""
6578

66-
__slots__ = ("literal",)
79+
__slots__ = ("literal", "pattern", "value")
6780

81+
pattern: str
82+
value: str
6883
literal: bool
6984

7085
def __repr__(self) -> str:
@@ -88,35 +103,29 @@ def __repr__(self) -> str:
88103
return "str.slice"
89104

90105

91-
class Head(StringFunction):
92-
__slots__ = ("n",)
93-
94-
n: int
95-
96-
def __repr__(self) -> str:
97-
return "str.head"
98-
99-
100-
class Tail(StringFunction):
101-
__slots__ = ("n",)
102-
103-
n: int
104-
105-
def __repr__(self) -> str:
106-
return "str.tail"
106+
class Split(StringFunction):
107+
__slots__ = ("by",)
107108

109+
by: str
108110

109-
class Split(StringFunction):
110111
def __repr__(self) -> str:
111112
return "str.split"
112113

113114

114115
class StartsWith(StringFunction):
116+
__slots__ = ("prefix",)
117+
118+
prefix: str
119+
115120
def __repr__(self) -> str:
116121
return "str.startswith"
117122

118123

119124
class StripChars(StringFunction):
125+
__slots__ = ("characters",)
126+
127+
characters: str | None
128+
120129
def __repr__(self) -> str:
121130
return "str.strip_chars"
122131

@@ -133,6 +142,9 @@ class ToDatetime(StringFunction):
133142

134143
format: str | None
135144

145+
def __repr__(self) -> str:
146+
return "str.to_datetime"
147+
136148

137149
class ToLowercase(StringFunction):
138150
def __repr__(self) -> str:
@@ -142,3 +154,123 @@ def __repr__(self) -> str:
142154
class ToUppercase(StringFunction):
143155
def __repr__(self) -> str:
144156
return "str.to_uppercase"
157+
158+
159+
class IRStringNamespace(IRNamespace):
160+
def len_chars(self) -> LenChars:
161+
return LenChars()
162+
163+
def replace(
164+
self, pattern: str, value: str, *, literal: bool = False, n: int = 1
165+
) -> Replace:
166+
return Replace(pattern=pattern, value=value, literal=literal, n=n)
167+
168+
def replace_all(
169+
self, pattern: str, value: str, *, literal: bool = False
170+
) -> ReplaceAll:
171+
return ReplaceAll(pattern=pattern, value=value, literal=literal)
172+
173+
def strip_chars(self, characters: str | None = None) -> StripChars:
174+
return StripChars(characters=characters)
175+
176+
def starts_with(self, prefix: str) -> StartsWith:
177+
return StartsWith(prefix=prefix)
178+
179+
def ends_with(self, suffix: str) -> EndsWith:
180+
return EndsWith(suffix=suffix)
181+
182+
def contains(self, pattern: str, *, literal: bool = False) -> Contains:
183+
return Contains(pattern=pattern, literal=literal)
184+
185+
def slice(self, offset: int, length: int | None = None) -> Slice:
186+
return Slice(offset=offset, length=length)
187+
188+
def head(self, n: int = 5) -> Slice:
189+
return self.slice(0, n)
190+
191+
def tail(self, n: int = 5) -> Slice:
192+
return self.slice(-n)
193+
194+
def split(self, by: str) -> Split:
195+
return Split(by=by)
196+
197+
def to_datetime(self, format: str | None = None) -> ToDatetime:
198+
return ToDatetime(format=format)
199+
200+
def to_lowercase(self) -> ToUppercase:
201+
return ToUppercase()
202+
203+
def to_uppercase(self) -> ToLowercase:
204+
return ToLowercase()
205+
206+
207+
class ExprStringNamespace(ExprNamespace[IRStringNamespace]):
208+
@property
209+
def _ir_namespace(self) -> type[IRStringNamespace]:
210+
return IRStringNamespace
211+
212+
def len_chars(self) -> DummyExpr:
213+
return self._to_narwhals(self._ir.len_chars().to_function_expr(self._expr._ir))
214+
215+
def replace(
216+
self, pattern: str, value: str, *, literal: bool = False, n: int = 1
217+
) -> DummyExpr:
218+
return self._to_narwhals(
219+
self._ir.replace(pattern, value, literal=literal, n=n).to_function_expr(
220+
self._expr._ir
221+
)
222+
)
223+
224+
def replace_all(
225+
self, pattern: str, value: str, *, literal: bool = False
226+
) -> DummyExpr:
227+
return self._to_narwhals(
228+
self._ir.replace_all(pattern, value, literal=literal).to_function_expr(
229+
self._expr._ir
230+
)
231+
)
232+
233+
def strip_chars(self, characters: str | None = None) -> DummyExpr:
234+
return self._to_narwhals(
235+
self._ir.strip_chars(characters).to_function_expr(self._expr._ir)
236+
)
237+
238+
def starts_with(self, prefix: str) -> DummyExpr:
239+
return self._to_narwhals(
240+
self._ir.starts_with(prefix).to_function_expr(self._expr._ir)
241+
)
242+
243+
def ends_with(self, suffix: str) -> DummyExpr:
244+
return self._to_narwhals(
245+
self._ir.ends_with(suffix).to_function_expr(self._expr._ir)
246+
)
247+
248+
def contains(self, pattern: str, *, literal: bool = False) -> DummyExpr:
249+
return self._to_narwhals(
250+
self._ir.contains(pattern, literal=literal).to_function_expr(self._expr._ir)
251+
)
252+
253+
def slice(self, offset: int, length: int | None = None) -> DummyExpr:
254+
return self._to_narwhals(
255+
self._ir.slice(offset, length).to_function_expr(self._expr._ir)
256+
)
257+
258+
def head(self, n: int = 5) -> DummyExpr:
259+
return self._to_narwhals(self._ir.head(n).to_function_expr(self._expr._ir))
260+
261+
def tail(self, n: int = 5) -> DummyExpr:
262+
return self._to_narwhals(self._ir.tail(n).to_function_expr(self._expr._ir))
263+
264+
def split(self, by: str) -> DummyExpr:
265+
return self._to_narwhals(self._ir.split(by).to_function_expr(self._expr._ir))
266+
267+
def to_datetime(self, format: str | None = None) -> DummyExpr:
268+
return self._to_narwhals(
269+
self._ir.to_datetime(format).to_function_expr(self._expr._ir)
270+
)
271+
272+
def to_lowercase(self) -> DummyExpr:
273+
return self._to_narwhals(self._ir.to_lowercase().to_function_expr(self._expr._ir))
274+
275+
def to_uppercase(self) -> DummyExpr:
276+
return self._to_narwhals(self._ir.to_uppercase().to_function_expr(self._expr._ir))

0 commit comments

Comments
 (0)