diff --git a/.github/workflows/downstream_tests.yml b/.github/workflows/downstream_tests.yml index c608413d8b..cc6e0181fb 100644 --- a/.github/workflows/downstream_tests.yml +++ b/.github/workflows/downstream_tests.yml @@ -284,7 +284,8 @@ jobs: - name: Run pytest run: | cd tubular - pytest tests --config-file=pyproject.toml + # remove `-W ignore` once they address `new_series` deprecation + pytest tests --config-file=pyproject.toml -W ignore plotly: strategy: diff --git a/docs/backcompat.md b/docs/backcompat.md index 911b852a03..ce4de8fdb5 100644 --- a/docs/backcompat.md +++ b/docs/backcompat.md @@ -111,6 +111,12 @@ Which should you use? In general we recommend: ## `main` vs `stable.v1` +- Since Narwhals 1.42.0: + + - The DataFrame Interchange Protocol is no longer supported in the + main Narwhals namespace. + - `nw.new_series` is deprecated in favour of `nw.Series.from_iterable`. + - Since Narwhals 1.35: - pandas' ordered categoricals get mapped to `nw.Enum` instead of `nw.Categorical`. diff --git a/narwhals/functions.py b/narwhals/functions.py index 7a12e4e5e5..3b53aeb212 100644 --- a/narwhals/functions.py +++ b/narwhals/functions.py @@ -31,6 +31,7 @@ is_compliant_expr, is_eager_allowed, is_sequence_but_not_str, + issue_deprecation_warning, parse_version, supports_arrow_c_stream, validate_laziness, @@ -179,6 +180,12 @@ def new_series( ) -> Series[Any]: """Instantiate Narwhals Series from iterable (e.g. list or array). + Warning: + `new_series` is deprecated and will be removed in a future version. + Please use `Series.from_iterable` instead. + Note: this will remain available in `narwhals.stable.v1`. + See [stable api](../backcompat.md/) for more information. + Arguments: name: Name of resulting Series. values: Values of make Series from. @@ -205,7 +212,7 @@ def new_series( Examples: >>> import pandas as pd - >>> import narwhals as nw + >>> import narwhals.stable.v1 as nw >>> >>> values = [4, 1, 2, 3] >>> nw.new_series(name="a", values=values, dtype=nw.Int32, backend=pd) @@ -219,6 +226,14 @@ def new_series( |Name: a, dtype: int32| └─────────────────────┘ """ + issue_deprecation_warning( + "`new_series` is deprecated in the main `narwhals` namespace.\n\n" + "You may want to:\n" + " - Use `Series.from_iterable` instead.\n" + " - Use `narwhals.stable.v1`, where it is still supported.\n" + " - See https://narwhals-dev.github.io/narwhals/backcompat\n", + "1.42.0", + ) backend = cast("ModuleType | Implementation | str", backend) return _new_series_impl(name, values, dtype, backend=backend) diff --git a/narwhals/series.py b/narwhals/series.py index 77029510ad..13bf083606 100644 --- a/narwhals/series.py +++ b/narwhals/series.py @@ -16,6 +16,7 @@ from narwhals.dependencies import is_numpy_scalar from narwhals.dtypes import _validate_dtype from narwhals.exceptions import ComputeError +from narwhals.functions import _new_series_impl from narwhals.series_cat import SeriesCatNamespace from narwhals.series_dt import SeriesDateTimeNamespace from narwhals.series_list import SeriesListNamespace @@ -75,7 +76,9 @@ class Series(Generic[IntoSeriesT]): - If the object is a generic sequence (e.g. a list or a tuple of values), you can create a series via [`narwhals.new_series`][], e.g.: ```py - narwhals.new_series(name="price", values=[10.5, 9.4, 1.2], backend="pandas") + import narwhals as nw + + nw.Series.from_iterable(name="price", values=[10.5, 9.4], backend="pandas") ``` """ @@ -97,6 +100,54 @@ def __init__( msg = f"Expected Polars Series or an object which implements `__narwhals_series__`, got: {type(series)}." raise AssertionError(msg) + @classmethod + def from_iterable( + cls, + name: str, + values: Any, + dtype: DType | type[DType] | None = None, + *, + backend: ModuleType | Implementation | str, + ) -> Series[Any]: + """Instantiate Narwhals Series from iterable (e.g. list or array). + + Arguments: + name: Name of resulting Series. + values: Values of make Series from. + dtype: (Narwhals) dtype. If not provided, the native library + may auto-infer it from `values`. + backend: specifies which eager backend instantiate to. + + `backend` can be specified in various ways + + - As `Implementation.` with `BACKEND` being `PANDAS`, `PYARROW`, + `POLARS`, `MODIN` or `CUDF`. + - As a string: `"pandas"`, `"pyarrow"`, `"polars"`, `"modin"` or `"cudf"`. + - Directly as a module `pandas`, `pyarrow`, `polars`, `modin` or `cudf`. + + Returns: + A new Series + + Examples: + >>> import pandas as pd + >>> import narwhals as nw + >>> + >>> values = [4, 1, 2, 3] + >>> nw.Series.from_iterable( + ... name="a", values=values, dtype=nw.Int32, backend=pd + ... ) + ┌─────────────────────┐ + | Narwhals Series | + |---------------------| + |0 4 | + |1 1 | + |2 2 | + |3 3 | + |Name: a, dtype: int32| + └─────────────────────┘ + """ + return _new_series_impl(name, values, dtype, backend=backend) + @property def implementation(self) -> Implementation: """Return implementation of native Series. diff --git a/narwhals/stable/v1/__init__.py b/narwhals/stable/v1/__init__.py index 0d7b39588f..d2d173c5eb 100644 --- a/narwhals/stable/v1/__init__.py +++ b/narwhals/stable/v1/__init__.py @@ -1474,6 +1474,12 @@ def new_series( ) -> Series[Any]: """Instantiate Narwhals Series from iterable (e.g. list or array). + Warning: + `new_series` is deprecated and will be removed in a future version. + Please use `Series.from_iterable` instead. + Note: this will remain available in `narwhals.stable.v1`. + See [stable api](../backcompat.md/) for more information. + Arguments: name: Name of resulting Series. values: Values of make Series from. diff --git a/tests/new_series_test.py b/tests/new_series_test.py index f37e419688..68bce4c91a 100644 --- a/tests/new_series_test.py +++ b/tests/new_series_test.py @@ -10,17 +10,26 @@ def test_new_series(constructor_eager: ConstructorEager) -> None: s = nw.from_native(constructor_eager({"a": [1, 2, 3]}), eager_only=True)["a"] - result = nw.new_series("b", [4, 1, 2], backend=nw.get_native_namespace(s)) + with pytest.deprecated_call(): + result = nw.new_series("b", [4, 1, 2], backend=nw.get_native_namespace(s)) expected = {"b": [4, 1, 2]} # all supported libraries auto-infer this to be int64, we can always special-case # something different if necessary assert result.dtype == nw.Int64 assert_equal_data(result.to_frame(), expected) - result = nw.new_series("b", [4, 1, 2], nw.Int32, backend=nw.get_native_namespace(s)) + with pytest.deprecated_call(): + result = nw.new_series( + "b", [4, 1, 2], nw.Int32, backend=nw.get_native_namespace(s) + ) expected = {"b": [4, 1, 2]} assert result.dtype == nw.Int32 assert_equal_data(result.to_frame(), expected) + result = nw.Series.from_iterable( + "b", [4, 1, 2], nw.Int32, backend=nw.get_native_namespace(s) + ) + assert result.dtype == nw.Int32 + assert_equal_data(result.to_frame(), expected) def test_new_series_v1(constructor_eager: ConstructorEager) -> None: @@ -46,4 +55,4 @@ def test_new_series_dask() -> None: df = nw.from_native(dd.from_pandas(pd.DataFrame({"a": [1, 2, 3]}))) with pytest.raises(ValueError, match="lazy-only"): - nw.new_series("a", [1, 2, 3], backend=nw.get_native_namespace(df)) + nw_v1.new_series("a", [1, 2, 3], backend=nw.get_native_namespace(df)) diff --git a/tests/v1_test.py b/tests/v1_test.py index 858bc9f0f2..f0ec2170fb 100644 --- a/tests/v1_test.py +++ b/tests/v1_test.py @@ -78,7 +78,9 @@ def test_constructors() -> None: pytest.importorskip("pyarrow") if PANDAS_VERSION < (2, 2): pytest.skip() - assert nw_v1.new_series("a", [1, 2, 3], backend="pandas").to_list() == [1, 2, 3] + result_s = nw_v1.new_series("a", [1, 2, 3], backend="pandas") + assert result_s.to_list() == [1, 2, 3] + assert isinstance(result_s, nw_v1.Series) arr: np.ndarray[tuple[int, int], Any] = np.array([[1, 2], [3, 4]]) # pyright: ignore[reportAssignmentType] result = nw_v1.from_numpy(arr, schema=["a", "b"], backend="pandas") assert_equal_data(result, {"a": [1, 3], "b": [2, 4]})