Repository: narwhals-dev/narwhals
Pull request #2681 (2025-06-15 13:43:48)

depr: Deprecate `nw.get_level` from main namespace
<!--
# Thanks for contributing a pull request! 
## Please make sure you see our contribution guidelines: https://github.com/narwhals-dev/narwhals/blob/main/CONTRIBUTING.md
-->

## What type of PR is this? (check all applicable)

- [ ] 💾 Refactor
- [ ] ✨ Feature
- [ ] 🐛 Bug Fix
- [ ] 🔧 Optimization
- [ ] 📝 Documentation
- [ ] ✅ Test
- [ ] 🐳 Other

## Related issues

- Related issue #\<issue number\>
- Closes #\<issue number\>

## Checklist

- [ ] Code follows style guide (ruff)
- [ ] Tests added
- [ ] Documented the changes

## If you have comments or can explain your changes, please do so below



Patch:
diff --git a/docs/backcompat.md b/docs/backcompat.md
index 911b852a..9ba12389 100644
--- a/docs/backcompat.md
+++ b/docs/backcompat.md
@@ -111,6 +111,10 @@ Which should you use? In general we recommend:
 
 ## `main` vs `stable.v1`
 
+- Since Narwhals 1.43:
+
+    - `nw.get_level` is deprecated in the main Narwhals namespace.
+
 - 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 5e3bb38e..a2a5c8e9 100644
--- a/narwhals/functions.py
+++ b/narwhals/functions.py
@@ -14,6 +14,7 @@ from narwhals._expression_parsing import (
     extract_compliant,
     is_scalar_like,
 )
+from narwhals._typing_compat import deprecated
 from narwhals._utils import (
     Implementation,
     Version,
@@ -22,6 +23,7 @@ from narwhals._utils import (
     is_compliant_expr,
     is_eager_allowed,
     is_sequence_but_not_str,
+    issue_deprecation_warning,
     parse_version,
     supports_arrow_c_stream,
     validate_laziness,
@@ -594,11 +596,21 @@ def show_versions() -> None:
         print(f"{k:>13}: {stat}")  # noqa: T201
 
 
+@deprecated(
+    "`get_level` is deprecated, as Narwhals no longer supports the Dataframe Interchange Protocol."
+)
 def get_level(
     obj: DataFrame[Any] | LazyFrame[Any] | Series[IntoSeriesT],
 ) -> Literal["full", "lazy", "interchange"]:
     """Level of support Narwhals has for current object.
 
+    Warning:
+        `get_level` is deprecated and will be removed in a future version.
+        "DuckDB and Ibis now have full lazy support in Narwhals, and passing
+        them to `nw.from_native` returns `nw.LazyFrame`.
+        Note: this will remain available in `narwhals.stable.v1`.
+        See [stable api](../backcompat.md/) for more information.
+
     Arguments:
         obj: Dataframe or Series.
 
@@ -608,8 +620,13 @@ def get_level(
             - 'full': full Narwhals API support
             - 'lazy': only lazy operations are supported. This excludes anything
               which involves iterating over rows in Python.
-            - 'interchange': only metadata operations are supported (`df.schema`)
     """
+    issue_deprecation_warning(
+        "`get_level` is deprecated, as Narwhals no longer supports the Dataframe Interchange Protocol.\n"
+        "DuckDB and Ibis now have full lazy support in Narwhals, and passing them to `nw.from_native` \n"
+        "returns `nw.LazyFrame`.",
+        "1.43",
+    )
     return obj._level
 
 
diff --git a/narwhals/stable/v1/__init__.py b/narwhals/stable/v1/__init__.py
index a9ca2a5d..d49d3abd 100644
--- a/narwhals/stable/v1/__init__.py
+++ b/narwhals/stable/v1/__init__.py
@@ -26,7 +26,7 @@ from narwhals.dataframe import DataFrame as NwDataFrame, LazyFrame as NwLazyFram
 from narwhals.dependencies import get_polars
 from narwhals.exceptions import InvalidIntoExprError
 from narwhals.expr import Expr as NwExpr
-from narwhals.functions import _new_series_impl, concat, get_level, show_versions
+from narwhals.functions import _new_series_impl, concat, show_versions
 from narwhals.schema import Schema as NwSchema
 from narwhals.series import Series as NwSeries
 from narwhals.stable.v1 import dtypes
@@ -1413,6 +1413,25 @@ def concat_str(
     )
 
 
+def get_level(
+    obj: DataFrame[Any] | LazyFrame[Any] | Series[IntoSeriesT],
+) -> Literal["full", "lazy", "interchange"]:
+    """Level of support Narwhals has for current object.
+
+    Arguments:
+        obj: Dataframe or Series.
+
+    Returns:
+        This can be one of
+
+            - 'full': full Narwhals API support
+            - 'lazy': only lazy operations are supported. This excludes anything
+              which involves iterating over rows in Python.
+            - 'interchange': only metadata operations are supported (`df.schema`)
+    """
+    return obj._level
+
+
 class When(nw_f.When):
     @classmethod
     def from_when(cls, when: nw_f.When) -> When:


Test patch:
diff --git a/tests/frame/interchange_schema_test.py b/tests/frame/interchange_schema_test.py
index cf5ec1d4..9913c3bf 100644
--- a/tests/frame/interchange_schema_test.py
+++ b/tests/frame/interchange_schema_test.py
@@ -243,14 +243,3 @@ def test_invalid() -> None:
         nw_v1.from_native(df, eager_only=True)
     with pytest.raises(ValueError, match="Invalid parameter combination"):
         nw_v1.from_native(df, eager_only=True, eager_or_interchange_only=True)  # type: ignore[call-overload]
-
-
-def test_get_level() -> None:
-    df = pl.DataFrame({"a": [1, 2, 3]})
-    assert nw_v1.get_level(nw_v1.from_native(df)) == "full"
-    assert (
-        nw_v1.get_level(
-            nw_v1.from_native(df.__dataframe__(), eager_or_interchange_only=True)
-        )
-        == "interchange"
-    )
diff --git a/tests/stable_api_test.py b/tests/stable_api_test.py
index cc77a1d7..6de02b2d 100644
--- a/tests/stable_api_test.py
+++ b/tests/stable_api_test.py
@@ -92,7 +92,7 @@ def test_stable_api_docstrings() -> None:
     for item in main_namespace_api:
         if (doc := getdoc(getattr(nw, item))) is None:
             continue
-        if item in {"from_native", "narwhalify"}:
+        if item in {"from_native", "narwhalify", "get_level"}:
             # `eager_or_interchange` param was removed from main namespace,
             # but is still present in v1 docstring.
             continue
diff --git a/tests/v1_test.py b/tests/v1_test.py
index 718f3861..7b746db7 100644
--- a/tests/v1_test.py
+++ b/tests/v1_test.py
@@ -343,3 +343,19 @@ def test_is_native_series(is_native_series: Callable[[Any], Any]) -> None:
     data = {"a": [1, 2]}
     ser = nw.from_native(pd.DataFrame(data))["a"]
     assert not is_native_series(ser)
+
+
+def test_get_level() -> None:
+    pytest.importorskip("polars")
+    import polars as pl
+
+    df = pl.DataFrame({"a": [1, 2, 3]})
+    with pytest.deprecated_call():
+        assert nw.get_level(nw.from_native(df)) == "full"
+    assert nw_v1.get_level(nw_v1.from_native(df)) == "full"
+    assert (
+        nw_v1.get_level(
+            nw_v1.from_native(df.__dataframe__(), eager_or_interchange_only=True)
+        )
+        == "interchange"
+    )