Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 21 additions & 61 deletions narwhals/_polars/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@

import polars as pl

from narwhals._duration import Interval
from narwhals._polars.utils import (
PolarsAnyNamespace,
PolarsCatNamespace,
PolarsDateTimeNamespace,
PolarsListNamespace,
PolarsStringNamespace,
PolarsStructNamespace,
extract_args_kwargs,
extract_native,
narwhals_to_native_dtype,
Expand Down Expand Up @@ -343,44 +347,14 @@ def native(self) -> pl.Expr:
return self._expr.native


class PolarsExprDateTimeNamespace(PolarsExprNamespace):
_accessor = "dt"

def truncate(self, every: str) -> PolarsExpr:
Interval.parse(every) # Ensure consistent error message is raised.
return self.compliant._with_native(self.native.dt.truncate(every))

def offset_by(self, by: str) -> PolarsExpr:
# Ensure consistent error message is raised.
Interval.parse_no_constraints(by)
return self.compliant._with_native(self.native.dt.offset_by(by))

to_string: Method[PolarsExpr]
replace_time_zone: Method[PolarsExpr]
convert_time_zone: Method[PolarsExpr]
timestamp: Method[PolarsExpr]
date: Method[PolarsExpr]
year: Method[PolarsExpr]
month: Method[PolarsExpr]
day: Method[PolarsExpr]
hour: Method[PolarsExpr]
minute: Method[PolarsExpr]
second: Method[PolarsExpr]
millisecond: Method[PolarsExpr]
microsecond: Method[PolarsExpr]
nanosecond: Method[PolarsExpr]
ordinal_day: Method[PolarsExpr]
weekday: Method[PolarsExpr]
total_minutes: Method[PolarsExpr]
total_seconds: Method[PolarsExpr]
total_milliseconds: Method[PolarsExpr]
total_microseconds: Method[PolarsExpr]
total_nanoseconds: Method[PolarsExpr]


class PolarsExprStringNamespace(PolarsExprNamespace):
_accessor = "str"
class PolarsExprDateTimeNamespace(
PolarsExprNamespace, PolarsDateTimeNamespace[PolarsExpr, pl.Expr]
): ...


class PolarsExprStringNamespace(
PolarsExprNamespace, PolarsStringNamespace[PolarsExpr, pl.Expr]
):
def zfill(self, width: int) -> PolarsExpr:
backend_version = self.compliant._backend_version
native_result = self.native.str.zfill(width)
Expand Down Expand Up @@ -409,24 +383,10 @@ def zfill(self, width: int) -> PolarsExpr:

return self.compliant._with_native(native_result)

len_chars: Method[PolarsExpr]
replace: Method[PolarsExpr]
replace_all: Method[PolarsExpr]
strip_chars: Method[PolarsExpr]
starts_with: Method[PolarsExpr]
ends_with: Method[PolarsExpr]
contains: Method[PolarsExpr]
slice: Method[PolarsExpr]
split: Method[PolarsExpr]
to_date: Method[PolarsExpr]
to_datetime: Method[PolarsExpr]
to_lowercase: Method[PolarsExpr]
to_uppercase: Method[PolarsExpr]


class PolarsExprCatNamespace(PolarsExprNamespace):
_accessor = "cat"
get_categories: Method[PolarsExpr]
class PolarsExprCatNamespace(
PolarsExprNamespace, PolarsCatNamespace[PolarsExpr, pl.Expr]
): ...


class PolarsExprNameNamespace(PolarsExprNamespace):
Expand All @@ -439,9 +399,9 @@ class PolarsExprNameNamespace(PolarsExprNamespace):
to_uppercase: Method[PolarsExpr]


class PolarsExprListNamespace(PolarsExprNamespace):
_accessor = "list"

class PolarsExprListNamespace(
PolarsExprNamespace, PolarsListNamespace[PolarsExpr, pl.Expr]
):
def len(self) -> PolarsExpr:
native_expr = self.native
native_result = native_expr.list.len()
Expand All @@ -456,6 +416,6 @@ def len(self) -> PolarsExpr:
return self.compliant._with_native(native_result)


class PolarsExprStructNamespace(PolarsExprNamespace):
_accessor = "struct"
field: Method[PolarsExpr]
class PolarsExprStructNamespace(
PolarsExprNamespace, PolarsStructNamespace[PolarsExpr, pl.Expr]
): ...
74 changes: 21 additions & 53 deletions narwhals/_polars/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
from narwhals._polars.utils import (
BACKEND_VERSION,
PolarsAnyNamespace,
PolarsCatNamespace,
PolarsDateTimeNamespace,
PolarsListNamespace,
PolarsStringNamespace,
PolarsStructNamespace,
catch_polars_exception,
extract_args_kwargs,
extract_native,
Expand Down Expand Up @@ -695,66 +700,29 @@ def native(self) -> pl.Series:
return self._series.native


class PolarsSeriesDateTimeNamespace(PolarsSeriesNamespace):
_accessor = "dt"

truncate: Method[PolarsSeries]
offset_by: Method[PolarsSeries]
to_string: Method[PolarsSeries]
replace_time_zone: Method[PolarsSeries]
convert_time_zone: Method[PolarsSeries]
timestamp: Method[PolarsSeries]
date: Method[PolarsSeries]
year: Method[PolarsSeries]
month: Method[PolarsSeries]
day: Method[PolarsSeries]
hour: Method[PolarsSeries]
minute: Method[PolarsSeries]
second: Method[PolarsSeries]
millisecond: Method[PolarsSeries]
microsecond: Method[PolarsSeries]
nanosecond: Method[PolarsSeries]
ordinal_day: Method[PolarsSeries]
weekday: Method[PolarsSeries]
total_minutes: Method[PolarsSeries]
total_seconds: Method[PolarsSeries]
total_milliseconds: Method[PolarsSeries]
total_microseconds: Method[PolarsSeries]
total_nanoseconds: Method[PolarsSeries]


class PolarsSeriesStringNamespace(PolarsSeriesNamespace):
_accessor = "str"
class PolarsSeriesDateTimeNamespace(
PolarsSeriesNamespace, PolarsDateTimeNamespace[PolarsSeries, pl.Series]
): ...


class PolarsSeriesStringNamespace(
PolarsSeriesNamespace, PolarsStringNamespace[PolarsSeries, pl.Series]
):
def zfill(self, width: int) -> PolarsSeries:
series = self.compliant
name = series.name
ns = series.__narwhals_namespace__()
return series.to_frame().select(ns.col(name).str.zfill(width)).get_column(name)

len_chars: Method[PolarsSeries]
replace: Method[PolarsSeries]
replace_all: Method[PolarsSeries]
strip_chars: Method[PolarsSeries]
starts_with: Method[PolarsSeries]
ends_with: Method[PolarsSeries]
contains: Method[PolarsSeries]
slice: Method[PolarsSeries]
split: Method[PolarsSeries]
to_date: Method[PolarsSeries]
to_datetime: Method[PolarsSeries]
to_lowercase: Method[PolarsSeries]
to_uppercase: Method[PolarsSeries]


class PolarsSeriesCatNamespace(PolarsSeriesNamespace):
_accessor = "cat"
get_categories: Method[PolarsSeries]

class PolarsSeriesCatNamespace(
PolarsSeriesNamespace, PolarsCatNamespace[PolarsSeries, pl.Series]
): ...

class PolarsSeriesListNamespace(PolarsSeriesNamespace):
_accessor = "list"

class PolarsSeriesListNamespace(
PolarsSeriesNamespace, PolarsListNamespace[PolarsSeries, pl.Series]
):
def len(self) -> PolarsSeries:
native_result = self.native.list.len()
if self.compliant._backend_version < (1, 16): # pragma: no cover
Expand All @@ -769,6 +737,6 @@ def len(self) -> PolarsSeries:
return self.compliant._with_native(native_result)


class PolarsSeriesStructNamespace(PolarsSeriesNamespace):
_accessor = "struct"
field: Method[PolarsSeries]
class PolarsSeriesStructNamespace(
PolarsSeriesNamespace, PolarsStructNamespace[PolarsSeries, pl.Series]
): ...
81 changes: 81 additions & 0 deletions narwhals/_polars/utils.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from __future__ import annotations

import abc
from functools import lru_cache
from typing import TYPE_CHECKING, Any, ClassVar, Protocol, TypeVar, overload

import polars as pl

from narwhals._duration import Interval
from narwhals._utils import (
Implementation,
Version,
Expand All @@ -28,6 +30,7 @@

from typing_extensions import TypeIs

from narwhals._polars.dataframe import Method
from narwhals._polars.expr import PolarsExpr
from narwhals._polars.series import PolarsSeries
from narwhals._polars.typing import NativeAccessor
Expand All @@ -41,6 +44,7 @@

NativeT_co = TypeVar("NativeT_co", "pl.Series", "pl.Expr", covariant=True)
CompliantT_co = TypeVar("CompliantT_co", "PolarsSeries", "PolarsExpr", covariant=True)
CompliantT = TypeVar("CompliantT", "PolarsSeries", "PolarsExpr")

BACKEND_VERSION = Implementation.POLARS._backend_version()
"""Static backend version for `polars`."""
Expand Down Expand Up @@ -264,3 +268,80 @@ def func(*args: Any, **kwargs: Any) -> CompliantT_co:
return self.compliant._with_native(method(*pos, **kwds))

return func


class PolarsDateTimeNamespace(PolarsAnyNamespace[CompliantT, NativeT_co]):
_accessor: ClassVar[NativeAccessor] = "dt"

def truncate(self, every: str) -> CompliantT:
# Ensure consistent error message is raised.
Interval.parse(every)
return self.__getattr__("truncate")(every)

def offset_by(self, by: str) -> CompliantT:
# Ensure consistent error message is raised.
Interval.parse_no_constraints(by)
return self.__getattr__("offset_by")(by)

to_string: Method[CompliantT]
replace_time_zone: Method[CompliantT]
convert_time_zone: Method[CompliantT]
timestamp: Method[CompliantT]
date: Method[CompliantT]
year: Method[CompliantT]
month: Method[CompliantT]
day: Method[CompliantT]
hour: Method[CompliantT]
minute: Method[CompliantT]
second: Method[CompliantT]
millisecond: Method[CompliantT]
microsecond: Method[CompliantT]
nanosecond: Method[CompliantT]
ordinal_day: Method[CompliantT]
weekday: Method[CompliantT]
total_minutes: Method[CompliantT]
total_seconds: Method[CompliantT]
total_milliseconds: Method[CompliantT]
total_microseconds: Method[CompliantT]
total_nanoseconds: Method[CompliantT]


class PolarsStringNamespace(PolarsAnyNamespace[CompliantT, NativeT_co]):
_accessor: ClassVar[NativeAccessor] = "str"

# NOTE: Use `abstractmethod` if we have defs to implement, but also `Method` usage
@abc.abstractmethod
def zfill(self, width: int) -> CompliantT: ...

len_chars: Method[CompliantT]
replace: Method[CompliantT]
replace_all: Method[CompliantT]
strip_chars: Method[CompliantT]
starts_with: Method[CompliantT]
ends_with: Method[CompliantT]
contains: Method[CompliantT]
slice: Method[CompliantT]
split: Method[CompliantT]
to_date: Method[CompliantT]
to_datetime: Method[CompliantT]
to_lowercase: Method[CompliantT]
to_uppercase: Method[CompliantT]


class PolarsCatNamespace(PolarsAnyNamespace[CompliantT, NativeT_co]):
_accessor: ClassVar[NativeAccessor] = "cat"
get_categories: Method[CompliantT]


# NOTE: Use `Protocol` if we **only** have defs to implement
class PolarsListNamespace(
PolarsAnyNamespace[CompliantT_co, NativeT_co], Protocol[CompliantT_co, NativeT_co]
):
_accessor: ClassVar[NativeAccessor] = "list"

def len(self) -> CompliantT_co: ...


class PolarsStructNamespace(PolarsAnyNamespace[CompliantT, NativeT_co]):
_accessor: ClassVar[NativeAccessor] = "struct"
field: Method[CompliantT]
Loading