Repository: narwhals-dev/narwhals
Pull request #1391 (2024-11-17 08:25:33)

feat: make exceptions module public
<!--
# 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/api-reference/exceptions.md b/docs/api-reference/exceptions.md
new file mode 100644
index 00000000..b6841597
--- /dev/null
+++ b/docs/api-reference/exceptions.md
@@ -0,0 +1,11 @@
+# `narwhals.exceptions`
+
+::: narwhals.exceptions
+    handler: python
+    options:
+      members:
+        - ColumnNotFoundError
+        - InvalidIntoExprError
+        - InvalidOperationError
+      show_source: false
+      show_bases: false
diff --git a/docs/api-reference/index.md b/docs/api-reference/index.md
index b4cbc78f..8a8195fa 100644
--- a/docs/api-reference/index.md
+++ b/docs/api-reference/index.md
@@ -16,5 +16,6 @@
 - [narwhals.Series.str](series_str.md)
 - [narwhals.dependencies](dependencies.md)
 - [narwhals.dtypes](dtypes.md)
+- [narwhals.exceptions](exceptions.md)
 - [narwhals.selectors](selectors.md)
 - [narwhals.typing](typing.md)
diff --git a/mkdocs.yml b/mkdocs.yml
index a4f4fe1e..4c336965 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -44,6 +44,7 @@ nav:
     - api-reference/series_str.md
     - api-reference/dependencies.md
     - api-reference/dtypes.md
+    - api-reference/exceptions.md
     - api-reference/selectors.md
     - api-reference/typing.md
   - This: this.md
diff --git a/narwhals/__init__.py b/narwhals/__init__.py
index e69db9da..c990f01f 100644
--- a/narwhals/__init__.py
+++ b/narwhals/__init__.py
@@ -1,6 +1,7 @@
 from __future__ import annotations
 
 from narwhals import dependencies
+from narwhals import exceptions
 from narwhals import selectors
 from narwhals import stable
 from narwhals.dataframe import DataFrame
@@ -71,72 +72,73 @@ from narwhals.utils import maybe_set_index
 __version__ = "1.13.5"
 
 __all__ = [
-    "dependencies",
-    "selectors",
+    "Array",
+    "Boolean",
+    "Categorical",
+    "DataFrame",
+    "Date",
+    "Datetime",
+    "Duration",
+    "Enum",
+    "Expr",
+    "Field",
+    "Float32",
+    "Float64",
+    "Int16",
+    "Int32",
+    "Int64",
+    "Int8",
+    "LazyFrame",
+    "List",
+    "Object",
+    "Schema",
+    "Series",
+    "String",
+    "Struct",
+    "UInt16",
+    "UInt32",
+    "UInt64",
+    "UInt8",
+    "Unknown",
+    "all",
+    "all_horizontal",
+    "any_horizontal",
+    "col",
     "concat",
-    "from_dict",
+    "concat_str",
+    "dependencies",
+    "exceptions",
     "from_arrow",
+    "from_dict",
+    "from_dict",
+    "from_native",
     "generate_temporary_column_name",
     "get_level",
-    "new_series",
-    "to_native",
-    "from_native",
+    "get_native_namespace",
     "is_ordered_categorical",
+    "len",
+    "lit",
+    "max",
+    "max_horizontal",
     "maybe_align_index",
     "maybe_convert_dtypes",
     "maybe_get_index",
     "maybe_reset_index",
     "maybe_set_index",
-    "get_native_namespace",
-    "to_py_scalar",
-    "all",
-    "all_horizontal",
-    "any_horizontal",
-    "col",
-    "concat_str",
-    "len",
-    "lit",
-    "max",
-    "max_horizontal",
     "mean",
     "mean_horizontal",
     "median",
     "min",
     "min_horizontal",
+    "narwhalify",
+    "new_series",
     "nth",
+    "selectors",
+    "show_versions",
+    "stable",
     "sum",
     "sum_horizontal",
+    "to_native",
+    "to_py_scalar",
     "when",
-    "DataFrame",
-    "LazyFrame",
-    "Series",
-    "Expr",
-    "Int64",
-    "Int32",
-    "Int16",
-    "Int8",
-    "UInt64",
-    "UInt32",
-    "UInt16",
-    "UInt8",
-    "Float64",
-    "Float32",
-    "Boolean",
-    "Object",
-    "Unknown",
-    "Categorical",
-    "Enum",
-    "String",
-    "Datetime",
-    "Duration",
-    "Field",
-    "Struct",
-    "Array",
-    "List",
-    "Date",
-    "narwhalify",
-    "show_versions",
-    "stable",
-    "Schema",
-    "from_dict",
 ]
