Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
e745e37
chore!: Remove interchange (temporary)
dangotbanned Jun 27, 2026
4940eb3
chore: Reintroduce (limited) interchange support
dangotbanned Jun 27, 2026
3288b0d
grow up mypy
dangotbanned Jun 27, 2026
ce86e5c
test: fix invalid test
dangotbanned Jun 27, 2026
60068f0
test: isolate `test_invalid_filter`
dangotbanned Jun 28, 2026
3cfb767
chore: reintroduce `ibis`
dangotbanned Jun 28, 2026
6ba4d66
fix: remember that `duckdb` doesn't support interchange
dangotbanned Jun 28, 2026
902796a
fix: add compat alias for marimo
dangotbanned Jun 28, 2026
b7e533d
ignore mypy
dangotbanned Jun 28, 2026
4e9f986
Merge branch 'chore/issue-2932-rm-level' into chore/issue-2932-rm-lev…
dangotbanned Jun 29, 2026
3ceb3e0
test: add `tests.interchange`
dangotbanned Jun 29, 2026
ecfc247
test: migrate `get_level` tests
dangotbanned Jun 29, 2026
545e9a3
test: migrate `from_native` tests
dangotbanned Jun 29, 2026
fc21485
test: migrate `interchange_*` test modules
dangotbanned Jun 29, 2026
af940e6
docs: add a package doc
dangotbanned Jun 29, 2026
68506ea
fix: Don't require `eager_or_interchange_only=True`
dangotbanned Jun 29, 2026
d7c5b45
test: isolate interchange `is_into_*frame` tests
dangotbanned Jun 29, 2026
edc6fea
fix: Stop expecting interchange to support everything
dangotbanned Jun 29, 2026
4a85d71
chore: isolate `duckdb` interchange
dangotbanned Jun 29, 2026
af0bb3c
refactor: Simplify both `duckdb` and `ibis`
dangotbanned Jun 29, 2026
f4f7d72
test: cover `get_column`, `to_native` explicitly
dangotbanned Jun 29, 2026
68f8136
refactor: remove everything that isn't tested
dangotbanned Jun 29, 2026
28dbbf2
refactor: goodbye `supports_dataframe_interchange`
dangotbanned Jun 29, 2026
94f2e86
fix: avoid `Protocol.__init__`
dangotbanned Jun 30, 2026
395ee9d
chore: no cover on unreachable path
dangotbanned Jun 30, 2026
9adb2db
chore: remove dead comment
dangotbanned Jun 30, 2026
c27bfbd
refactor: Simplify `SQLLazyFrame.to_narwhals`
dangotbanned Jun 30, 2026
9a7879e
refactor: Expose what is actually used externally
dangotbanned Jun 30, 2026
0d596eb
refactor: Fix weird leftovers
dangotbanned Jun 30, 2026
322ccba
chore: rename and explain `__getattr__` trick
dangotbanned Jun 30, 2026
7699757
docs(typing): Explain magic
dangotbanned Jun 30, 2026
7c121b7
refactor: `__all__` is shorter
dangotbanned Jun 30, 2026
0402c65
shrinky-dink
dangotbanned Jun 30, 2026
4eb26e2
fix: Don't give false hope
dangotbanned Jun 30, 2026
fd13f4b
refactor: remove `_interchange.series.py`
dangotbanned Jun 30, 2026
35478b3
refactor: share more between interchange series/frame
dangotbanned Jun 30, 2026
c857c8c
test: update error pattern x2
dangotbanned Jun 30, 2026
f19899b
refactor: consistent imports
dangotbanned Jun 30, 2026
9f794e5
test: move fixtures to `interchange.conftest`
dangotbanned Jun 30, 2026
4bbdf11
merge upstream
FBruzzesi Jul 2, 2026
bb05096
fix: exclude `cudf.DataFrame` too
dangotbanned Jul 3, 2026
28545e0
refactor: lower density, more clarity?
dangotbanned Jul 3, 2026
8eb114c
chore: guard against `ibis` removing `__dataframe__` support
dangotbanned Jul 3, 2026
3199d03
no cov fireducks
dangotbanned Jul 3, 2026
955a677
refactor: invert checks, explain a bit more
dangotbanned Jul 3, 2026
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
76 changes: 10 additions & 66 deletions src/narwhals/_duckdb/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from duckdb import StarExpression

