Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
2c36eea
feat(typing): Make `Implementation` less opaque
dangotbanned Aug 20, 2025
2350dfc
ci(typing): fix pyright coverage
dangotbanned Aug 20, 2025
fe80d52
ci: Handle descriptors in API reference
dangotbanned Aug 20, 2025
123dc2e
Merge branch 'main' into implementation-typing
dangotbanned Aug 20, 2025
54bfbe4
Merge remote-tracking branch 'upstream/main' into implementation-typing
dangotbanned Aug 21, 2025
cadcdf0
cov
dangotbanned Aug 21, 2025
5b2bc62
add typing tests, tweak overloads
dangotbanned Aug 21, 2025
14974bc
refactor(typing): Switch most overloads to `BaseFrame`
dangotbanned Aug 21, 2025
685409c
feat(typing): Get basic `LazyFrame.implementation` working
dangotbanned Aug 21, 2025
0f83e44
kinda support dask
dangotbanned Aug 21, 2025
49a10bd
ci: try include `dask` in typing?
dangotbanned Aug 21, 2025
fd2b93e
aaaaand `modin` as well
dangotbanned Aug 21, 2025
618ce8c
feat(typing): `duckdb` & `sqlframe` work!
dangotbanned Aug 21, 2025
0606a14
kinda support `ibis`
dangotbanned Aug 21, 2025
37aaa69
test(typing): Simplify Any/Into, also test lazy
dangotbanned Aug 21, 2025
141b687
test(typing): Add `DataFrame.lazy` suite
dangotbanned Aug 21, 2025
cabedd4
refactor: Prepare for `Series` support
dangotbanned Aug 22, 2025
71c5163
extend this overload abomination
dangotbanned Aug 22, 2025
2b7945b
refactor: Move `_ImplDescriptor` to `_utils`
dangotbanned Aug 22, 2025
c573cfd
feat(typing): Add (new) `Series.implementation`
dangotbanned Aug 22, 2025
410b5bd
oop
dangotbanned Aug 22, 2025
e07cbc5
test(typing): Add `Series` tests
dangotbanned Aug 22, 2025
c4bceed
test: Redo everything, check collect as well
dangotbanned Aug 22, 2025
c8dbe07
Merge branch 'main' into implementation-typing
dangotbanned Aug 23, 2025
eaa43c1
docs: Ensure `BaseFrame.implementation` shows in api ref
dangotbanned Aug 23, 2025
5ef8103
fix(typing): `ibis`, `dask` work!!!
dangotbanned Aug 23, 2025
f55cb3a
fix(typing): Unbreak `modin`
dangotbanned Aug 23, 2025
811290c
test(typing): Check `mpd.Series` too
dangotbanned Aug 23, 2025
012c2bf
typo
dangotbanned Aug 23, 2025
b0694d0
fix `mpd.Series`
dangotbanned Aug 23, 2025
2a75529
chore: Add overload for pyspark
dangotbanned Aug 23, 2025
7d42972
simplify, add notes
dangotbanned Aug 23, 2025
fd736c6
Merge branch 'main' into implementation-typing
dangotbanned Aug 23, 2025
5d2f54f
Merge branch 'main' into implementation-typing
dangotbanned Aug 24, 2025
05d4115
rename, add brief doc to `_Implementation`
dangotbanned Aug 24, 2025
87d4439
refactor: Rename `NarwhalsObj` -> `Narwhals`
dangotbanned Aug 24, 2025
bee6984
tighten up `Narwhals` w/ `Compliant`
dangotbanned Aug 24, 2025
1c68c68
docs(typing): Add `Narwhals` explainer
dangotbanned Aug 24, 2025
fcafec6
docs: Add crossref to `Implementation`
dangotbanned Aug 24, 2025
7157bbd
refactor: shrinking
dangotbanned Aug 24, 2025
08d900c
Merge branch 'main' into implementation-typing
dangotbanned Aug 24, 2025
b2aaf0d
Merge branch 'main' into implementation-typing
dangotbanned Aug 25, 2025
635b5a8
Merge branch 'main' into implementation-typing
dangotbanned Aug 25, 2025
5049a2a
docs: Explain typing test structure
dangotbanned Aug 26, 2025
4b78837
Update narwhals/_utils.py
dangotbanned Aug 26, 2025
29daf5e
Merge branch 'main' into implementation-typing
dangotbanned Aug 26, 2025
f6da9ce
Merge branch 'main' into implementation-typing
dangotbanned Aug 27, 2025
fe21d09
Merge remote-tracking branch 'upstream/main' into implementation-typing
dangotbanned Aug 27, 2025
a94a0f8
test(typing): Update for (#3032)
dangotbanned Aug 27, 2025
884d135
Merge branch 'main' into implementation-typing
dangotbanned Aug 28, 2025
4ad081c
Merge branch 'main' into implementation-typing
dangotbanned Aug 28, 2025
791ecae
ci: Exclude `OrderedDict` methods from `check-api-reference`
dangotbanned Aug 28, 2025
a3bd3ac
Merge branch 'main' into implementation-typing
dangotbanned Aug 28, 2025
043b9d1
Merge branch 'main' into implementation-typing
dangotbanned Aug 28, 2025
6b59ed9
Merge branch 'main' into implementation-typing
dangotbanned Aug 29, 2025
919b22f
Merge remote-tracking branch 'upstream/main' into implementation-typing
dangotbanned Aug 29, 2025
5e2838e
Merge branch 'main' into implementation-typing
dangotbanned Aug 30, 2025
6bc2c47
Merge branch 'main' into implementation-typing
dangotbanned Sep 2, 2025
21800fe
ci: Try adding `--group 'typing-ci'`
dangotbanned Sep 3, 2025
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
87 changes: 64 additions & 23 deletions narwhals/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,21 @@

from narwhals._compliant import CompliantDataFrame, CompliantLazyFrame
from narwhals._compliant.typing import CompliantExprAny, EagerNamespaceAny
from narwhals._namespace import _NativePandasLikeDataFrame
from narwhals._translate import IntoArrowTable
from narwhals._typing import Dask, DuckDB, EagerAllowed, Ibis, IntoBackend, Polars
from narwhals._typing import (
Dask,
DuckDB,
EagerAllowed,
Ibis,
IntoBackend,
Polars,
_ArrowImpl,
_EagerAllowedImpl,
_PandasImpl,
_PandasLikeImpl,
_PolarsImpl,
)
from narwhals.group_by import GroupBy, LazyGroupBy
from narwhals.typing import (
AsofJoinStrategy,
Expand Down Expand Up @@ -407,6 +420,36 @@ def explode(self, columns: str | Sequence[str], *more_columns: str) -> Self:
return self._with_compliant(self._compliant_frame.explode(columns=to_explode))


class _ImplDescriptor:
def __set_name__(self, owner: type[Any], name: str) -> None:
self.__name__: str = name

@overload
def __get__(self, instance: DataFrame[pl.DataFrame], owner: Any) -> _PolarsImpl: ...
@overload
def __get__(self, instance: DataFrame[pd.DataFrame], owner: Any) -> _PandasImpl: ...
@overload
def __get__(
self, instance: DataFrame[_NativePandasLikeDataFrame], owner: Any
) -> _PandasLikeImpl: ...
@overload
def __get__(self, instance: DataFrame[pa.Table], owner: Any) -> _ArrowImpl: ...
@overload
def __get__(
self, instance: DataFrame[pl.DataFrame | pd.DataFrame | pa.Table], owner: Any
) -> _PolarsImpl | _PandasImpl | _ArrowImpl: ...
@overload
def __get__(
self, instance: DataFrame[IntoDataFrame], owner: Any
) -> _EagerAllowedImpl: ...
@overload
def __get__(self, instance: None, owner: Any) -> Self: ...
def __get__(self, instance: Any | None, owner: Any) -> Any:
if instance is None:
return self
return instance._compliant_frame._implementation


class DataFrame(BaseFrame[DataFrameT]):
"""Narwhals DataFrame, backed by a native eager dataframe.

Expand Down Expand Up @@ -660,28 +703,26 @@ def from_numpy(
)
raise ValueError(msg)

@property
def implementation(self) -> Implementation:
"""Return implementation of native frame.

This can be useful when you need to use special-casing for features outside of
Narwhals' scope - for example, when dealing with pandas' Period Dtype.

Examples:
>>> import narwhals as nw
>>> import pandas as pd
>>> df_native = pd.DataFrame({"a": [1, 2, 3]})
>>> df = nw.from_native(df_native)
>>> df.implementation
<Implementation.PANDAS: 'pandas'>
>>> df.implementation.is_pandas()
True
>>> df.implementation.is_pandas_like()
True
>>> df.implementation.is_polars()
False
"""
return self._compliant_frame._implementation
implementation: _ImplDescriptor = _ImplDescriptor()
"""Return implementation of native frame.

This can be useful when you need to use special-casing for features outside of
Narwhals' scope - for example, when dealing with pandas' Period Dtype.

Examples:
>>> import narwhals as nw
>>> import pandas as pd
>>> df_native = pd.DataFrame({"a": [1, 2, 3]})
>>> df = nw.from_native(df_native)
>>> df.implementation
<Implementation.PANDAS: 'pandas'>
>>> df.implementation.is_pandas()
True
>>> df.implementation.is_pandas_like()
True
>>> df.implementation.is_polars()
False
"""

def __len__(self) -> int:
return self._compliant_frame.__len__()
Expand Down
11 changes: 2 additions & 9 deletions tests/expr_and_series/is_close_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import pytest

import narwhals as nw
from narwhals._utils import is_eager_allowed
from narwhals.exceptions import ComputeError, InvalidOperationError
from tests.conftest import (
dask_lazy_p1_constructor,
Expand Down Expand Up @@ -114,11 +113,8 @@ def test_is_close_series_with_series(
) -> None:
df = nw.from_native(constructor_eager(data), eager_only=True)
x, y = df["x"], df["y"]
backend = df.implementation
assert is_eager_allowed(backend)
Copy link
Member

Choose a reason for hiding this comment

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

πŸ₯³


nulls = nw.new_series(
name="nulls", values=[None] * len(x), dtype=nw.Float64(), backend=backend
"nulls", [None] * len(x), nw.Float64(), backend=df.implementation
)
x = x.zip_with(x != NAN_PLACEHOLDER, x**0.5).zip_with(x != NULL_PLACEHOLDER, nulls)
y = y.zip_with(y != NAN_PLACEHOLDER, y**0.5).zip_with(y != NULL_PLACEHOLDER, nulls)
Expand All @@ -141,11 +137,8 @@ def test_is_close_series_with_scalar(
) -> None:
df = nw.from_native(constructor_eager(data), eager_only=True)
y = df["y"]
backend = df.implementation
assert is_eager_allowed(backend)

nulls = nw.new_series(
name="nulls", values=[None] * len(y), dtype=nw.Float64(), backend=backend
"nulls", [None] * len(y), nw.Float64(), backend=df.implementation
)
y = y.zip_with(y != NAN_PLACEHOLDER, y**0.5).zip_with(y != NULL_PLACEHOLDER, nulls)
result = y.is_close(other, abs_tol=abs_tol, rel_tol=rel_tol, nans_equal=nans_equal)
Expand Down
7 changes: 4 additions & 3 deletions utils/check_api_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

# ruff: noqa: N806
from collections import deque
from inspect import isfunction
from inspect import isfunction, ismethoddescriptor
from pathlib import Path
from types import MethodType, ModuleType
from typing import TYPE_CHECKING, Any
Expand All @@ -24,13 +24,14 @@

def _is_public_method_or_property(obj: Any) -> bool:
return (
isfunction(obj) or isinstance(obj, (MethodType, property))
isfunction(obj)
or (isinstance(obj, (MethodType, property)) or ismethoddescriptor(obj))
) and obj.__name__.startswith(LOWERCASE)
else:

def _is_public_method_or_property(obj: Any) -> bool:
return (
(isfunction(obj) or isinstance(obj, MethodType))
(isfunction(obj) or (isinstance(obj, MethodType) or ismethoddescriptor(obj)))
and obj.__name__.startswith(LOWERCASE)
) or (isinstance(obj, property) and obj.fget.__name__.startswith(LOWERCASE))

Expand Down
Loading