Skip to content

Commit

Permalink
feat: validate library minimum version in compliant objects
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcoGorelli committed Jan 5, 2025
1 parent 9f4b419 commit 47c38a7
Show file tree
Hide file tree
Showing 14 changed files with 96 additions and 14 deletions.
2 changes: 2 additions & 0 deletions narwhals/_arrow/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from narwhals.utils import is_sequence_but_not_str
from narwhals.utils import parse_columns_to_drop
from narwhals.utils import scale_bytes
from narwhals.utils import validate_backend_version

if TYPE_CHECKING:
from types import ModuleType
Expand Down Expand Up @@ -56,6 +57,7 @@ def __init__(
self._implementation = Implementation.PYARROW
self._backend_version = backend_version
self._version = version
validate_backend_version(self._implementation, self._backend_version)

def __narwhals_namespace__(self: Self) -> ArrowNamespace:
from narwhals._arrow.namespace import ArrowNamespace
Expand Down
2 changes: 2 additions & 0 deletions narwhals/_arrow/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from narwhals.utils import Implementation
from narwhals.utils import generate_temporary_column_name
from narwhals.utils import import_dtypes_module
from narwhals.utils import validate_backend_version

if TYPE_CHECKING:
from types import ModuleType
Expand Down Expand Up @@ -54,6 +55,7 @@ def __init__(
self._implementation = Implementation.PYARROW
self._backend_version = backend_version
self._version = version
validate_backend_version(self._implementation, self._backend_version)

def _change_version(self: Self, version: Version) -> Self:
return self.__class__(
Expand Down
4 changes: 3 additions & 1 deletion narwhals/_dask/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
from narwhals._dask.utils import parse_exprs_and_named_exprs
from narwhals._pandas_like.utils import native_to_narwhals_dtype
from narwhals._pandas_like.utils import select_columns_by_name
from narwhals.typing import CompliantLazyFrame
from narwhals.utils import Implementation
from narwhals.utils import flatten
from narwhals.utils import generate_temporary_column_name
from narwhals.utils import parse_columns_to_drop
from narwhals.utils import parse_version
from narwhals.utils import validate_backend_version

if TYPE_CHECKING:
from types import ModuleType
Expand All @@ -29,7 +31,6 @@
from narwhals._dask.typing import IntoDaskExpr
from narwhals.dtypes import DType
from narwhals.utils import Version
from narwhals.typing import CompliantLazyFrame


class DaskLazyFrame(CompliantLazyFrame):
Expand All @@ -44,6 +45,7 @@ def __init__(
self._backend_version = backend_version
self._implementation = Implementation.DASK
self._version = version
validate_backend_version(self._implementation, self._backend_version)

def __native_namespace__(self: Self) -> ModuleType:
if self._implementation is Implementation.DASK:
Expand Down
18 changes: 15 additions & 3 deletions narwhals/_duckdb/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
from typing import Any

from narwhals.dependencies import get_duckdb
from narwhals.utils import Implementation
from narwhals.utils import import_dtypes_module
from narwhals.utils import parse_version
from narwhals.utils import validate_backend_version

if TYPE_CHECKING:
from types import ModuleType
Expand Down Expand Up @@ -82,9 +84,15 @@ def native_to_narwhals_dtype(duckdb_dtype: str, version: Version) -> DType:


class DuckDBInterchangeFrame:
def __init__(self, df: Any, version: Version) -> None:
_implementation = Implementation.DUCKDB

def __init__(
self, df: Any, *, backend_version: tuple[int, ...], version: Version
) -> None:
self._native_frame = df
self._version = version
self._backend_version = backend_version
validate_backend_version(self._implementation, self._backend_version)

def __narwhals_dataframe__(self) -> Any:
return self
Expand Down Expand Up @@ -147,10 +155,14 @@ def to_arrow(self: Self) -> pa.Table:
return self._native_frame.arrow()

def _change_version(self: Self, version: Version) -> Self:
return self.__class__(self._native_frame, version=version)
return self.__class__(
self._native_frame, version=version, backend_version=self._backend_version
)

def _from_native_frame(self: Self, df: Any) -> Self:
return self.__class__(df, version=self._version)
return self.__class__(
df, version=self._version, backend_version=self._backend_version
)

def collect_schema(self) -> dict[str, DType]:
return {
Expand Down
18 changes: 15 additions & 3 deletions narwhals/_ibis/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
from typing import Any

from narwhals.dependencies import get_ibis
from narwhals.utils import Implementation
from narwhals.utils import import_dtypes_module
from narwhals.utils import validate_backend_version

if TYPE_CHECKING:
from types import ModuleType
Expand Down Expand Up @@ -69,9 +71,15 @@ def native_to_narwhals_dtype(ibis_dtype: Any, version: Version) -> DType:


class IbisInterchangeFrame:
def __init__(self, df: Any, version: Version) -> None:
_implementation = Implementation.IBIS

def __init__(
self, df: Any, *, backend_version: tuple[int, ...], version: Version
) -> None:
self._native_frame = df
self._version = version
self._backend_version = backend_version
validate_backend_version(self._implementation, self._backend_version)

def __narwhals_dataframe__(self) -> Any:
return self
Expand Down Expand Up @@ -125,10 +133,14 @@ def __getattr__(self, attr: str) -> Any:
raise NotImplementedError(msg)

def _change_version(self: Self, version: Version) -> Self:
return self.__class__(self._native_frame, version=version)
return self.__class__(
self._native_frame, version=version, backend_version=self._backend_version
)

def _from_native_frame(self: Self, df: Any) -> Self:
return self.__class__(df, version=self._version)
return self.__class__(
df, version=self._version, backend_version=self._backend_version
)

def collect_schema(self) -> dict[str, DType]:
return {
Expand Down
2 changes: 2 additions & 0 deletions narwhals/_pandas_like/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from narwhals.utils import is_sequence_but_not_str
from narwhals.utils import parse_columns_to_drop
from narwhals.utils import scale_bytes
from narwhals.utils import validate_backend_version

if TYPE_CHECKING:
from types import ModuleType
Expand Down Expand Up @@ -59,6 +60,7 @@ def __init__(
self._implementation = implementation
self._backend_version = backend_version
self._version = version
validate_backend_version(self._implementation, self._backend_version)

def __narwhals_dataframe__(self) -> Self:
return self
Expand Down
2 changes: 2 additions & 0 deletions narwhals/_pandas_like/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from narwhals.typing import CompliantSeries
from narwhals.utils import Implementation
from narwhals.utils import import_dtypes_module
from narwhals.utils import validate_backend_version

if TYPE_CHECKING:
from types import ModuleType
Expand Down Expand Up @@ -94,6 +95,7 @@ def __init__(
self._implementation = implementation
self._backend_version = backend_version
self._version = version
validate_backend_version(self._implementation, self._backend_version)

def __native_namespace__(self: Self) -> ModuleType:
if self._implementation in {
Expand Down
3 changes: 3 additions & 0 deletions narwhals/_polars/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from narwhals.utils import Implementation
from narwhals.utils import is_sequence_but_not_str
from narwhals.utils import parse_columns_to_drop
from narwhals.utils import validate_backend_version

if TYPE_CHECKING:
from types import ModuleType
Expand Down Expand Up @@ -45,6 +46,7 @@ def __init__(
self._backend_version = backend_version
self._implementation = Implementation.POLARS
self._version = version
validate_backend_version(self._implementation, self._backend_version)

def __repr__(self: Self) -> str: # pragma: no cover
return "PolarsDataFrame"
Expand Down Expand Up @@ -343,6 +345,7 @@ def __init__(
self._backend_version = backend_version
self._implementation = Implementation.POLARS
self._version = version
validate_backend_version(self._implementation, self._backend_version)

def __repr__(self: Self) -> str: # pragma: no cover
return "PolarsLazyFrame"
Expand Down
2 changes: 2 additions & 0 deletions narwhals/_polars/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from narwhals._polars.utils import narwhals_to_native_dtype
from narwhals._polars.utils import native_to_narwhals_dtype
from narwhals.utils import Implementation
from narwhals.utils import validate_backend_version

if TYPE_CHECKING:
from types import ModuleType
Expand Down Expand Up @@ -38,6 +39,7 @@ def __init__(
self._backend_version = backend_version
self._implementation = Implementation.POLARS
self._version = version
validate_backend_version(self._implementation, self._backend_version)

def __repr__(self: Self) -> str: # pragma: no cover
return "PolarsSeries"
Expand Down
2 changes: 2 additions & 0 deletions narwhals/_spark_like/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from narwhals.utils import flatten
from narwhals.utils import parse_columns_to_drop
from narwhals.utils import parse_version
from narwhals.utils import validate_backend_version

if TYPE_CHECKING:
from pyspark.sql import DataFrame
Expand All @@ -37,6 +38,7 @@ def __init__(
self._backend_version = backend_version
self._implementation = Implementation.PYSPARK
self._version = version
validate_backend_version(self._implementation, self._backend_version)

def __native_namespace__(self) -> Any: # pragma: no cover
if self._implementation is Implementation.PYSPARK:
Expand Down
14 changes: 12 additions & 2 deletions narwhals/translate.py
Original file line number Diff line number Diff line change
Expand Up @@ -709,8 +709,13 @@ def _from_native_impl( # noqa: PLR0915
else:
return native_object
raise TypeError(msg)
import duckdb # ignore-banned-import

backend_version = parse_version(duckdb.__version__)
return DataFrame(
DuckDBInterchangeFrame(native_object, version=version),
DuckDBInterchangeFrame(
native_object, version=version, backend_version=backend_version
),
level="interchange",
)

Expand All @@ -726,8 +731,13 @@ def _from_native_impl( # noqa: PLR0915
)
raise TypeError(msg)
return native_object
import ibis # ignore-banned-import

backend_version = parse_version(ibis.__version__)
return DataFrame(
IbisInterchangeFrame(native_object, version=version),
IbisInterchangeFrame(
native_object, version=version, backend_version=backend_version
),
level="interchange",
)

Expand Down
27 changes: 27 additions & 0 deletions narwhals/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from narwhals.dependencies import get_cudf
from narwhals.dependencies import get_dask_dataframe
from narwhals.dependencies import get_duckdb
from narwhals.dependencies import get_modin
from narwhals.dependencies import get_pandas
from narwhals.dependencies import get_polars
Expand Down Expand Up @@ -73,6 +74,10 @@ class Implementation(Enum):
"""Polars implementation."""
DASK = auto()
"""Dask implementation."""
DUCKDB = auto()
"""DuckDB implementation."""
IBIS = auto()
"""Ibis implementation."""

UNKNOWN = auto()
"""Unknown implementation."""
Expand All @@ -97,6 +102,7 @@ def from_native_namespace(
get_pyspark_sql(): Implementation.PYSPARK,
get_polars(): Implementation.POLARS,
get_dask_dataframe(): Implementation.DASK,
get_duckdb(): Implementation.DUCKDB,
}
return mapping.get(native_namespace, Implementation.UNKNOWN)

Expand Down Expand Up @@ -246,6 +252,27 @@ def is_dask(self) -> bool:
return self is Implementation.DASK # pragma: no cover


MIN_VERSIONS: dict[Implementation, tuple[int, ...]] = {
Implementation.PANDAS: (0, 25, 3),
Implementation.MODIN: (0, 32),
Implementation.CUDF: (24, 10),
Implementation.PYARROW: (11,),
Implementation.PYSPARK: (3, 3),
Implementation.POLARS: (0, 20, 3),
Implementation.DASK: (2024, 10),
Implementation.DUCKDB: (1,),
Implementation.IBIS: (6,),
}


def validate_backend_version(
implementation: Implementation, backend_version: tuple[int, ...]
) -> None:
if backend_version < (min_version := MIN_VERSIONS[implementation]):
msg = f"Minimum version of {implementation} supported by Narwhals is {min_version}, found: {backend_version}"
raise ValueError(msg)


def import_dtypes_module(version: Version) -> DTypes:
if version is Version.V1:
from narwhals.stable.v1 import dtypes
Expand Down
11 changes: 6 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@ classifiers = [
]

[project.optional-dependencies]
cudf = ["cudf>=24.10.0"]
modin = ["modin"]
# these should be aligned with MIN_VERSIONS in narwhals/utils.py
pandas = ["pandas>=0.25.3"]
polars = ["polars>=0.20.3"]
ibis = ["ibis-framework>=6.0.0", "rich", "packaging", "pyarrow_hotfix"]
modin = ["modin>=0.32"]
cudf = ["cudf>=24.10.0"]
pyarrow = ["pyarrow>=11.0.0"]
pyspark = ["pyspark>=3.3.0"]
polars = ["polars>=0.20.3"]
dask = ["dask[dataframe]>=2024.10"]
duckdb = ["duckdb>=1.0"]
pyspark = ["pyspark>=3.3.0"]
ibis = ["ibis-framework>=6.0.0", "rich", "packaging", "pyarrow_hotfix"]
dev = [
"covdefaults",
"pre-commit",
Expand Down
3 changes: 3 additions & 0 deletions tests/expr_and_series/clip_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ def test_clip_series_expressified(
) -> None:
if "modin_pyarrow" in str(constructor_eager):
request.applymarker(pytest.mark.xfail)
if "cudf" in str(constructor_eager):
# https://github.com/rapidsai/cudf/issues/17682
request.applymarker(pytest.mark.xfail)

data = {"a": [1, 2, 3, -4, 5], "lb": [3, 2, 1, 1, 1], "ub": [4, 4, 2, 2, 2]}
df = nw.from_native(constructor_eager(data), eager_only=True)
Expand Down

0 comments on commit 47c38a7

Please sign in to comment.