Skip to content

Commit fd0467b

Browse files
authored
feat: Warn, instead of raising, in is_*_dataframe / is_*_series functions (#2906)
1 parent 358659a commit fd0467b

File tree

10 files changed

+111
-126
lines changed

10 files changed

+111
-126
lines changed

narwhals/_exceptions.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from __future__ import annotations
2+
3+
from warnings import warn
4+
5+
6+
def find_stacklevel() -> int:
7+
"""Find the first place in the stack that is not inside narwhals.
8+
9+
Returns:
10+
Stacklevel.
11+
12+
Taken from:
13+
https://github.com/pandas-dev/pandas/blob/ab89c53f48df67709a533b6a95ce3d911871a0a8/pandas/util/_exceptions.py#L30-L51
14+
"""
15+
import inspect
16+
from pathlib import Path
17+
18+
import narwhals as nw
19+
20+
pkg_dir = str(Path(nw.__file__).parent)
21+
22+
# https://stackoverflow.com/questions/17407119/python-inspect-stack-is-slow
23+
frame = inspect.currentframe()
24+
n = 0
25+
try:
26+
while frame:
27+
fname = inspect.getfile(frame)
28+
if fname.startswith(pkg_dir) or (
29+
(qualname := getattr(frame.f_code, "co_qualname", None))
30+
# ignore @singledispatch wrappers
31+
and qualname.startswith("singledispatch.")
32+
):
33+
frame = frame.f_back
34+
n += 1
35+
else: # pragma: no cover
36+
break
37+
else: # pragma: no cover
38+
pass
39+
finally:
40+
# https://docs.python.org/3/library/inspect.html
41+
# > Though the cycle detector will catch these, destruction of the frames
42+
# > (and local variables) can be made deterministic by removing the cycle
43+
# > in a finally clause.
44+
del frame
45+
return n
46+
47+
48+
def issue_deprecation_warning(message: str, _version: str) -> None: # pragma: no cover
49+
"""Issue a deprecation warning.
50+
51+
Arguments:
52+
message: The message associated with the warning.
53+
_version: Narwhals version when the warning was introduced. Just used for internal
54+
bookkeeping.
55+
"""
56+
warn(message=message, category=DeprecationWarning, stacklevel=find_stacklevel())
57+
58+
59+
def issue_warning(message: str, category: type[Warning]) -> None:
60+
warn(message=message, category=category, stacklevel=find_stacklevel())

narwhals/_pandas_like/group_by.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
from typing import TYPE_CHECKING, Any, ClassVar, Literal
88

99
from narwhals._compliant import EagerGroupBy
10+
from narwhals._exceptions import issue_warning
1011
from narwhals._expression_parsing import evaluate_output_names_and_aliases
11-
from narwhals._utils import find_stacklevel
1212
from narwhals.dependencies import is_pandas_like_dataframe
1313

1414
if TYPE_CHECKING:
@@ -310,12 +310,11 @@ def empty_results_error() -> ValueError:
310310

311311

312312
def warn_complex_group_by() -> None:
313-
warnings.warn(
313+
issue_warning(
314314
"Found complex group-by expression, which can't be expressed efficiently with the "
315315
"pandas API. If you can, please rewrite your query such that group-by aggregations "
316316
"are simple (e.g. mean, std, min, max, ...). \n\n"
317317
"Please see: "
318318
"https://narwhals-dev.github.io/narwhals/concepts/improve_group_by_operation/",
319319
UserWarning,
320-
stacklevel=find_stacklevel(),
321320
)

narwhals/_spark_like/dataframe.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from __future__ import annotations
22

3-
import warnings
43
from functools import reduce
54
from operator import and_
65
from typing import TYPE_CHECKING, Any
76

7+
from narwhals._exceptions import issue_warning
88
from narwhals._namespace import is_native_spark_like
99
from narwhals._spark_like.utils import (
1010
catch_pyspark_connect_exception,
@@ -19,7 +19,6 @@
1919
from narwhals._utils import (
2020
Implementation,
2121
ValidateBackendVersion,
22-
find_stacklevel,
2322
generate_temporary_column_name,
2423
not_implemented,
2524
parse_columns_to_drop,
@@ -156,9 +155,9 @@ def _to_arrow_schema(self) -> pa.Schema: # pragma: no cover
156155
# We can avoid the check when we introduce `nw.Null` dtype.
157156
null_type = self._native_dtypes.NullType # pyright: ignore[reportAttributeAccessIssue]
158157
if not isinstance(native_spark_dtype, null_type):
159-
warnings.warn(
158+
issue_warning(
160159
f"Could not convert dtype {native_spark_dtype} to PyArrow dtype, {exc!r}",
161-
stacklevel=find_stacklevel(),
160+
UserWarning,
162161
)
163162
schema.append((key, pa.null()))
164163
else:

narwhals/_utils.py

Lines changed: 3 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
cast,
2222
overload,
2323
)
24-
from warnings import warn
2524

2625
from narwhals._enum import NoAutoEnum
26+
from narwhals._exceptions import issue_deprecation_warning
2727
from narwhals._typing_compat import assert_never, deprecated
2828
from narwhals.dependencies import (
2929
get_cudf,
@@ -46,12 +46,7 @@
4646
is_polars_series,
4747
is_pyarrow_chunked_array,
4848
)
49-
from narwhals.exceptions import (
50-
ColumnNotFoundError,
51-
DuplicateError,
52-
InvalidOperationError,
53-
PerformanceWarning,
54-
)
49+
from narwhals.exceptions import ColumnNotFoundError, DuplicateError, InvalidOperationError
5550

5651
if TYPE_CHECKING:
5752
from collections.abc import Set # noqa: PYI025
@@ -1431,68 +1426,6 @@ def is_sequence_of(obj: Any, tp: type[_T]) -> TypeIs[Sequence[_T]]:
14311426
)
14321427

14331428

1434-
def find_stacklevel() -> int:
1435-
"""Find the first place in the stack that is not inside narwhals.
1436-
1437-
Returns:
1438-
Stacklevel.
1439-
1440-
Taken from:
1441-
https://github.com/pandas-dev/pandas/blob/ab89c53f48df67709a533b6a95ce3d911871a0a8/pandas/util/_exceptions.py#L30-L51
1442-
"""
1443-
import inspect
1444-
from pathlib import Path
1445-
1446-
import narwhals as nw
1447-
1448-
pkg_dir = str(Path(nw.__file__).parent)
1449-
1450-
# https://stackoverflow.com/questions/17407119/python-inspect-stack-is-slow
1451-
frame = inspect.currentframe()
1452-
n = 0
1453-
try:
1454-
while frame:
1455-
fname = inspect.getfile(frame)
1456-
if fname.startswith(pkg_dir) or (
1457-
(qualname := getattr(frame.f_code, "co_qualname", None))
1458-
# ignore @singledispatch wrappers
1459-
and qualname.startswith("singledispatch.")
1460-
):
1461-
frame = frame.f_back
1462-
n += 1
1463-
else: # pragma: no cover
1464-
break
1465-
else: # pragma: no cover
1466-
pass
1467-
finally:
1468-
# https://docs.python.org/3/library/inspect.html
1469-
# > Though the cycle detector will catch these, destruction of the frames
1470-
# > (and local variables) can be made deterministic by removing the cycle
1471-
# > in a finally clause.
1472-
del frame
1473-
return n
1474-
1475-
1476-
def issue_deprecation_warning(message: str, _version: str) -> None: # pragma: no cover
1477-
"""Issue a deprecation warning.
1478-
1479-
Arguments:
1480-
message: The message associated with the warning.
1481-
_version: Narwhals version when the warning was introduced. Just used for internal
1482-
bookkeeping.
1483-
"""
1484-
warn(message=message, category=DeprecationWarning, stacklevel=find_stacklevel())
1485-
1486-
1487-
def issue_performance_warning(message: str) -> None:
1488-
"""Issue a performance warning.
1489-
1490-
Arguments:
1491-
message: The message associated with the warning.
1492-
"""
1493-
warn(message=message, category=PerformanceWarning, stacklevel=find_stacklevel())
1494-
1495-
14961429
def validate_strict_and_pass_though(
14971430
strict: bool | None, # noqa: FBT001
14981431
pass_through: bool | None, # noqa: FBT001
@@ -1727,7 +1660,7 @@ def is_eager_allowed(obj: Implementation) -> TypeIs[EagerAllowedImplementation]:
17271660

17281661

17291662
def has_native_namespace(obj: Any) -> TypeIs[SupportsNativeNamespace]:
1730-
return hasattr(obj, "__native_namespace__")
1663+
return _hasattr_static(obj, "__native_namespace__")
17311664

17321665

17331666
def _supports_dataframe_interchange(obj: Any) -> TypeIs[DataFrameLike]:

narwhals/dataframe.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
TypeVar,
1414
overload,
1515
)
16-
from warnings import warn
1716

17+
from narwhals._exceptions import issue_warning
1818
from narwhals._expression_parsing import (
1919
ExprKind,
2020
all_exprs_are_scalar_like,
@@ -24,7 +24,6 @@
2424
from narwhals._utils import (
2525
Implementation,
2626
Version,
27-
find_stacklevel,
2827
flatten,
2928
generate_repr,
3029
is_compliant_dataframe,
@@ -34,7 +33,6 @@
3433
is_list_of,
3534
is_sequence_like,
3635
is_slice_none,
37-
issue_performance_warning,
3836
supports_arrow_c_stream,
3937
)
4038
from narwhals.dependencies import (
@@ -43,7 +41,11 @@
4341
is_numpy_array_2d,
4442
is_pyarrow_table,
4543
)
46-
from narwhals.exceptions import InvalidIntoExprError, InvalidOperationError
44+
from narwhals.exceptions import (
45+
InvalidIntoExprError,
46+
InvalidOperationError,
47+
PerformanceWarning,
48+
)
4749
from narwhals.functions import _from_dict_no_backend, _is_into_schema
4850
from narwhals.schema import Schema
4951
from narwhals.series import Series
@@ -2196,7 +2198,7 @@ def pivot(
21962198
"`maintain_order` has no effect and is only kept around for backwards-compatibility. "
21972199
"You can safely remove this argument."
21982200
)
2199-
warn(message=msg, category=UserWarning, stacklevel=find_stacklevel())
2201+
issue_warning(msg, UserWarning)
22002202
on = [on] if isinstance(on, str) else on
22012203
values = [values] if isinstance(values, str) else values
22022204
index = [index] if isinstance(index, str) else index
@@ -2692,7 +2694,7 @@ def schema(self) -> Schema:
26922694
"Resolving the schema of a LazyFrame is a potentially expensive operation. "
26932695
"Use `LazyFrame.collect_schema()` to get the schema without this warning."
26942696
)
2695-
issue_performance_warning(msg)
2697+
issue_warning(msg, PerformanceWarning)
26962698
return super().schema
26972699

26982700
def collect_schema(self) -> Schema:

0 commit comments

Comments
 (0)