Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion narwhals/_compliant/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ def __narwhals_namespace__(
]: ...

def to_narwhals(self) -> DataFrame[NativeDataFrameT]:
return self._version.dataframe(self, level="full")
return self._version.dataframe(self)

def aggregate(self, *exprs: EagerExprT) -> Self: # pyright: ignore[reportIncompatibleMethodOverride]
# NOTE: Ignore intermittent [False Negative] (1)
Expand Down
2 changes: 1 addition & 1 deletion narwhals/_compliant/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def __native_namespace__(self) -> ModuleType: ...
@classmethod
def from_native(cls, data: NativeSeriesT, /, *, context: _LimitedContext) -> Self: ...
def to_narwhals(self) -> Series[NativeSeriesT]:
return self._version.series(self, level="full")
return self._version.series(self)

def _with_native(self, series: Any) -> Self: ...
def _with_version(self, version: Version) -> Self: ...
Expand Down
2 changes: 1 addition & 1 deletion narwhals/_dask/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def from_native(cls, data: dd.DataFrame, /, *, context: _LimitedContext) -> Self
return cls(data, version=context._version)

def to_narwhals(self) -> LazyFrame[dd.DataFrame]:
return self._version.lazyframe(self, level="lazy")
return self._version.lazyframe(self)

def __native_namespace__(self) -> ModuleType:
if self._implementation is Implementation.DASK:
Expand Down
2 changes: 1 addition & 1 deletion narwhals/_duckdb/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def to_narwhals(
from narwhals.stable.v1 import DataFrame as DataFrameV1

return DataFrameV1(self, level="interchange") # type: ignore[no-any-return]
return self._version.lazyframe(self, level="lazy")
return self._version.lazyframe(self)

def __narwhals_dataframe__(self) -> Self: # pragma: no cover
# Keep around for backcompat.
Expand Down
2 changes: 1 addition & 1 deletion narwhals/_ibis/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def to_narwhals(self) -> LazyFrame[ir.Table] | DataFrameV1[ir.Table]:
from narwhals.stable.v1 import DataFrame

return DataFrame(self, level="interchange")
return self._version.lazyframe(self, level="lazy")
return self._version.lazyframe(self)

def __narwhals_dataframe__(self) -> Self: # pragma: no cover
# Keep around for backcompat.
Expand Down
7 changes: 5 additions & 2 deletions narwhals/_interchange/dataframe.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

import enum
from typing import TYPE_CHECKING, Any, NoReturn
from typing import TYPE_CHECKING, Any, NoReturn, Protocol

from narwhals._utils import Version, parse_version

Expand All @@ -12,7 +12,10 @@

from narwhals._interchange.series import InterchangeSeries
from narwhals.dtypes import DType
from narwhals.stable.v1.typing import DataFrameLike


class DataFrameLike(Protocol):
def __dataframe__(self, *args: Any, **kwargs: Any) -> Any: ...


class DtypeKind(enum.IntEnum):
Expand Down
4 changes: 2 additions & 2 deletions narwhals/_polars/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ def from_numpy(
return cls.from_native(pl.from_numpy(data, pl_schema), context=context)

def to_narwhals(self) -> DataFrame[pl.DataFrame]:
return self._version.dataframe(self, level="full")
return self._version.dataframe(self)

def __repr__(self) -> str: # pragma: no cover
return "PolarsDataFrame"
Expand Down Expand Up @@ -681,7 +681,7 @@ def _is_native(obj: pl.LazyFrame | Any) -> TypeIs[pl.LazyFrame]:
return isinstance(obj, pl.LazyFrame)

def to_narwhals(self) -> LazyFrame[pl.LazyFrame]:
return self._version.lazyframe(self, level="lazy")
return self._version.lazyframe(self)

def __repr__(self) -> str: # pragma: no cover
return "PolarsLazyFrame"
Expand Down
2 changes: 1 addition & 1 deletion narwhals/_polars/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ def from_numpy(cls, data: Into1DArray, /, *, context: _LimitedContext) -> Self:
return cls.from_native(native, context=context)

def to_narwhals(self) -> Series[pl.Series]:
return self._version.series(self, level="full")
return self._version.series(self)

def _with_native(self, series: pl.Series) -> Self:
return self.__class__(series, version=self._version)
Expand Down
2 changes: 1 addition & 1 deletion narwhals/_spark_like/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def from_native(cls, data: SQLFrameDataFrame, /, *, context: _LimitedContext) ->
return cls(data, version=context._version, implementation=context._implementation)

def to_narwhals(self) -> LazyFrame[SQLFrameDataFrame]:
return self._version.lazyframe(self, level="lazy")
return self._version.lazyframe(self)

def __native_namespace__(self) -> ModuleType: # pragma: no cover
return self._implementation.to_native_namespace()
Expand Down
2 changes: 1 addition & 1 deletion narwhals/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1922,7 +1922,7 @@ def convert_str_slice_to_int_slice(


def inherit_doc(
tp_parent: Callable[P, R1], /
tp_parent: Callable[..., R1], /
Copy link
Copy Markdown
Member Author

@FBruzzesi FBruzzesi Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dangotbanned you might have a better way of doing this but here what happened - by changing the main namespace __init__ signatures, now the V1 is overriding them and the arguments are different, to so the child ones are not bind to the parent ones - hence removing the P from the parent here.

This meant that return DataFrame(self, level="interchange") was resulting in a typing issue

) -> Callable[[_Constructor[_T, P, R2]], _Constructor[_T, P, R2]]:
"""Steal the class-level docstring from parent and attach to child `__init__`.

Expand Down
40 changes: 22 additions & 18 deletions narwhals/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
SingleColSelector,
SingleIndexSelector,
SizeUnit,
SupportLevel,
UniqueKeepStrategy,
_2DArray,
)
Expand All @@ -106,7 +107,10 @@

class BaseFrame(Generic[_FrameT]):
_compliant_frame: Any
_level: Literal["full", "lazy", "interchange"]
# `_level` is stored on subclasses:
# * For stable.v1 "interchange" is still supported.
# * For main and stable.v2 only "full" and "lazy" are supported.
_level: SupportLevel

implementation: _Implementation = _Implementation()
"""Return [`narwhals.Implementation`][] of native frame.
Expand Down Expand Up @@ -141,7 +145,7 @@ def __narwhals_namespace__(self) -> Any:

def _with_compliant(self, df: Any) -> Self:
# construct, preserving properties
return self.__class__(df, level=self._level) # type: ignore[call-arg]
return self.__class__(df) # type: ignore[call-arg]

def _flatten_and_extract(
self, *exprs: IntoExpr | Iterable[IntoExpr], **named_exprs: IntoExpr
Expand Down Expand Up @@ -473,6 +477,7 @@ class DataFrame(BaseFrame[DataFrameT]):
"""

_version: ClassVar[Version] = Version.MAIN
_level: SupportLevel = "full"

@property
def _compliant(self) -> CompliantDataFrame[Any, Any, DataFrameT, Self]:
Expand All @@ -490,8 +495,7 @@ def _validate_metadata(self, metadata: ExprMetadata) -> None:
# all is valid in eager case.
pass

def __init__(self, df: Any, *, level: Literal["full", "lazy", "interchange"]) -> None:
self._level: Literal["full", "lazy", "interchange"] = level
def __init__(self, df: Any) -> None:
self._compliant_frame: CompliantDataFrame[Any, Any, DataFrameT, Self]
if is_compliant_dataframe(df):
self._compliant_frame = df.__narwhals_dataframe__()
Expand Down Expand Up @@ -544,7 +548,7 @@ def from_arrow(
if is_eager_allowed(implementation):
ns = cls._version.namespace.from_backend(implementation).compliant
compliant = ns._dataframe.from_arrow(native_frame, context=ns)
return cls(compliant, level="full")
return cls(compliant)
msg = (
f"{implementation} support in Narwhals is lazy-only, but `DataFrame.from_arrow` is an eager-only function.\n\n"
"Hint: you may want to use an eager backend and then call `.lazy`, e.g.:\n\n"
Expand Down Expand Up @@ -604,7 +608,7 @@ def from_dict(
if is_eager_allowed(implementation):
ns = cls._version.namespace.from_backend(implementation).compliant
compliant = ns._dataframe.from_dict(data, schema=schema, context=ns)
return cls(compliant, level="full")
return cls(compliant)
# NOTE: (#2786) needs resolving for extensions
msg = (
f"{implementation} support in Narwhals is lazy-only, but `DataFrame.from_dict` is an eager-only function.\n\n"
Expand Down Expand Up @@ -676,7 +680,7 @@ def from_dicts(
if is_eager_allowed(implementation):
ns = cls._version.namespace.from_backend(implementation).compliant
compliant = ns._dataframe.from_dicts(data, schema=schema, context=ns)
return cls(compliant, level="full")
return cls(compliant)
# NOTE: (#2786) needs resolving for extensions
msg = (
f"{implementation} support in Narwhals is lazy-only, but `DataFrame.from_dicts` is an eager-only function.\n\n"
Expand Down Expand Up @@ -748,7 +752,7 @@ def from_numpy(
implementation = Implementation.from_backend(backend)
if is_eager_allowed(implementation):
ns = cls._version.namespace.from_backend(implementation).compliant
return cls(ns.from_numpy(data, schema), level="full")
return cls(ns.from_numpy(data, schema))
msg = (
f"{implementation} support in Narwhals is lazy-only, but `DataFrame.from_numpy` is an eager-only function.\n\n"
"Hint: you may want to use an eager backend and then call `.lazy`, e.g.:\n\n"
Expand Down Expand Up @@ -864,10 +868,10 @@ def lazy(
"""
lazy = self._compliant_frame.lazy
if backend is None:
return self._lazyframe(lazy(None, session=session), level="lazy")
return self._lazyframe(lazy(None, session=session))
lazy_backend = Implementation.from_backend(backend)
if is_lazy_allowed(lazy_backend):
return self._lazyframe(lazy(lazy_backend, session=session), level="lazy")
return self._lazyframe(lazy(lazy_backend, session=session))
msg = f"Not-supported backend.\n\nExpected one of {get_args(_LazyAllowedImpl)} or `None`, got {lazy_backend}"
raise ValueError(msg)

Expand Down Expand Up @@ -1025,7 +1029,7 @@ def get_column(self, name: str) -> Series[Any]:
1 2
Name: a, dtype: int64
"""
return self._series(self._compliant_frame.get_column(name), level=self._level)
return self._series(self._compliant_frame.get_column(name))

def estimated_size(self, unit: SizeUnit = "b") -> int | float:
"""Return an estimation of the total (heap) allocated size of the `DataFrame`.
Expand Down Expand Up @@ -1211,7 +1215,7 @@ def to_dict(
"""
if as_series:
return {
key: self._series(value, level=self._level)
key: self._series(value)
for key, value in self._compliant_frame.to_dict(
as_series=as_series
).items()
Expand Down Expand Up @@ -1419,7 +1423,7 @@ def iter_columns(self) -> Iterator[Series[Any]]:
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
"""
for series in self._compliant_frame.iter_columns():
yield self._series(series, level=self._level)
yield self._series(series)

@overload
def iter_rows(
Expand Down Expand Up @@ -2055,7 +2059,7 @@ def is_unique(self) -> Series[Any]:
| dtype: bool |
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
"""
return self._series(self._compliant_frame.is_unique(), level=self._level)
return self._series(self._compliant_frame.is_unique())

def null_count(self) -> Self:
r"""Create a new DataFrame that shows the null counts per column.
Expand Down Expand Up @@ -2362,6 +2366,7 @@ class LazyFrame(BaseFrame[LazyFrameT]):
"""

_version: ClassVar[Version] = Version.MAIN
_level: SupportLevel = "lazy"

@property
def _compliant(self) -> CompliantLazyFrame[Any, LazyFrameT, Self]:
Expand Down Expand Up @@ -2395,8 +2400,7 @@ def _validate_metadata(self, metadata: ExprMetadata) -> None:
)
raise InvalidOperationError(msg)

def __init__(self, df: Any, *, level: Literal["full", "lazy", "interchange"]) -> None:
self._level = level
def __init__(self, df: Any) -> None:
self._compliant_frame: CompliantLazyFrame[Any, LazyFrameT, Self]
if is_compliant_lazyframe(df):
self._compliant_frame = df.__narwhals_lazyframe__()
Expand Down Expand Up @@ -2473,10 +2477,10 @@ def collect(
"""
collect = self._compliant_frame.collect
if backend is None:
return self._dataframe(collect(None, **kwargs), level="full")
return self._dataframe(collect(None, **kwargs))
eager_backend = Implementation.from_backend(backend)
if can_lazyframe_collect(eager_backend):
return self._dataframe(collect(eager_backend, **kwargs), level="full")
return self._dataframe(collect(eager_backend, **kwargs))
msg = f"Unsupported `backend` value.\nExpected one of {get_args(_LazyFrameCollectImpl)} or None, got: {eager_backend}."
raise ValueError(msg)

Expand Down
26 changes: 12 additions & 14 deletions narwhals/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
RankMethod,
RollingInterpolationMethod,
SingleIndexSelector,
SupportLevel,
TemporalLiteral,
_1DArray,
)
Expand Down Expand Up @@ -94,6 +95,8 @@ class Series(Generic[IntoSeriesT]):
"""

_version: ClassVar[Version] = Version.MAIN
# See note on `BaseFrame._level`. Subclasses override at class or instance level.
_level: SupportLevel = "full"

@property
def _compliant(self) -> CompliantSeries[IntoSeriesT]:
Expand All @@ -110,10 +113,7 @@ def _to_expr(self) -> Expr:
ExprNode(ExprKind.SERIES, "_expr._from_series", series=self._compliant)
)

def __init__(
self, series: Any, *, level: Literal["full", "lazy", "interchange"]
) -> None:
self._level: Literal["full", "lazy", "interchange"] = level
def __init__(self, series: Any) -> None:
if is_compliant_series(series):
self._compliant_series: CompliantSeries[IntoSeriesT] = (
series.__narwhals_series__()
Expand Down Expand Up @@ -178,8 +178,8 @@ def from_numpy(
ns = cls._version.namespace.from_backend(implementation).compliant
compliant = ns.from_numpy(values).alias(name)
if dtype:
return cls(compliant.cast(dtype), level="full")
return cls(compliant, level="full")
return cls(compliant.cast(dtype))
return cls(compliant)
msg = ( # pragma: no cover
f"{implementation} support in Narwhals is lazy-only, but `Series.from_numpy` is an eager-only function.\n\n"
"Hint: you may want to use an eager backend and then call `.lazy`, e.g.:\n\n"
Expand Down Expand Up @@ -241,7 +241,7 @@ def from_iterable(
compliant = ns._series.from_iterable(
values, context=ns, name=name, dtype=dtype
)
return cls(compliant, level="full")
return cls(compliant)
msg = (
f"{implementation} support in Narwhals is lazy-only, but `Series.from_iterable` is an eager-only function.\n\n"
"Hint: you may want to use an eager backend and then call `.lazy`, e.g.:\n\n"
Expand Down Expand Up @@ -482,7 +482,7 @@ def _extract_native(self, arg: Any) -> Any:
return arg

def _with_compliant(self, series: Any) -> Self:
return self.__class__(series, level=self._level)
return self.__class__(series)

def pipe(self, function: Callable[[Any], Self], *args: Any, **kwargs: Any) -> Self:
"""Pipe function call.
Expand Down Expand Up @@ -665,7 +665,7 @@ def to_frame(self) -> DataFrame[Any]:
β”‚ 2 β”‚
β””β”€β”€β”€β”€β”€β”˜
"""
return self._dataframe(self._compliant_series.to_frame(), level=self._level)
return self._dataframe(self._compliant_series.to_frame())

def to_list(self) -> list[Any]:
"""Convert to list.
Expand Down Expand Up @@ -1962,8 +1962,7 @@ def value_counts(
return self._dataframe(
self._compliant_series.value_counts(
sort=sort, parallel=parallel, name=name, normalize=normalize
),
level=self._level,
)
)

def quantile(
Expand Down Expand Up @@ -2201,8 +2200,7 @@ def to_dummies(
2 0 1
"""
return self._dataframe(
self._compliant_series.to_dummies(separator=separator, drop_first=drop_first),
level=self._level,
self._compliant_series.to_dummies(separator=separator, drop_first=drop_first)
)

def gather_every(self, n: int, offset: int = 0) -> Self:
Expand Down Expand Up @@ -2732,7 +2730,7 @@ def hist(
bin_count=bin_count, include_breakpoint=include_breakpoint
)

return self._dataframe(result, level=self._level)
return self._dataframe(result)

def log(self, base: float = math.e) -> Self:
r"""Compute the logarithm to a given base.
Expand Down
10 changes: 4 additions & 6 deletions narwhals/sql.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Literal
from typing import TYPE_CHECKING

from narwhals._duckdb.utils import DeferredTimeZone, narwhals_to_native_dtype
from narwhals.dataframe import LazyFrame
Expand Down Expand Up @@ -29,10 +29,8 @@
class SQLTable(LazyFrame[duckdb.DuckDBPyRelation]):
"""A LazyFrame with an additional `to_sql` method."""

def __init__(
self, df: CompliantLazyFrameAny, level: Literal["full", "interchange", "lazy"]
) -> None:
super().__init__(df, level=level)
def __init__(self, df: CompliantLazyFrameAny) -> None:
super().__init__(df)

def to_sql(self, *, pretty: bool = False) -> str:
"""Convert to SQL query.
Expand Down Expand Up @@ -99,7 +97,7 @@ def table(name: str, schema: IntoSchema) -> SQLTable:
({dtypes});
""")
lf = from_native(CONN.table(name))
return SQLTable(lf._compliant_frame, level=lf._level)
return SQLTable(lf._compliant_frame)


__all__ = ["table"]
Loading
Loading