diff --git a/marimo/_data/charts.py b/marimo/_data/charts.py index 50cf574f604..f16bc199f5d 100644 --- a/marimo/_data/charts.py +++ b/marimo/_data/charts.py @@ -7,7 +7,7 @@ from textwrap import dedent from typing import TYPE_CHECKING, Any, Literal, Optional, cast -import narwhals.stable.v1 as nw +import narwhals.stable.v2 as nw from marimo._data.models import DataType from marimo._utils import assert_never @@ -351,7 +351,9 @@ def _guess_date_format( if not can_narwhalify(data, eager_only=True): return self.DEFAULT_DATE_FORMAT, self.DEFAULT_TIME_UNIT - df = nw.from_native(data, eager_only=True) + df: nw.DataFrame[Any] = nw.from_native( + data, pass_through=True, eager_only=True + ) # Get min and max dates using narwhals min_date = df[column].min() diff --git a/marimo/_data/preview_column.py b/marimo/_data/preview_column.py index 957e11c250f..6a6ed7c452f 100644 --- a/marimo/_data/preview_column.py +++ b/marimo/_data/preview_column.py @@ -3,7 +3,7 @@ from typing import Any, Optional -import narwhals.stable.v1 as nw +import narwhals.stable.v2 as nw from marimo import _loggers from marimo._data.charts import get_chart_builder @@ -21,6 +21,7 @@ from marimo._plugins.ui._impl.tables.utils import get_table_manager_or_none from marimo._runtime.requests import PreviewDatasetColumnRequest from marimo._sql.utils import wrapped_sql +from marimo._utils.narwhals_utils import downgrade_narwhals_df_to_v1 LOGGER = _loggers.marimo_logger() @@ -274,7 +275,10 @@ def _get_altair_chart( # We may not know number of rows, so we can check for max rows error try: chart_spec = _get_chart_spec( - column_data=column_data, + # Downgrade to v1 since altair doesn't support v2 yet + # This is valiadted with our tests, so if the tests pass with this + # removed, we can remove the downgrade. + column_data=downgrade_narwhals_df_to_v1(column_data), column_type=column_type, column_name=column_name, should_limit_to_10_items=should_limit_to_10_items, diff --git a/marimo/_data/series.py b/marimo/_data/series.py index 594bad0c579..7d3f2e86d0a 100644 --- a/marimo/_data/series.py +++ b/marimo/_data/series.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from typing import Any, cast -import narwhals.stable.v1 as nw +import narwhals.stable.v2 as nw from narwhals.typing import IntoSeries from marimo._utils.narwhals_utils import ( @@ -54,7 +54,7 @@ def _get_name(series: nw.Series) -> str: return str(series.name) -@nw.narwhalify(eager_or_interchange_only=True, series_only=True) +@nw.narwhalify(eager_only=True, series_only=True) def get_number_series_info(series: nw.Series) -> NumberSeriesInfo: """ Get the summary of a numeric series. @@ -77,7 +77,7 @@ def validate_number(value: Any) -> float: ) -@nw.narwhalify(eager_or_interchange_only=True, series_only=True) +@nw.narwhalify(eager_only=True, series_only=True) def get_category_series_info(series: nw.Series) -> CategorySeriesInfo: """ Get the summary of a categorical series. @@ -92,7 +92,7 @@ def get_category_series_info(series: nw.Series) -> CategorySeriesInfo: ) -@nw.narwhalify(eager_or_interchange_only=True, series_only=True) +@nw.narwhalify(eager_only=True, series_only=True) def get_date_series_info(series: nw.Series) -> DateSeriesInfo: """ Get the summary of a date series. @@ -116,7 +116,7 @@ def validate_date(value: Any) -> str: ) -@nw.narwhalify(eager_or_interchange_only=True, series_only=True) +@nw.narwhalify(eager_only=True, series_only=True) def get_datetime_series_info(series: nw.Series) -> DateSeriesInfo: """ Get the summary of a datetime series. diff --git a/marimo/_output/formatters/df_formatters.py b/marimo/_output/formatters/df_formatters.py index 29b6a2df99e..6b565a86a7a 100644 --- a/marimo/_output/formatters/df_formatters.py +++ b/marimo/_output/formatters/df_formatters.py @@ -6,7 +6,7 @@ from enum import Enum from typing import Any -import narwhals.stable.v1 as nw +import narwhals.stable.v2 as nw from marimo import _loggers from marimo._messaging.mimetypes import KnownMimeType diff --git a/marimo/_plugins/core/media.py b/marimo/_plugins/core/media.py index b91a2e37a8c..8d73597585e 100644 --- a/marimo/_plugins/core/media.py +++ b/marimo/_plugins/core/media.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Any, Optional, Union, cast from urllib.parse import urlparse -import narwhals.stable.v1 as nw +import narwhals.stable.v2 as nw from marimo._dependencies.dependencies import DependencyManager from marimo._utils.narwhals_utils import can_narwhalify @@ -129,7 +129,9 @@ def io_to_data_url( # Handle Pandas DataFrames (convert to CSV) if can_narwhalify(src, eager_only=True): - df = nw.from_native(src, pass_through=False, eager_only=True) + df: nw.DataFrame[Any] = nw.from_native( + src, pass_through=True, eager_only=True + ) file = io.BytesIO() df.write_csv(file) file.seek(0) diff --git a/marimo/_plugins/ui/_impl/altair_chart.py b/marimo/_plugins/ui/_impl/altair_chart.py index 0b2be2b55eb..484635daf88 100644 --- a/marimo/_plugins/ui/_impl/altair_chart.py +++ b/marimo/_plugins/ui/_impl/altair_chart.py @@ -14,8 +14,8 @@ cast, ) -import narwhals.stable.v1 as nw -from narwhals.typing import IntoDataFrame +import narwhals.stable.v2 as nw +from narwhals.typing import IntoDataFrame, IntoLazyFrame from marimo import _loggers from marimo._dependencies.dependencies import DependencyManager @@ -54,7 +54,9 @@ RowOrientedData = list[dict[str, Any]] ColumnOrientedData = dict[str, list[Any]] -ChartDataType = Union[IntoDataFrame, RowOrientedData, ColumnOrientedData] +ChartDataType = Union[ + IntoDataFrame, IntoLazyFrame, RowOrientedData, ColumnOrientedData +] # Union of all possible chart types AltairChartType: TypeAlias = "altair.vegalite.v5.api.ChartType" @@ -96,8 +98,8 @@ def _using_vegafusion() -> bool: def _filter_dataframe( - native_df: IntoDataFrame, selection: ChartSelection -) -> IntoDataFrame: + native_df: Union[IntoDataFrame, IntoLazyFrame], selection: ChartSelection +) -> Union[IntoDataFrame, IntoLazyFrame]: df = nw.from_native(native_df) if not isinstance(selection, dict): raise TypeError("Input 'selection' must be a dictionary") @@ -182,14 +184,9 @@ def _coerce_value(value: Any, dtype: Any) -> Any: if nw.Datetime == dtype and isinstance(dtype, nw.Datetime): if isinstance(value, str): res = datetime.datetime.fromisoformat(value) - # If dtype has no timezone, shift by local timezone offset - if dtype.time_zone is None: - local_tz = datetime.datetime.now().astimezone().tzinfo - LOGGER.warning( - f"Datetime was given with a timezone when not expected. " - f"Shifting by local timezone offset {local_tz}." - ) - return res.astimezone(local_tz).replace(tzinfo=None) + # If dtype has no timezone, but value has timezone, remove timezone without shifting + if dtype.time_zone is None and res.tzinfo is not None: + return res.replace(tzinfo=None) return res # Value is milliseconds since epoch diff --git a/marimo/_plugins/ui/_impl/charts/altair_transformer.py b/marimo/_plugins/ui/_impl/charts/altair_transformer.py index 65e579b4aa2..7118ac0f414 100644 --- a/marimo/_plugins/ui/_impl/charts/altair_transformer.py +++ b/marimo/_plugins/ui/_impl/charts/altair_transformer.py @@ -4,7 +4,7 @@ import base64 from typing import Any, Literal, TypedDict, Union -import narwhals.stable.v1 as nw +import narwhals.stable.v2 as nw from narwhals.typing import IntoDataFrame import marimo._output.data.data as mo_data @@ -137,7 +137,9 @@ def _maybe_sanitize_dataframe(data: Any) -> Any: ): narwhals_data = nw.from_native(data) try: - res: nw.DataFrame[Any] = alt.utils.sanitize_narwhals_dataframe( + import narwhals.stable.v1 as nw1 + + res: nw1.DataFrame[Any] = alt.utils.sanitize_narwhals_dataframe( narwhals_data # type: ignore[arg-type] ) return res.to_native() # type: ignore[return-value] diff --git a/marimo/_plugins/ui/_impl/data_editor.py b/marimo/_plugins/ui/_impl/data_editor.py index d544e67043f..f0a38e3d4f0 100644 --- a/marimo/_plugins/ui/_impl/data_editor.py +++ b/marimo/_plugins/ui/_impl/data_editor.py @@ -17,7 +17,7 @@ cast, ) -import narwhals.stable.v1 as nw +import narwhals.stable.v2 as nw from narwhals.typing import IntoDataFrame import marimo._output.data.data as mo_data @@ -303,7 +303,7 @@ def _apply_edits_row_oriented( def _apply_edits_dataframe( native_df: IntoDataFrame, edits: DataEdits, schema: Optional[nw.Schema] ) -> IntoDataFrame: - df = nw.from_native(native_df, eager_or_interchange_only=True) + df = nw.from_native(native_df, eager_only=True) column_oriented = df.to_dict(as_series=False) schema = schema or cast(nw.Schema, df.schema) diff --git a/marimo/_plugins/ui/_impl/dataframes/transforms/apply.py b/marimo/_plugins/ui/_impl/dataframes/transforms/apply.py index 0be0b99a05c..ca469c6f909 100644 --- a/marimo/_plugins/ui/_impl/dataframes/transforms/apply.py +++ b/marimo/_plugins/ui/_impl/dataframes/transforms/apply.py @@ -3,6 +3,8 @@ from typing import Any, Generic, TypeVar +from narwhals.dependencies import is_narwhals_dataframe + from marimo._dependencies.dependencies import DependencyManager from marimo._plugins.ui._impl.dataframes.transforms.handlers import ( IbisTransformHandler, @@ -84,9 +86,7 @@ def get_handler_for_dataframe( return IbisTransformHandler() if DependencyManager.narwhals.imported(): - import narwhals as nw - - if isinstance(df, nw.DataFrame): + if is_narwhals_dataframe(df): return get_handler_for_dataframe(df.to_native()) raise ValueError( diff --git a/marimo/_plugins/ui/_impl/tables/narwhals_table.py b/marimo/_plugins/ui/_impl/tables/narwhals_table.py index fea3a4bdc24..7b66bcfb473 100644 --- a/marimo/_plugins/ui/_impl/tables/narwhals_table.py +++ b/marimo/_plugins/ui/_impl/tables/narwhals_table.py @@ -8,8 +8,8 @@ from typing import Any, Optional, Union, cast import msgspec -import narwhals.stable.v1 as nw -from narwhals.stable.v1.typing import IntoFrameT +import narwhals.stable.v2 as nw +from narwhals.typing import IntoDataFrameT, IntoLazyFrameT from marimo import _loggers from marimo._data.models import BinValue, ColumnStats, ExternalDataType @@ -32,13 +32,13 @@ from marimo._utils.narwhals_utils import ( can_narwhalify, dataframe_to_csv, + downgrade_narwhals_df_to_v1, is_narwhals_integer_type, is_narwhals_lazyframe, is_narwhals_string_type, is_narwhals_temporal_type, is_narwhals_time_type, unwrap_py_scalar, - upgrade_narwhals_df, ) LOGGER = _loggers.marimo_logger() @@ -46,12 +46,16 @@ class NarwhalsTableManager( - TableManager[Union[nw.DataFrame[IntoFrameT], nw.LazyFrame[IntoFrameT]]] + TableManager[ + Union[nw.DataFrame[IntoDataFrameT], nw.LazyFrame[IntoLazyFrameT]] + ] ): type = "narwhals" @staticmethod - def from_dataframe(data: IntoFrameT) -> NarwhalsTableManager[IntoFrameT]: + def from_dataframe( + data: Union[IntoDataFrameT, IntoLazyFrameT], + ) -> NarwhalsTableManager[IntoDataFrameT, IntoLazyFrameT]: return NarwhalsTableManager(nw.from_native(data, pass_through=False)) def as_frame(self) -> nw.DataFrame[Any]: @@ -59,9 +63,6 @@ def as_frame(self) -> nw.DataFrame[Any]: return self.data.collect() return self.data - def upgrade(self) -> NarwhalsTableManager[Any]: - return NarwhalsTableManager(upgrade_narwhals_df(self.data)) - def as_lazy_frame(self) -> nw.LazyFrame[Any]: if is_narwhals_lazyframe(self.data): return self.data @@ -86,7 +87,7 @@ def to_csv_str( def to_json_str( self, format_mapping: Optional[FormatMapping] = None ) -> str: - frame = self.upgrade().apply_formatting(format_mapping).as_frame() + frame = self.apply_formatting(format_mapping).as_frame() return sanitize_json_bigint(frame.rows(named=True)) def to_parquet(self) -> bytes: @@ -96,11 +97,11 @@ def to_parquet(self) -> bytes: def apply_formatting( self, format_mapping: Optional[FormatMapping] - ) -> NarwhalsTableManager[Any]: + ) -> NarwhalsTableManager[IntoDataFrameT, IntoLazyFrameT]: if not format_mapping: return self - frame = self.upgrade().as_frame() + frame = self.as_frame() _data = frame.to_dict(as_series=False).copy() for col in _data.keys(): if col in format_mapping: @@ -114,7 +115,9 @@ def apply_formatting( def supports_filters(self) -> bool: return True - def select_rows(self, indices: list[int]) -> TableManager[Any]: + def select_rows( + self, indices: list[int] + ) -> TableManager[Union[IntoDataFrameT, IntoLazyFrameT]]: if not indices: return self.with_new_data(self.data.head(0)) @@ -456,7 +459,10 @@ def get_bin_values(self, column: str, num_bins: int) -> list[BinValue]: if not dtype.is_numeric(): return [] - col = self.as_frame().get_column(column) + # Downgrade to v1 since v2 does not support the hist() method yet + downgraded_df = downgrade_narwhals_df_to_v1(self.as_frame()) + col = downgraded_df.get_column(column) + bin_start = col.min() bin_values: list[BinValue] = [] @@ -484,10 +490,12 @@ def _get_bin_values_temporal( nw.hist does not support temporal columns, so we convert to numeric and then convert back to temporal values. """ - # Convert to timestamp in ms - col = self.as_frame().get_column(column) + # Downgrade to v1 since v2 does not support the hist() method yet + downgraded_df = downgrade_narwhals_df_to_v1(self.as_frame()) + col = downgraded_df.get_column(column) if dtype == nw.Time: + # Convert to timestamp in ms col_in_ms = ( col.dt.hour().cast(nw.Int64) * 3600000 + col.dt.minute().cast(nw.Int64) * 60000 diff --git a/marimo/_plugins/ui/_impl/tables/pandas_table.py b/marimo/_plugins/ui/_impl/tables/pandas_table.py index 731aed0399a..b8a122226a2 100644 --- a/marimo/_plugins/ui/_impl/tables/pandas_table.py +++ b/marimo/_plugins/ui/_impl/tables/pandas_table.py @@ -6,7 +6,7 @@ from functools import cached_property from typing import TYPE_CHECKING, Any, Optional -import narwhals.stable.v1 as nw +import narwhals.stable.v2 as nw from marimo import _loggers from marimo._data.models import ExternalDataType @@ -57,7 +57,7 @@ def package_name() -> str: def create() -> type[TableManager[Any]]: import pandas as pd - class PandasTableManager(NarwhalsTableManager[pd.DataFrame]): + class PandasTableManager(NarwhalsTableManager[pd.DataFrame, Any]): type = "pandas" def __init__(self, data: pd.DataFrame) -> None: diff --git a/marimo/_plugins/ui/_impl/tables/polars_table.py b/marimo/_plugins/ui/_impl/tables/polars_table.py index 725716b24b6..058392d8f8f 100644 --- a/marimo/_plugins/ui/_impl/tables/polars_table.py +++ b/marimo/_plugins/ui/_impl/tables/polars_table.py @@ -6,7 +6,7 @@ from functools import cached_property from typing import Any, Optional, Union -import narwhals.stable.v1 as nw +import narwhals.stable.v2 as nw from marimo import _loggers from marimo._data.models import ( @@ -38,7 +38,7 @@ def create() -> type[TableManager[Any]]: import polars as pl class PolarsTableManager( - NarwhalsTableManager[Union[pl.DataFrame, pl.LazyFrame]] + NarwhalsTableManager[pl.DataFrame, pl.LazyFrame] ): type = "polars" diff --git a/marimo/_plugins/ui/_impl/tables/selection.py b/marimo/_plugins/ui/_impl/tables/selection.py index 1d8b98d5855..4a70bda31e6 100644 --- a/marimo/_plugins/ui/_impl/tables/selection.py +++ b/marimo/_plugins/ui/_impl/tables/selection.py @@ -3,7 +3,7 @@ from typing import TypeVar, cast -import narwhals.stable.v1 as nw +import narwhals.stable.v2 as nw from narwhals.typing import IntoDataFrame INDEX_COLUMN_NAME = "_marimo_row_id" diff --git a/marimo/_runtime/primitives.py b/marimo/_runtime/primitives.py index 606d727407c..9e331f27fa9 100644 --- a/marimo/_runtime/primitives.py +++ b/marimo/_runtime/primitives.py @@ -83,7 +83,7 @@ def is_data_primitive(value: Any) -> bool: elif hasattr(value, "dtypes"): # Bit of discrepancy between objects like polars and pandas, so use # narwhals to normalize the dataframe. - import narwhals as nw + import narwhals.stable.v2 as nw try: return bool( diff --git a/marimo/_utils/narwhals_utils.py b/marimo/_utils/narwhals_utils.py index a295c260f40..5a4ffa1c72f 100644 --- a/marimo/_utils/narwhals_utils.py +++ b/marimo/_utils/narwhals_utils.py @@ -6,7 +6,9 @@ import narwhals as nw_main import narwhals.dtypes as nw_dtypes -import narwhals.stable.v1 as nw +import narwhals.stable.v1 as nw1 +import narwhals.stable.v2 as nw +from narwhals.typing import IntoDataFrame from marimo._dependencies.dependencies import DependencyManager @@ -17,11 +19,21 @@ if TYPE_CHECKING: - from narwhals.typing import IntoFrame + from narwhals.typing import IntoDataFrame, IntoFrame, IntoLazyFrame from typing_extensions import TypeIs -def empty_df(native_df: IntoFrame) -> IntoFrame: +@overload +def empty_df(native_df: IntoDataFrame) -> IntoDataFrame: ... + + +@overload +def empty_df(native_df: IntoLazyFrame) -> IntoLazyFrame: ... + + +def empty_df( + native_df: Union[IntoDataFrame, IntoLazyFrame], +) -> Union[IntoDataFrame, IntoLazyFrame]: """ Get an empty dataframe with the same schema as the given dataframe. """ @@ -31,11 +43,13 @@ def empty_df(native_df: IntoFrame) -> IntoFrame: return native_df -def assert_narwhals_dataframe(df: nw.DataFrame[Any]) -> None: +def assert_narwhals_dataframe_or_lazyframe( + df: nw.DataFrame[Any] | nw.LazyFrame[Any], +) -> None: """ - Assert that the given dataframe is a valid narwhals dataframe. + Assert that the given dataframe is a valid narwhals dataframe or lazyframe. """ - if not is_narwhals_dataframe(df): + if not is_narwhals_dataframe(df) and not is_narwhals_lazyframe(df): raise ValueError(f"Unsupported dataframe type. Got {type(df)}") @@ -56,7 +70,7 @@ def can_narwhalify( if obj is None: return False try: - nw.from_native(obj, strict=True, eager_only=eager_only) # type: ignore[call-overload] + nw.from_native(obj, pass_through=False, eager_only=eager_only) # type: ignore[call-overload] return True except TypeError: return False @@ -75,7 +89,7 @@ def dataframe_to_csv(df: IntoFrame) -> str: Convert a dataframe to a CSV string. """ assert_can_narwhalify(df) - df = nw.from_native(df, strict=True) + df = nw.from_native(df, pass_through=False) df = upgrade_narwhals_df(df) if is_narwhals_lazyframe(df): return df.collect().write_csv() @@ -192,13 +206,38 @@ def upgrade_narwhals_df( return nw_main.from_native(df.to_native()) # type: ignore[no-any-return] +@overload +def downgrade_narwhals_df_to_v1( + df: nw.LazyFrame[Any], +) -> nw.LazyFrame[Any]: ... + + +@overload +def downgrade_narwhals_df_to_v1( + df: nw.DataFrame[Any], +) -> nw.DataFrame[Any]: ... + + +def downgrade_narwhals_df_to_v1( + df: Union[nw.DataFrame[Any], nw.LazyFrame[Any]], +) -> Union[nw.DataFrame[Any], nw.LazyFrame[Any]]: + """ + Downgrade a narwhals dataframe to the latest version. + """ + return nw1.from_native(df.to_native()) # type: ignore[no-any-return] + + def is_narwhals_lazyframe(df: Any) -> TypeIs[nw.LazyFrame[Any]]: """ Check if the given object is a narwhals lazyframe. Checks both v1 and main. """ - return isinstance(df, nw.LazyFrame) or isinstance(df, nw_main.LazyFrame) + return ( + isinstance(df, nw.LazyFrame) + or isinstance(df, nw_main.LazyFrame) + or isinstance(df, nw1.LazyFrame) + ) def is_narwhals_dataframe(df: Any) -> TypeIs[nw.DataFrame[Any]]: @@ -207,4 +246,8 @@ def is_narwhals_dataframe(df: Any) -> TypeIs[nw.DataFrame[Any]]: Checks both v1 and main. """ - return isinstance(df, nw.DataFrame) or isinstance(df, nw_main.DataFrame) + return ( + isinstance(df, nw.DataFrame) + or isinstance(df, nw_main.DataFrame) + or isinstance(df, nw1.DataFrame) + ) diff --git a/pixi.lock b/pixi.lock index 88252322768..ccebf09a515 100644 --- a/pixi.lock +++ b/pixi.lock @@ -112,7 +112,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/a3/48/f97f0920c20bc522c34f1eaec4c62d7a03c95c37da2dec858f65c8cb4325/loro-1.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/51/3f/afe76f8e2246ffbc867440cbcf90525264df0e658f8a5ca1f872b3f6192a/markdown-3.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d0/ef/c5422ce8af73928d194a6606f8ae36e93a52fd5e8df5abd366903a5ca8da/msgspec-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/22/58/a12a534269aa5ba9abdf73a9e0deb600297b71cbf7291bca212944663143/narwhals-1.39.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/50/3b/0e2c535c3e6970cfc5763b67f6cc31accaab35a7aa3e322fb6a12830450f/narwhals-2.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a7/d1/c54e608505776ce4e7966d03358ae635cfd51dff1da6ee421c090dbc797b/pymdown_extensions-10.15-py3-none-any.whl @@ -1236,10 +1236,10 @@ packages: - tomli ; python_full_version < '3.11' and extra == 'dev' - tomli-w ; extra == 'dev' requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/22/58/a12a534269aa5ba9abdf73a9e0deb600297b71cbf7291bca212944663143/narwhals-1.39.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/7d/81/1cf7a468ef2f8e88266d5c3e5ab026a045aa76150e6640bfe9c5450a8c11/narwhals-2.3.0-py3-none-any.whl name: narwhals - version: 1.39.0 - sha256: 50b6778f4b4249eb86c88dd17c3907dd004a16ec25b02d5effaf226a2bcfb940 + version: 2.3.0 + sha256: 5507b1a9a9c2b1c55a627fdf6cf722fef2e23498bd14362a332c8848a311c321 requires_dist: - cudf>=24.10.0 ; extra == 'cudf' - dask[dataframe]>=2024.8 ; extra == 'dask' @@ -1249,17 +1249,17 @@ packages: - pyarrow-hotfix ; extra == 'ibis' - rich ; extra == 'ibis' - modin ; extra == 'modin' - - pandas>=0.25.3 ; extra == 'pandas' - - polars>=0.20.3 ; extra == 'polars' - - pyarrow>=11.0.0 ; extra == 'pyarrow' + - pandas>=1.1.3 ; extra == 'pandas' + - polars>=0.20.4 ; extra == 'polars' + - pyarrow>=13.0.0 ; extra == 'pyarrow' - pyspark>=3.5.0 ; extra == 'pyspark' - pyspark[connect]>=3.5.0 ; extra == 'pyspark-connect' - - sqlframe>=3.22.0 ; extra == 'sqlframe' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/7d/81/1cf7a468ef2f8e88266d5c3e5ab026a045aa76150e6640bfe9c5450a8c11/narwhals-2.3.0-py3-none-any.whl + - sqlframe>=3.22.0,!=3.39.3 ; extra == 'sqlframe' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/50/3b/0e2c535c3e6970cfc5763b67f6cc31accaab35a7aa3e322fb6a12830450f/narwhals-2.6.0-py3-none-any.whl name: narwhals - version: 2.3.0 - sha256: 5507b1a9a9c2b1c55a627fdf6cf722fef2e23498bd14362a332c8848a311c321 + version: 2.6.0 + sha256: 3215ea42afb452c6c8527e79cefbe542b674aa08d7e2e99d46b2c9708870e0d4 requires_dist: - cudf>=24.10.0 ; extra == 'cudf' - dask[dataframe]>=2024.8 ; extra == 'dask' diff --git a/pyproject.toml b/pyproject.toml index 04a310b0b54..1d9771f2d8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ dependencies = [ # required dependency in Starlette for SessionMiddleware support "itsdangerous>=2.0.0", # for dataframe support - "narwhals>=1.34.1", + "narwhals>=2.0.0", # for packaging.version; not sure what the lower bound is. "packaging", "msgspec>=0.19.0", @@ -158,8 +158,7 @@ dependencies = [ "panel~=1.5.3", "polars~=1.9.0", "duckdb<=1.3.0", # 1.4.0 does not typecheck - # Types in 2.2.0 don't work great with mypy - "narwhals>=1.34.1, <2.2.0", + "narwhals>=2.0.0", "matplotlib>=3.8.0", "sqlglot[rs]>=26.2.0", "sqlalchemy>=2.0.40", @@ -169,7 +168,7 @@ dependencies = [ "loro>=1.5.0", "pandas-stubs>=1.5.3.230321", "pyiceberg>=0.9.0", - "litellm>=1.70.0", + "litellm==1.70.0", "python-dotenv>=1.0.1", "jupytext>=1.17.2", "types-Pillow~=10.2.0.20240520", @@ -268,7 +267,7 @@ extra-dependencies = [ "anthropic==0.52.2; python_version > '3.9'", "google-genai>=0.2.0", "boto3>=1.38.46", - "litellm>=1.70.0", + "litellm==1.70.0", # exporting as ipynb "nbformat>=5.10.4", "sympy>=1.13.3", diff --git a/tests/_data/test_series.py b/tests/_data/test_series.py index 4fee4fc5e70..b7204ce747b 100644 --- a/tests/_data/test_series.py +++ b/tests/_data/test_series.py @@ -20,7 +20,7 @@ and DependencyManager.polars.has() ) -# We exclude ibis because it neither eager_or_interchange_only +# We exclude ibis because it neither eager_only @pytest.mark.skipif(not HAS_DEPS, reason="optional dependencies not installed") diff --git a/tests/_plugins/ui/_impl/tables/test_narwhals.py b/tests/_plugins/ui/_impl/tables/test_narwhals.py index 5f805bd0302..4e11f6e7e79 100644 --- a/tests/_plugins/ui/_impl/tables/test_narwhals.py +++ b/tests/_plugins/ui/_impl/tables/test_narwhals.py @@ -7,7 +7,7 @@ from math import isnan from typing import TYPE_CHECKING, Any -import narwhals.stable.v1 as nw +import narwhals.stable.v2 as nw import pytest from marimo._data.models import BinValue, ColumnStats diff --git a/tests/_plugins/ui/_impl/tables/test_pandas_table.py b/tests/_plugins/ui/_impl/tables/test_pandas_table.py index c4ff64313f9..bc8bc497a61 100644 --- a/tests/_plugins/ui/_impl/tables/test_pandas_table.py +++ b/tests/_plugins/ui/_impl/tables/test_pandas_table.py @@ -7,7 +7,7 @@ from typing import Any from unittest.mock import Mock -import narwhals.stable.v1 as nw +import narwhals.stable.v2 as nw import pytest from marimo._data.models import ColumnStats @@ -1031,7 +1031,7 @@ def test_dataframe_with_all_null_column(self) -> None: summary = manager.get_stats("B") assert summary.nulls == 3 assert summary.total == 3 - assert summary.unique is None + assert summary.unique == 1 def test_dataframe_with_mixed_types(self) -> None: df = pd.DataFrame({"A": [1, "two", 3.0, True]}) diff --git a/tests/_plugins/ui/_impl/tables/test_polars_table.py b/tests/_plugins/ui/_impl/tables/test_polars_table.py index 966614a8f70..c581c59c0f5 100644 --- a/tests/_plugins/ui/_impl/tables/test_polars_table.py +++ b/tests/_plugins/ui/_impl/tables/test_polars_table.py @@ -6,7 +6,7 @@ from math import isnan from typing import Any -import narwhals.stable.v1 as nw +import narwhals.stable.v2 as nw import pytest from marimo._data.models import ColumnStats diff --git a/tests/_plugins/ui/_impl/tables/test_selection.py b/tests/_plugins/ui/_impl/tables/test_selection.py index c769793718a..8f5c24a2eeb 100644 --- a/tests/_plugins/ui/_impl/tables/test_selection.py +++ b/tests/_plugins/ui/_impl/tables/test_selection.py @@ -2,7 +2,7 @@ from typing import Any -import narwhals.stable.v1 as nw +import narwhals.stable.v2 as nw import pytest from marimo._dependencies.dependencies import DependencyManager diff --git a/tests/_plugins/ui/_impl/test_altair_chart.py b/tests/_plugins/ui/_impl/test_altair_chart.py index 24a40142cb8..45e34eaea18 100644 --- a/tests/_plugins/ui/_impl/test_altair_chart.py +++ b/tests/_plugins/ui/_impl/test_altair_chart.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, Any from unittest.mock import Mock -import narwhals.stable.v1 as nw +import narwhals.stable.v2 as nw import pytest from marimo._dependencies.dependencies import DependencyManager @@ -665,7 +665,7 @@ def test_parse_spec_pandas() -> None: chart = alt.Chart(data).mark_point().encode(x="values:Q") spec = _parse_spec(chart) # Replace data.url with a placeholder - spec["data"]["url"] = "_placeholder_" + spec["data"] = {"url": "_placeholder_", "format": spec["data"]["format"]} snapshot("parse_spec_pandas.txt", json.dumps(spec, indent=2)) @@ -673,11 +673,11 @@ def test_parse_spec_pandas() -> None: def test_parse_spec_narwhal() -> None: import altair as alt - data = nw.from_native(pd.DataFrame({"values": [1, 2, 3]})) + data = pd.DataFrame({"values": [1, 2, 3]}) chart = alt.Chart(data).mark_point().encode(x="values:Q") spec = _parse_spec(chart) # Replace data.url with a placeholder - spec["data"]["url"] = "_placeholder_" + spec["data"] = {"url": "_placeholder_", "format": spec["data"]["format"]} snapshot("parse_spec_narwhal.txt", json.dumps(spec, indent=2)) @@ -690,7 +690,7 @@ def test_parse_spec_polars() -> None: chart = alt.Chart(data).mark_point().encode(x="values:Q") spec = _parse_spec(chart) # Replace data.url with a placeholder - spec["data"]["url"] = "_placeholder_" + spec["data"] = {"url": "_placeholder_", "format": spec["data"]["format"]} snapshot("parse_spec_polars.txt", json.dumps(spec, indent=2)) diff --git a/tests/_plugins/ui/_impl/test_data_editor.py b/tests/_plugins/ui/_impl/test_data_editor.py index 4cdf17177a9..be64d886b9c 100644 --- a/tests/_plugins/ui/_impl/test_data_editor.py +++ b/tests/_plugins/ui/_impl/test_data_editor.py @@ -5,7 +5,7 @@ from copy import deepcopy from typing import Any -import narwhals.stable.v1 as nw +import narwhals.stable.v2 as nw import pytest from marimo._dependencies.dependencies import DependencyManager diff --git a/tests/_plugins/ui/_impl/utils/test_dataframe_utils.py b/tests/_plugins/ui/_impl/utils/test_dataframe_utils.py index 910b1c495d1..d1eae71bd5a 100644 --- a/tests/_plugins/ui/_impl/utils/test_dataframe_utils.py +++ b/tests/_plugins/ui/_impl/utils/test_dataframe_utils.py @@ -69,7 +69,7 @@ def test_get_row_headers_list() -> None: reason="optional dependencies not installed", ) def test_get_table_manager() -> None: - import narwhals.stable.v1 as nw + import narwhals.stable.v2 as nw import pandas as pd import polars as pl import pyarrow as pa diff --git a/tests/_server/api/endpoints/test_editing.py b/tests/_server/api/endpoints/test_editing.py index a4b53e9f891..f15b07fac36 100644 --- a/tests/_server/api/endpoints/test_editing.py +++ b/tests/_server/api/endpoints/test_editing.py @@ -3,6 +3,8 @@ from typing import TYPE_CHECKING +import pytest + from tests._server.mocks import token_header, with_session if TYPE_CHECKING: @@ -45,6 +47,7 @@ def test_delete_cell(client: TestClient) -> None: assert "success" in response.json() +@pytest.mark.flaky(reruns=5) @with_session(SESSION_ID) def test_format_cell(client: TestClient) -> None: response = client.post( diff --git a/tests/_utils/test_narwhals_utils.py b/tests/_utils/test_narwhals_utils.py index 66e9906e6d9..f7861662cc7 100644 --- a/tests/_utils/test_narwhals_utils.py +++ b/tests/_utils/test_narwhals_utils.py @@ -2,12 +2,12 @@ from typing import TYPE_CHECKING, Any -import narwhals.stable.v1 as nw +import narwhals.stable.v2 as nw import pytest from marimo._dependencies.dependencies import DependencyManager from marimo._utils.narwhals_utils import ( - assert_narwhals_dataframe, + assert_narwhals_dataframe_or_lazyframe, assert_narwhals_series, can_narwhalify, can_narwhalify_lazyframe, @@ -54,10 +54,10 @@ def test_empty_df(df: IntoDataFrame) -> None: @pytest.mark.skipif(not HAS_DEPS, reason="optional dependencies not installed") def test_assert_narwhals_dataframe(df: IntoDataFrame) -> None: df_wrapped = nw.from_native(df) - assert_narwhals_dataframe(df_wrapped) # Should not raise + assert_narwhals_dataframe_or_lazyframe(df_wrapped) # Should not raise with pytest.raises(ValueError, match="Unsupported dataframe type"): - assert_narwhals_dataframe([]) + assert_narwhals_dataframe_or_lazyframe([]) @pytest.mark.skipif(not HAS_DEPS, reason="optional dependencies not installed")