Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: validate library minimum version in compliant objects #1727

Merged
merged 4 commits into from
Jan 5, 2025
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
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
10 changes: 5 additions & 5 deletions narwhals/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,16 @@ def get_duckdb() -> Any:
return sys.modules.get("duckdb", None)


def get_dask_expr() -> Any:
"""Get dask_expr module (if already imported - else return None)."""
return sys.modules.get("dask_expr", None)


def get_ibis() -> Any:
"""Get ibis module (if already imported - else return None)."""
return sys.modules.get("ibis", None)


def get_dask_expr() -> Any:
"""Get dask_expr module (if already imported - else return None)."""
return sys.modules.get("dask_expr", None)


def get_pyspark() -> Any: # pragma: no cover
"""Get pyspark module (if already imported - else return None)."""
return sys.modules.get("pyspark", None)
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
61 changes: 61 additions & 0 deletions narwhals/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

from narwhals.dependencies import get_cudf
from narwhals.dependencies import get_dask_dataframe
from narwhals.dependencies import get_duckdb
from narwhals.dependencies import get_ibis
from narwhals.dependencies import get_modin
from narwhals.dependencies import get_pandas
from narwhals.dependencies import get_polars
Expand Down Expand Up @@ -73,6 +75,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 +103,8 @@ def from_native_namespace(
get_pyspark_sql(): Implementation.PYSPARK,
get_polars(): Implementation.POLARS,
get_dask_dataframe(): Implementation.DASK,
get_duckdb(): Implementation.DUCKDB,
get_ibis(): Implementation.IBIS,
}
return mapping.get(native_namespace, Implementation.UNKNOWN)

Expand Down Expand Up @@ -245,6 +253,59 @@ def is_dask(self) -> bool:
"""
return self is Implementation.DASK # pragma: no cover

def is_duckdb(self) -> bool:
"""Return whether implementation is DuckDB.

Returns:
Boolean.

Examples:
>>> import polars as pl
>>> import narwhals as nw
>>> df_native = pl.DataFrame({"a": [1, 2, 3]})
>>> df = nw.from_native(df_native)
>>> df.implementation.is_duckdb()
False
"""
return self is Implementation.DUCKDB # pragma: no cover

def is_ibis(self) -> bool:
"""Return whether implementation is Ibis.

Returns:
Boolean.

Examples:
>>> import polars as pl
>>> import narwhals as nw
>>> df_native = pl.DataFrame({"a": [1, 2, 3]})
>>> df = nw.from_native(df_native)
>>> df.implementation.is_ibis()
False
"""
return self is Implementation.IBIS # pragma: no cover


MIN_VERSIONS: dict[Implementation, tuple[int, ...]] = {
Implementation.PANDAS: (0, 25, 3),
Implementation.MODIN: (0, 25, 3),
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:
Expand Down
Loading
Loading