from narwhals._duckdb.utils import (
BACKEND_VERSION,
DeferredTimeZone,
F,
catch_duckdb_exception,
Expand Down Expand Up @@ -37,7 +38,6 @@
from pathlib import Path
from types import ModuleType

import pandas as pd
import pyarrow as pa
from duckdb import Expression
from typing_extensions import Self, TypeIs
Expand All @@ -46,23 +46,15 @@
from narwhals._duckdb.expr import DuckDBExpr
from narwhals._duckdb.group_by import DuckDBGroupBy
from narwhals._duckdb.namespace import DuckDBNamespace
from narwhals._duckdb.series import DuckDBInterchangeSeries
from narwhals._duckdb.utils import duckdb_dtypes
from narwhals._typing import _EagerAllowedImpl
from narwhals._utils import _LimitedContext
from narwhals.dataframe import LazyFrame
from narwhals.dtypes import DType
from narwhals.stable.v1 import DataFrame as DataFrameV1
from narwhals.typing import AsofJoinStrategy, JoinStrategy, UniqueKeepStrategy


class DuckDBLazyFrame(
SQLLazyFrame[
"DuckDBExpr",
"duckdb.DuckDBPyRelation",
"LazyFrame[duckdb.DuckDBPyRelation] | DataFrameV1[duckdb.DuckDBPyRelation]",
],
ValidateBackendVersion,
SQLLazyFrame["DuckDBExpr", "duckdb.DuckDBPyRelation"], ValidateBackendVersion
):
_implementation = Implementation.DUCKDB

Expand All @@ -77,7 +69,7 @@ def __init__(
self._version = version
self._cached_native_schema: dict[str, duckdb_dtypes.DuckDBPyType] | None = None
self._cached_columns: list[str] | None = None
if validate_backend_version:
if validate_backend_version: # pragma: no cover
self._validate_backend_version()

@property
Expand All @@ -94,22 +86,6 @@ def from_native(
) -> Self:
return cls(data, version=context._version)

def to_narwhals(
self, *args: Any, **kwds: Any
) -> LazyFrame[duckdb.DuckDBPyRelation] | DataFrameV1[duckdb.DuckDBPyRelation]:
if self._version is Version.V1:
from narwhals.stable.v1 import DataFrame as DataFrameV1

return DataFrameV1(self) # type: ignore[no-any-return]
return self._version.lazyframe(self)

def __narwhals_dataframe__(self) -> Self: # pragma: no cover
# Keep around for backcompat.
if self._version is not Version.V1:
msg = "__narwhals_dataframe__ is not implemented for DuckDBLazyFrame"
raise AttributeError(msg)
return self

def __narwhals_lazyframe__(self) -> Self:
return self

Expand All @@ -121,11 +97,6 @@ def __narwhals_namespace__(self) -> DuckDBNamespace:

return DuckDBNamespace(version=self._version)

def get_column(self, name: str) -> DuckDBInterchangeSeries:
from narwhals._duckdb.series import DuckDBInterchangeSeries

return DuckDBInterchangeSeries(self.native.select(name), version=self._version)

def _iter_columns(self) -> Iterator[Expression]:
for name in self.columns:
yield col(name)
Expand All @@ -134,19 +105,8 @@ def collect(
self, backend: _EagerAllowedImpl | None, **kwargs: Any
) -> CompliantDataFrameAny:
if backend is None or backend is Implementation.PYARROW:
from narwhals._arrow.dataframe import ArrowDataFrame

res_native = (
self.native.to_arrow_table()
if self._backend_version >= (1, 5)
else self.native.fetch_arrow_table()
)
return ArrowDataFrame(
res_native,
validate_backend_version=True,
version=self._version,
validate_column_names=True,
)
ns = self._version.namespace.from_backend(Implementation.PYARROW).compliant
return ns.from_native(to_arrow_table(self.native))

if backend is Implementation.PANDAS:
from narwhals._pandas_like.dataframe import PandasLikeDataFrame
Expand Down Expand Up @@ -198,16 +158,6 @@ def drop(self, columns: Sequence[str], *, strict: bool) -> Self:
selection = [col(name) for name in self.columns if name not in columns_to_drop]
return self._with_native(self.native.select(*selection))

def lazy(self, backend: None = None, **_: None) -> Self:
# The `backend`` argument has no effect but we keep it here for
# backwards compatibility because in `narwhals.stable.v1`
# function `.from_native()` will return a DataFrame for DuckDB.

if backend is not None: # pragma: no cover
msg = "`backend` argument is not supported for DuckDB"
raise ValueError(msg)
return self

def with_columns(self, *exprs: DuckDBExpr) -> Self:
new_columns_map = dict(evaluate_exprs_and_aliases(self, *exprs))
result = [
Expand Down Expand Up @@ -259,14 +209,6 @@ def columns(self) -> list[str]:
)
return self._cached_columns

def to_pandas(self) -> pd.DataFrame:
# only if version is v1, keep around for backcompat
return self.native.df()

def to_arrow(self) -> pa.Table:
# only if version is v1, keep around for backcompat
return self.lazy().collect(Implementation.PYARROW).native # type: ignore[no-any-return]

def _with_version(self, version: Version) -> Self:
return self.__class__(self.native, version=version)

Expand Down Expand Up @@ -540,9 +482,6 @@ def unpivot(

@requires.backend_version((1, 3))
def with_row_index(self, name: str, order_by: Sequence[str]) -> Self:
if order_by is None:
msg = "Cannot pass `order_by` to `with_row_index` for DuckDB"
raise TypeError(msg)
Comment thread
dangotbanned marked this conversation as resolved.
expr = (window_expression(F("row_number"), order_by=order_by) - lit(1)).alias(
name
)
Expand All @@ -556,3 +495,8 @@ def sink_parquet(self, file: str | Path | BytesIO) -> None:
(FORMAT parquet)
""" # noqa: S608
duckdb.sql(query)


def to_arrow_table(rel: duckdb.DuckDBPyRelation) -> pa.Table:
to_arrow = rel.to_arrow_table if BACKEND_VERSION >= (1, 5) else rel.fetch_arrow_table
return to_arrow()
23 changes: 23 additions & 0 deletions src/narwhals/_duckdb/interchange.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from duckdb import DuckDBPyRelation

from narwhals import _interchange
from narwhals._duckdb.dataframe import to_arrow_table
from narwhals._utils import Implementation

if TYPE_CHECKING:
import pandas as pd
import pyarrow as pa


class DuckDBDataFrame(_interchange.LazyFrame[DuckDBPyRelation]):
_implementation = Implementation.DUCKDB

def to_pandas(self) -> pd.DataFrame:
return self._compliant.native.df()

def to_arrow(self) -> pa.Table:
return to_arrow_table(self._compliant.native)
44 changes: 0 additions & 44 deletions src/narwhals/_duckdb/series.py

This file was deleted.

2 changes: 1 addition & 1 deletion src/narwhals/_duckdb/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ def narwhals_to_native_dtype( # noqa: PLR0912, C901
if duckdb_type := NW_TO_DUCKDB_DTYPES.get(base_type):
return duckdb_type
if isinstance_or_issubclass(dtype, dtypes.Enum):
if version is Version.V1:
if version is Version.V1: # pragma: no cover

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Probably should remove this branch (same reason as #3721 (comment))

msg = "Converting to Enum is not supported in narwhals.stable.v1"
raise NotImplementedError(msg)
if isinstance(dtype, dtypes.Enum):
Expand Down
47 changes: 1 addition & 46 deletions src/narwhals/_ibis/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,21 @@
from types import ModuleType
from typing import TypeAlias

import pandas as pd
import pyarrow as pa
from ibis.expr.operations import Binary
from typing_extensions import Self, TypeIs

from narwhals._compliant.typing import CompliantDataFrameAny
from narwhals._ibis.group_by import IbisGroupBy
from narwhals._ibis.namespace import IbisNamespace
from narwhals._ibis.series import IbisInterchangeSeries
from narwhals._typing import _EagerAllowedImpl
from narwhals._utils import _LimitedContext
from narwhals.dataframe import LazyFrame
from narwhals.dtypes import DType
from narwhals.stable.v1 import DataFrame as DataFrameV1
from narwhals.typing import AsofJoinStrategy, JoinStrategy, UniqueKeepStrategy

JoinPredicates: TypeAlias = "Sequence[ir.BooleanColumn] | Sequence[str]"


class IbisLazyFrame(
SQLLazyFrame["IbisExpr", "ir.Table", "LazyFrame[ir.Table] | DataFrameV1[ir.Table]"],
ValidateBackendVersion,
):
class IbisLazyFrame(SQLLazyFrame["IbisExpr", "ir.Table"], ValidateBackendVersion):
_implementation = Implementation.IBIS

def __init__(
Expand All @@ -70,20 +62,6 @@ def _is_native(obj: ir.Table | Any) -> TypeIs[ir.Table]:
def from_native(cls, data: ir.Table, /, *, context: _LimitedContext) -> Self:
return cls(data, version=context._version)

def to_narwhals(self) -> LazyFrame[ir.Table] | DataFrameV1[ir.Table]:
if self._version is Version.V1:
from narwhals.stable.v1 import DataFrame

return DataFrame(self)
return self._version.lazyframe(self)

def __narwhals_dataframe__(self) -> Self: # pragma: no cover
# Keep around for backcompat.
if self._version is not Version.V1:
msg = "__narwhals_dataframe__ is not implemented for IbisLazyFrame"
raise AttributeError(msg)
return self

def __narwhals_lazyframe__(self) -> Self:
return self

Expand All @@ -95,11 +73,6 @@ def __narwhals_namespace__(self) -> IbisNamespace:

return IbisNamespace(version=self._version)

def get_column(self, name: str) -> IbisInterchangeSeries:
from narwhals._ibis.series import IbisInterchangeSeries

return IbisInterchangeSeries(self.native.select(name), version=self._version)

def _iter_columns(self) -> Iterator[ir.Expr]:
for name in self.columns:
yield self.native[name]
Expand Down Expand Up @@ -167,16 +140,6 @@ def drop(self, columns: Sequence[str], *, strict: bool) -> Self:
selection = (col for col in self.columns if col not in columns_to_drop)
return self._with_native(self.native.select(*selection))

def lazy(self, backend: None = None, **_: None) -> Self:
# The `backend`` argument has no effect but we keep it here for
# backwards compatibility because in `narwhals.stable.v1`
# function `.from_native()` will return a DataFrame for Ibis.

if backend is not None: # pragma: no cover
msg = "`backend` argument is not supported for Ibis"
raise ValueError(msg)
return self

def with_columns(self, *exprs: IbisExpr) -> Self:
new_columns_map = dict(evaluate_exprs(self, *exprs))
return self._with_native(self.native.mutate(**new_columns_map))
Expand Down Expand Up @@ -207,14 +170,6 @@ def columns(self) -> list[str]:
)
return self._cached_columns

def to_pandas(self) -> pd.DataFrame:
# only if version is v1, keep around for backcompat
return self.native.to_pandas()

def to_arrow(self) -> pa.Table:
# only if version is v1, keep around for backcompat
return self.native.to_pyarrow()

def _with_version(self, version: Version) -> Self:
return self.__class__(self.native, version=version)

Expand Down
22 changes: 22 additions & 0 deletions src/narwhals/_ibis/interchange.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from __future__ import annotations

from typing import TYPE_CHECKING

import ibis

from narwhals import _interchange
from narwhals._utils import Implementation

if TYPE_CHECKING:
import pandas as pd
import pyarrow as pa


class IbisDataFrame(_interchange.LazyFrame[ibis.Table]):
_implementation = Implementation.IBIS

def to_pandas(self) -> pd.DataFrame:
return self._compliant.native.to_pandas()

def to_arrow(self) -> pa.Table:
return self._compliant.native.to_pyarrow()
41 changes: 0 additions & 41 deletions src/narwhals/_ibis/series.py

This file was deleted.

Loading
Loading