diff --git a/narwhals/_arrow/expr.py b/narwhals/_arrow/expr.py
index 6d5adc6a..772505ee 100644
--- a/narwhals/_arrow/expr.py
+++ b/narwhals/_arrow/expr.py
@@ -6,11 +6,11 @@ from typing import Callable
 from typing import Literal
 from typing import Sequence
 
-from narwhals._exceptions import ColumnNotFoundError
 from narwhals._expression_parsing import reuse_series_implementation
 from narwhals._expression_parsing import reuse_series_namespace_implementation
 from narwhals.dependencies import get_numpy
 from narwhals.dependencies import is_numpy_array
+from narwhals.exceptions import ColumnNotFoundError
 from narwhals.utils import Implementation
 
 if TYPE_CHECKING:
diff --git a/narwhals/_arrow/series.py b/narwhals/_arrow/series.py
index 19c424a7..75a3cabd 100644
--- a/narwhals/_arrow/series.py
+++ b/narwhals/_arrow/series.py
@@ -264,7 +264,7 @@ class ArrowSeries:
     def median(self) -> int:
         import pyarrow.compute as pc  # ignore-banned-import()
 
-        from narwhals._exceptions import InvalidOperationError
+        from narwhals.exceptions import InvalidOperationError
 
         if not self.dtype.is_numeric():
             msg = "`median` operation not supported for non-numeric input type."
diff --git a/narwhals/_dask/expr.py b/narwhals/_dask/expr.py
index 2b50a962..142349a8 100644
--- a/narwhals/_dask/expr.py
+++ b/narwhals/_dask/expr.py
@@ -11,10 +11,10 @@ from typing import Sequence
 from narwhals._dask.utils import add_row_index
 from narwhals._dask.utils import maybe_evaluate
 from narwhals._dask.utils import narwhals_to_native_dtype
-from narwhals._exceptions import ColumnNotFoundError
 from narwhals._pandas_like.utils import calculate_timestamp_date
 from narwhals._pandas_like.utils import calculate_timestamp_datetime
 from narwhals._pandas_like.utils import native_to_narwhals_dtype
+from narwhals.exceptions import ColumnNotFoundError
 from narwhals.utils import Implementation
 from narwhals.utils import generate_temporary_column_name
 
@@ -400,15 +400,14 @@ class DaskExpr:
         )
 
     def median(self) -> Self:
-        from dask_expr._shuffle import _is_numeric_cast_type
+        from narwhals.exceptions import InvalidOperationError
 
-        from narwhals._exceptions import InvalidOperationError
-
-        def func(_input: dask_expr.Series) -> dask_expr.Series:
-            if not _is_numeric_cast_type(_input.dtype):
+        def func(s: dask_expr.Series) -> dask_expr.Series:
+            dtype = native_to_narwhals_dtype(s, self._dtypes, Implementation.DASK)
+            if not dtype.is_numeric():
                 msg = "`median` operation not supported for non-numeric input type."
                 raise InvalidOperationError(msg)
-            return _input.median_approximate()
+            return s.median_approximate()
 
         return self._from_call(func, "median", returns_scalar=True)
 
diff --git a/narwhals/_dask/utils.py b/narwhals/_dask/utils.py
index f822245b..ef9e7439 100644
--- a/narwhals/_dask/utils.py
+++ b/narwhals/_dask/utils.py
@@ -3,10 +3,10 @@ from __future__ import annotations
 from typing import TYPE_CHECKING
 from typing import Any
 
