Skip to content
Merged
1 change: 1 addition & 0 deletions narwhals/_compliant/any_namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def total_seconds(self) -> CompliantT_co: ...
def total_milliseconds(self) -> CompliantT_co: ...
def total_microseconds(self) -> CompliantT_co: ...
def total_nanoseconds(self) -> CompliantT_co: ...
def truncate(self, every: str) -> CompliantT_co: ...


class ListNamespace(_StoresCompliant[CompliantT_co], Protocol[CompliantT_co]):
Expand Down
14 changes: 7 additions & 7 deletions narwhals/_compliant/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,17 +254,17 @@ def _evaluate_aliases(
return alias(names) if (alias := self._alias_output_names) else names

@property
def str(self) -> Any: ...
def str(self) -> StringNamespace[Self]: ...
@property
def name(self) -> Any: ...
def name(self) -> NameNamespace[Self]: ...
@property
def dt(self) -> Any: ...
def dt(self) -> DateTimeNamespace[Self]: ...
@property
def cat(self) -> Any: ...
def cat(self) -> CatNamespace[Self]: ...
@property
def list(self) -> Any: ...
def list(self) -> ListNamespace[Self]: ...
@property
def struct(self) -> Any: ...
def struct(self) -> StructNamespace[Self]: ...


class DepthTrackingExpr(
Expand Down Expand Up @@ -871,7 +871,7 @@ class LazyExpr(
ewm_mean: not_implemented = not_implemented()
gather_every: not_implemented = not_implemented()
replace_strict: not_implemented = not_implemented()
cat: not_implemented = not_implemented() # pyright: ignore[reportAssignmentType]
cat: not_implemented = not_implemented() # type: ignore[assignment]

@property
def window_function(self) -> WindowFunction[CompliantLazyFrameT, NativeExprT]: ...
Expand Down
4 changes: 2 additions & 2 deletions narwhals/_dask/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -678,8 +678,8 @@ def str(self) -> DaskExprStringNamespace:
def dt(self) -> DaskExprDateTimeNamespace:
return DaskExprDateTimeNamespace(self)

list = not_implemented() # pyright: ignore[reportAssignmentType]
struct = not_implemented() # pyright: ignore[reportAssignmentType]
list = not_implemented() # type: ignore[assignment]
struct = not_implemented() # type: ignore[assignment]
rank = not_implemented() # pyright: ignore[reportAssignmentType]
_alias_native = not_implemented()
window_function = not_implemented() # pyright: ignore[reportAssignmentType]
Expand Down
63 changes: 30 additions & 33 deletions narwhals/_dask/expr_dt.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from typing import TYPE_CHECKING

from narwhals._compliant.any_namespace import DateTimeNamespace
from narwhals._compliant.expr import LazyExprNamespace
from narwhals._constants import MS_PER_SECOND, NS_PER_SECOND, US_PER_SECOND
from narwhals._duration import parse_interval_string
from narwhals._pandas_like.utils import (
Expand All @@ -19,66 +21,65 @@
from narwhals.typing import TimeUnit


class DaskExprDateTimeNamespace:
def __init__(self, expr: DaskExpr) -> None:
self._compliant_expr = expr

class DaskExprDateTimeNamespace(
LazyExprNamespace["DaskExpr"], DateTimeNamespace["DaskExpr"]
):
def date(self) -> DaskExpr:
return self._compliant_expr._with_callable(lambda expr: expr.dt.date, "date")
return self.compliant._with_callable(lambda expr: expr.dt.date, "date")

def year(self) -> DaskExpr:
return self._compliant_expr._with_callable(lambda expr: expr.dt.year, "year")
return self.compliant._with_callable(lambda expr: expr.dt.year, "year")

def month(self) -> DaskExpr:
return self._compliant_expr._with_callable(lambda expr: expr.dt.month, "month")
return self.compliant._with_callable(lambda expr: expr.dt.month, "month")

def day(self) -> DaskExpr:
return self._compliant_expr._with_callable(lambda expr: expr.dt.day, "day")
return self.compliant._with_callable(lambda expr: expr.dt.day, "day")

def hour(self) -> DaskExpr:
return self._compliant_expr._with_callable(lambda expr: expr.dt.hour, "hour")
return self.compliant._with_callable(lambda expr: expr.dt.hour, "hour")

def minute(self) -> DaskExpr:
return self._compliant_expr._with_callable(lambda expr: expr.dt.minute, "minute")
return self.compliant._with_callable(lambda expr: expr.dt.minute, "minute")

def second(self) -> DaskExpr:
return self._compliant_expr._with_callable(lambda expr: expr.dt.second, "second")
return self.compliant._with_callable(lambda expr: expr.dt.second, "second")

def millisecond(self) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr: expr.dt.microsecond // 1000, "millisecond"
)

def microsecond(self) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr: expr.dt.microsecond, "microsecond"
)

def nanosecond(self) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr: expr.dt.microsecond * 1000 + expr.dt.nanosecond, "nanosecond"
)

def ordinal_day(self) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr: expr.dt.dayofyear, "ordinal_day"
)

def weekday(self) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr: expr.dt.weekday + 1, # Dask is 0-6
"weekday",
)

def to_string(self, format: str) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr, format: expr.dt.strftime(format.replace("%.f", ".%f")),
"strftime",
format=format,
)

