Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions narwhals/_arrow/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
)
from narwhals._compliant import EagerSeries
from narwhals._expression_parsing import ExprKind
from narwhals._typing_compat import assert_never
from narwhals._utils import (
Implementation,
generate_temporary_column_name,
Expand Down Expand Up @@ -564,8 +565,8 @@ def is_between(
ge = pc.greater_equal(self.native, lower_bound)
le = pc.less_equal(self.native, upper_bound)
res = pc.and_kleene(ge, le)
else: # pragma: no cover
raise AssertionError
else:
assert_never(closed)
return self._with_native(res)

def is_null(self) -> Self:
Expand Down
12 changes: 5 additions & 7 deletions narwhals/_compliant/dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
ToNarwhals,
ToNarwhalsT_co,
)
from narwhals._typing_compat import deprecated
from narwhals._typing_compat import assert_never, deprecated
from narwhals._utils import (
Version,
_StoresNative,
Expand Down Expand Up @@ -479,9 +479,8 @@ def __getitem__( # noqa: C901, PLR0912
compliant = self._select_multi_name(columns.native)
elif is_sequence_like(columns):
compliant = self._select_multi_name(columns)
else: # pragma: no cover
msg = f"Unreachable code, got unexpected type: {type(columns)}"
raise AssertionError(msg)
else:
assert_never(columns)

if not is_slice_none(rows):
if isinstance(rows, int):
Expand All @@ -492,8 +491,7 @@ def __getitem__( # noqa: C901, PLR0912
compliant = compliant._gather(rows.native)
elif is_sized_multi_index_selector(rows):
compliant = compliant._gather(rows)
else: # pragma: no cover
msg = f"Unreachable code, got unexpected type: {type(rows)}"
raise AssertionError(msg)
else:
assert_never(rows)

return compliant
6 changes: 3 additions & 3 deletions narwhals/_compliant/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
NativeSeriesT_co,
)
from narwhals._translate import FromIterable, FromNative, NumpyConvertible, ToNarwhals
from narwhals._typing_compat import assert_never
from narwhals._utils import (
_StoresCompliant,
_StoresNative,
Expand Down Expand Up @@ -330,9 +331,8 @@ def __getitem__(self, item: MultiIndexSelector[Self]) -> Self:
return self._gather(item.native)
elif is_sized_multi_index_selector(item):
return self._gather(item)
else: # pragma: no cover
msg = f"Unreachable code, got unexpected type: {type(item)}"
raise AssertionError(msg)
else:
assert_never(item)

@property
def str(self) -> EagerSeriesStringNamespace[Self, NativeSeriesT]: ...
Expand Down
5 changes: 3 additions & 2 deletions narwhals/_pandas_like/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
select_columns_by_name,
set_index,
)
from narwhals._typing_compat import assert_never
from narwhals._utils import (
Implementation,
is_list_of,
Expand Down Expand Up @@ -375,8 +376,8 @@ def is_between(
res = ser.gt(lower_bound) & ser.lt(upper_bound)
elif closed == "both":
res = ser.ge(lower_bound) & ser.le(upper_bound)
else: # pragma: no cover
raise AssertionError
else:
assert_never(closed)
return self._with_native(res).alias(ser.name)

def is_in(self, other: Any) -> Self:
Expand Down
22 changes: 21 additions & 1 deletion narwhals/_typing_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
else:
from typing_extensions import TypeVar, deprecated

if sys.version_info >= (3, 11):
from typing import Never, assert_never
else:
from typing_extensions import Never, assert_never

_Fn = TypeVar("_Fn", bound=Callable[..., Any])


Expand Down Expand Up @@ -65,9 +70,24 @@ def wrapper(func: _Fn, /) -> _Fn:

return wrapper

_ASSERT_NEVER_REPR_MAX_LENGTH = 100
_BUG_URL = (
"https://github.com/narwhals-dev/narwhals/issues/new?template=bug_report.yml"
)
Comment on lines +74 to +76
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes nice one! Thank you πŸ‘Œ


def assert_never(arg: Never, /) -> Never:
value = repr(arg)
if len(value) > _ASSERT_NEVER_REPR_MAX_LENGTH:
value = value[:_ASSERT_NEVER_REPR_MAX_LENGTH] + "..."
msg = (
f"Expected code to be unreachable, but got: {value}.\n"
f"Please report an issue at {_BUG_URL}"
)
raise AssertionError(msg)

# TODO @dangotbanned: Remove after dropping `3.8` (#2084)
# - https://github.com/narwhals-dev/narwhals/pull/2064#discussion_r1965921386
from typing import Protocol as Protocol38


__all__ = ["Protocol38", "TypeVar", "deprecated"]
__all__ = ["Protocol38", "TypeVar", "assert_never", "deprecated"]
5 changes: 2 additions & 3 deletions narwhals/stable/v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import narwhals as nw
from narwhals import exceptions, functions as nw_f
from narwhals._typing_compat import TypeVar
from narwhals._typing_compat import TypeVar, assert_never
from narwhals._utils import (
Implementation,
Version,
Expand Down Expand Up @@ -482,8 +482,7 @@ def _stableify(
return Series(obj._compliant_series._with_version(Version.V1), level=obj._level)
if isinstance(obj, NwExpr):
return Expr(obj._to_compliant_expr, obj._metadata)
msg = f"Expected DataFrame, LazyFrame, Series, or Expr, got: {type(obj)}" # pragma: no cover
raise AssertionError(msg)
assert_never(obj)


@overload
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ exclude_also = [
'if "pyspark" in str\(constructor',
'if "pyspark_connect" in str\(constructor',
'pytest.skip\(',
'assert_never\(',
]

[tool.mypy]
Expand Down
28 changes: 28 additions & 0 deletions tests/typing_compat_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Ensuring backports/extensions to new `typing` features are understood correctly."""

from __future__ import annotations

import re
from typing import TYPE_CHECKING, Literal

import pytest

from narwhals._typing_compat import assert_never


def test_assert_never() -> None:
pattern = re.compile(
r"expected.+unreachable.+got.+'a'.+report.+issue.+github.+narwhals",
re.DOTALL | re.IGNORECASE,
)
some: Literal["a"] = "a"
if some != "a":
assigned = "b"
assert_never(assigned)
else:
assigned = some
if not TYPE_CHECKING:
# NOTE: Trying to avoid the assert influencing narrowing
assert assigned == "a"
with pytest.raises(AssertionError, match=pattern):
assert_never(assigned) # type: ignore[arg-type]
Loading