-from narwhals._exceptions import InvalidIntoExprError
 from narwhals.dependencies import get_pandas
 from narwhals.dependencies import get_polars
 from narwhals.dependencies import get_pyarrow
+from narwhals.exceptions import InvalidIntoExprError
 from narwhals.utils import isinstance_or_issubclass
 from narwhals.utils import parse_version
 
diff --git a/narwhals/_expression_parsing.py b/narwhals/_expression_parsing.py
index 8c2914ea..05056376 100644
--- a/narwhals/_expression_parsing.py
+++ b/narwhals/_expression_parsing.py
@@ -12,8 +12,8 @@ from typing import Union
 from typing import cast
 from typing import overload
 
-from narwhals._exceptions import InvalidIntoExprError
 from narwhals.dependencies import is_numpy_array
+from narwhals.exceptions import InvalidIntoExprError
 
 if TYPE_CHECKING:
     from narwhals._arrow.dataframe import ArrowDataFrame
diff --git a/narwhals/_pandas_like/expr.py b/narwhals/_pandas_like/expr.py
index 323517db..80facd57 100644
--- a/narwhals/_pandas_like/expr.py
+++ b/narwhals/_pandas_like/expr.py
@@ -6,12 +6,12 @@ from typing import Callable
 from typing import Literal
 from typing import Sequence
 
-from narwhals._exceptions import ColumnNotFoundError
 from narwhals._expression_parsing import reuse_series_implementation
 from narwhals._expression_parsing import reuse_series_namespace_implementation
 from narwhals._pandas_like.series import PandasLikeSeries
 from narwhals.dependencies import get_numpy
 from narwhals.dependencies import is_numpy_array
+from narwhals.exceptions import ColumnNotFoundError
 
 if TYPE_CHECKING:
     from typing_extensions import Self
diff --git a/narwhals/_pandas_like/series.py b/narwhals/_pandas_like/series.py
index 91fd4361..98caf921 100644
--- a/narwhals/_pandas_like/series.py
+++ b/narwhals/_pandas_like/series.py
@@ -427,7 +427,7 @@ class PandasLikeSeries:
         return ser.mean()
 
     def median(self) -> Any:
-        from narwhals._exceptions import InvalidOperationError
+        from narwhals.exceptions import InvalidOperationError
 
         if not self.dtype.is_numeric():
             msg = "`median` operation not supported for non-numeric input type."
diff --git a/narwhals/_pandas_like/utils.py b/narwhals/_pandas_like/utils.py
index c718850c..ba5eece4 100644
--- a/narwhals/_pandas_like/utils.py
+++ b/narwhals/_pandas_like/utils.py
@@ -11,8 +11,8 @@ from typing import TypeVar
 from narwhals._arrow.utils import (
     native_to_narwhals_dtype as arrow_native_to_narwhals_dtype,
 )
-from narwhals._exceptions import ColumnNotFoundError
 from narwhals.dependencies import get_polars
+from narwhals.exceptions import ColumnNotFoundError
 from narwhals.utils import Implementation
 from narwhals.utils import isinstance_or_issubclass
 
diff --git a/narwhals/_polars/dataframe.py b/narwhals/_polars/dataframe.py
index 9e76dc99..453ada3c 100644
--- a/narwhals/_polars/dataframe.py
+++ b/narwhals/_polars/dataframe.py
@@ -5,12 +5,12 @@ from typing import Any
 from typing import Literal
 from typing import Sequence
 
-from narwhals._exceptions import ColumnNotFoundError
-from narwhals._exceptions import InvalidIntoExprError
 from narwhals._polars.namespace import PolarsNamespace
 from narwhals._polars.utils import convert_str_slice_to_int_slice
 from narwhals._polars.utils import extract_args_kwargs
 from narwhals._polars.utils import native_to_narwhals_dtype