def replace_time_zone(self, time_zone: str | None) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr, time_zone: expr.dt.tz_localize(None).dt.tz_localize(time_zone)
if time_zone is not None
else expr.dt.tz_localize(None),
Expand All @@ -89,25 +90,23 @@ def replace_time_zone(self, time_zone: str | None) -> DaskExpr:
def convert_time_zone(self, time_zone: str) -> DaskExpr:
def func(s: dx.Series, time_zone: str) -> dx.Series:
dtype = native_to_narwhals_dtype(
s.dtype, self._compliant_expr._version, Implementation.DASK
s.dtype, self.compliant._version, Implementation.DASK
)
if dtype.time_zone is None: # type: ignore[attr-defined]
return s.dt.tz_localize("UTC").dt.tz_convert(time_zone) # pyright: ignore[reportAttributeAccessIssue]
else:
return s.dt.tz_convert(time_zone) # pyright: ignore[reportAttributeAccessIssue]

return self._compliant_expr._with_callable(
func, "tz_convert", time_zone=time_zone
)
return self.compliant._with_callable(func, "tz_convert", time_zone=time_zone)

def timestamp(self, time_unit: TimeUnit) -> DaskExpr:
def func(s: dx.Series, time_unit: TimeUnit) -> dx.Series:
dtype = native_to_narwhals_dtype(
s.dtype, self._compliant_expr._version, Implementation.DASK
s.dtype, self.compliant._version, Implementation.DASK
)
is_pyarrow_dtype = "pyarrow" in str(dtype)
mask_na = s.isna()
dtypes = self._compliant_expr._version.dtypes
dtypes = self.compliant._version.dtypes
if dtype == dtypes.Date:
# Date is only supported in pandas dtypes if pyarrow-backed
s_cast = s.astype("Int32[pyarrow]")
Expand All @@ -125,32 +124,32 @@ def func(s: dx.Series, time_unit: TimeUnit) -> dx.Series:
raise TypeError(msg)
return result.where(~mask_na) # pyright: ignore[reportReturnType]

return self._compliant_expr._with_callable(func, "datetime", time_unit=time_unit)
return self.compliant._with_callable(func, "datetime", time_unit=time_unit)

def total_minutes(self) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr: expr.dt.total_seconds() // 60, "total_minutes"
)

def total_seconds(self) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr: expr.dt.total_seconds() // 1, "total_seconds"
)

def total_milliseconds(self) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr: expr.dt.total_seconds() * MS_PER_SECOND // 1,
"total_milliseconds",
)

def total_microseconds(self) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr: expr.dt.total_seconds() * US_PER_SECOND // 1,
"total_microseconds",
)

def total_nanoseconds(self) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr: expr.dt.total_seconds() * NS_PER_SECOND // 1, "total_nanoseconds"
)

Expand All @@ -160,6 +159,4 @@ def truncate(self, every: str) -> DaskExpr:
msg = f"Truncating to {unit} is not supported yet for dask."
raise NotImplementedError(msg)
freq = f"{multiple}{UNIT_DICT.get(unit, unit)}"
return self._compliant_expr._with_callable(
lambda expr: expr.dt.floor(freq), "truncate"
)
return self.compliant._with_callable(lambda expr: expr.dt.floor(freq), "truncate")
34 changes: 17 additions & 17 deletions narwhals/_dask/expr_str.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@

import dask.dataframe as dd

from narwhals._compliant.any_namespace import StringNamespace
from narwhals._compliant.expr import LazyExprNamespace

if TYPE_CHECKING:
from narwhals._dask.expr import DaskExpr


class DaskExprStringNamespace:
def __init__(self, expr: DaskExpr) -> None:
self._compliant_expr = expr

class DaskExprStringNamespace(LazyExprNamespace["DaskExpr"], StringNamespace["DaskExpr"]):
def len_chars(self) -> DaskExpr:
return self._compliant_expr._with_callable(lambda expr: expr.str.len(), "len")
return self.compliant._with_callable(lambda expr: expr.str.len(), "len")

def replace(self, pattern: str, value: str, *, literal: bool, n: int) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr, pattern, value, literal, n: expr.str.replace(
pattern, value, regex=not literal, n=n
),
Expand All @@ -28,7 +28,7 @@ def replace(self, pattern: str, value: str, *, literal: bool, n: int) -> DaskExp
)

def replace_all(self, pattern: str, value: str, *, literal: bool) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr, pattern, value, literal: expr.str.replace(
pattern, value, n=-1, regex=not literal
),
Expand All @@ -39,24 +39,24 @@ def replace_all(self, pattern: str, value: str, *, literal: bool) -> DaskExpr:
)

def strip_chars(self, characters: str | None) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr, characters: expr.str.strip(characters),
"strip",
characters=characters,
)

def starts_with(self, prefix: str) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr, prefix: expr.str.startswith(prefix), "starts_with", prefix=prefix
)

def ends_with(self, suffix: str) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr, suffix: expr.str.endswith(suffix), "ends_with", suffix=suffix
)

def contains(self, pattern: str, *, literal: bool) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr, pattern, literal: expr.str.contains(
pat=pattern, regex=not literal
),
Expand All @@ -66,7 +66,7 @@ def contains(self, pattern: str, *, literal: bool) -> DaskExpr:
)

def slice(self, offset: int, length: int | None) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr, offset, length: expr.str.slice(
start=offset, stop=offset + length if length else None
),
Expand All @@ -76,28 +76,28 @@ def slice(self, offset: int, length: int | None) -> DaskExpr:
)

def split(self, by: str) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr, by: expr.str.split(pat=by), "split", by=by
)

def to_datetime(self, format: str | None) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr, format: dd.to_datetime(expr, format=format),
"to_datetime",
format=format,
)

def to_uppercase(self) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr: expr.str.upper(), "to_uppercase"
)

def to_lowercase(self) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr: expr.str.lower(), "to_lowercase"
)

def zfill(self, width: int) -> DaskExpr:
return self._compliant_expr._with_callable(
return self.compliant._with_callable(
lambda expr, width: expr.str.zfill(width), "zfill", width=width
)
Loading
Loading