+from narwhals.exceptions import ColumnNotFoundError
+from narwhals.exceptions import InvalidIntoExprError
 from narwhals.utils import Implementation
 from narwhals.utils import is_sequence_but_not_str
 from narwhals.utils import parse_columns_to_drop
diff --git a/narwhals/_polars/series.py b/narwhals/_polars/series.py
index 5f6a940c..9013d6ab 100644
--- a/narwhals/_polars/series.py
+++ b/narwhals/_polars/series.py
@@ -193,7 +193,7 @@ class PolarsSeries:
         return self._from_native_series(self._native_series.__invert__())
 
     def median(self) -> Any:
-        from narwhals._exceptions import InvalidOperationError
+        from narwhals.exceptions import InvalidOperationError
 
         if not self.dtype.is_numeric():
             msg = "`median` operation not supported for non-numeric input type."
diff --git a/narwhals/_exceptions.py b/narwhals/exceptions.py
similarity index 88%
rename from narwhals/_exceptions.py
rename to narwhals/exceptions.py
index 52938977..6991ea75 100644
--- a/narwhals/_exceptions.py
+++ b/narwhals/exceptions.py
@@ -18,6 +18,8 @@ class FormattedKeyError(KeyError):
 
 
 class ColumnNotFoundError(FormattedKeyError):
+    """Exception raised when column name isn't present."""
+
     def __init__(self, message: str) -> None:
         self.message = message
         super().__init__(self.message)
@@ -33,10 +35,13 @@ class ColumnNotFoundError(FormattedKeyError):
         return ColumnNotFoundError(message)
 
 
-class InvalidOperationError(Exception): ...
+class InvalidOperationError(Exception):
+    """Exception raised during invalid operations."""
 
 
 class InvalidIntoExprError(TypeError):
+    """Exception raised when object can't be converted to expression."""
+
     def __init__(self, message: str) -> None:
         self.message = message
         super().__init__(self.message)
diff --git a/narwhals/stable/v1/__init__.py b/narwhals/stable/v1/__init__.py
index e53eb2f9..874337cf 100644
--- a/narwhals/stable/v1/__init__.py
+++ b/narwhals/stable/v1/__init__.py
@@ -12,6 +12,7 @@ from typing import overload
 
 import narwhals as nw
 from narwhals import dependencies
+from narwhals import exceptions
 from narwhals import selectors
 from narwhals.dataframe import DataFrame as NwDataFrame
 from narwhals.dataframe import LazyFrame as NwLazyFrame
@@ -2536,70 +2537,71 @@ def from_dict(
 
 
 __all__ = [
-    "selectors",
-    "concat",
-    "dependencies",
-    "to_native",
-    "from_native",
-    "to_py_scalar",
-    "is_ordered_categorical",
-    "maybe_align_index",
-    "maybe_convert_dtypes",
-    "maybe_get_index",
-    "maybe_reset_index",
-    "maybe_set_index",
-    "generate_temporary_column_name",
-    "get_native_namespace",
-    "get_level",
+    "Array",
+    "Boolean",
+    "Categorical",
+    "DataFrame",
+    "Date",
+    "Datetime",
+    "Duration",
+    "Enum",
+    "Expr",
+    "Field",
+    "Float32",
+    "Float64",
+    "Int16",
+    "Int32",
+    "Int64",
+    "Int8",
+    "LazyFrame",
+    "List",
+    "Object",
+    "Schema",
+    "Series",
+    "String",
+    "Struct",
+    "UInt16",
+    "UInt32",
+    "UInt64",
+    "UInt8",
+    "Unknown",
     "all",
     "all_horizontal",
     "any_horizontal",
     "col",
+    "concat",
     "concat_str",
-    "nth",
+    "dependencies",
+    "exceptions",
+    "from_arrow",
+    "from_dict",
+    "from_native",
+    "generate_temporary_column_name",
+    "get_level",
+    "get_native_namespace",
+    "is_ordered_categorical",
     "len",
     "lit",
     "max",
     "max_horizontal",
+    "maybe_align_index",
+    "maybe_convert_dtypes",
+    "maybe_get_index",
+    "maybe_reset_index",
+    "maybe_set_index",
     "mean",
     "mean_horizontal",
     "median",
     "min",
     "min_horizontal",
+    "narwhalify",
+    "new_series",
+    "nth",
+    "selectors",
+    "show_versions",
     "sum",
     "sum_horizontal",
+    "to_native",
+    "to_py_scalar",
     "when",
-    "DataFrame",
-    "LazyFrame",
-    "Series",
-    "Expr",
-    "Int64",
-    "Int32",
-    "Int16",
-    "Int8",
-    "UInt64",
-    "UInt32",
-    "UInt16",
-    "UInt8",
-    "Float64",
-    "Float32",
-    "Boolean",
-    "Object",
-    "Unknown",
-    "Categorical",
-    "Enum",
-    "String",
-    "Datetime",
-    "Duration",
-    "Field",
-    "Struct",
-    "Array",
-    "List",
-    "Date",
-    "narwhalify",
-    "show_versions",
-    "Schema",
-    "from_dict",
-    "from_arrow",
-    "new_series",
 ]
diff --git a/narwhals/utils.py b/narwhals/utils.py
index b3127b7d..7cc41f78 100644
--- a/narwhals/utils.py
+++ b/narwhals/utils.py
@@ -13,7 +13,6 @@ from typing import Union
 from typing import cast
 from warnings import warn
 
-from narwhals._exceptions import ColumnNotFoundError
 from narwhals.dependencies import get_cudf
 from narwhals.dependencies import get_dask_dataframe
 from narwhals.dependencies import get_modin
@@ -28,6 +27,7 @@ from narwhals.dependencies import is_pandas_like_series
 from narwhals.dependencies import is_pandas_series
 from narwhals.dependencies import is_polars_series
 from narwhals.dependencies import is_pyarrow_chunked_array
+from narwhals.exceptions import ColumnNotFoundError
 from narwhals.translate import to_native
 
 if TYPE_CHECKING:


Test patch:
diff --git a/tests/expr_and_series/median_test.py b/tests/expr_and_series/median_test.py
index e46a0163..7c50988d 100644
--- a/tests/expr_and_series/median_test.py
+++ b/tests/expr_and_series/median_test.py
@@ -3,7 +3,7 @@ from __future__ import annotations
 import pytest
 
 import narwhals.stable.v1 as nw
-from narwhals._exceptions import InvalidOperationError
+from narwhals.exceptions import InvalidOperationError
 from tests.utils import Constructor
 from tests.utils import ConstructorEager
 from tests.utils import assert_equal_data
diff --git a/tests/frame/drop_test.py b/tests/frame/drop_test.py
index f9da91cb..2d6115c1 100644
--- a/tests/frame/drop_test.py
+++ b/tests/frame/drop_test.py
@@ -8,7 +8,7 @@ import pytest
 from polars.exceptions import ColumnNotFoundError as PlColumnNotFoundError
 
 import narwhals.stable.v1 as nw
-from narwhals._exceptions import ColumnNotFoundError
+from narwhals.exceptions import ColumnNotFoundError
 from tests.utils import POLARS_VERSION
 
 if TYPE_CHECKING:
diff --git a/tests/frame/select_test.py b/tests/frame/select_test.py
index 7c9398cb..b2897d83 100644
--- a/tests/frame/select_test.py
+++ b/tests/frame/select_test.py
@@ -7,8 +7,8 @@ import pyarrow as pa
 import pytest
 
 import narwhals.stable.v1 as nw
-from narwhals._exceptions import ColumnNotFoundError
-from narwhals._exceptions import InvalidIntoExprError
+from narwhals.exceptions import ColumnNotFoundError
+from narwhals.exceptions import InvalidIntoExprError
 from tests.utils import PANDAS_VERSION
 from tests.utils import POLARS_VERSION
 from tests.utils import Constructor