diff --git a/narwhals/_compliant/dataframe.py b/narwhals/_compliant/dataframe.py index 9c11876208..393e71c543 100644 --- a/narwhals/_compliant/dataframe.py +++ b/narwhals/_compliant/dataframe.py @@ -26,9 +26,9 @@ from narwhals._translate import NumpyConvertible from narwhals._translate import ToNarwhals from narwhals._translate import ToNarwhalsT_co +from narwhals._typing_compat import deprecated from narwhals.utils import Version from narwhals.utils import _StoresNative -from narwhals.utils import deprecated from narwhals.utils import is_compliant_series from narwhals.utils import is_index_selector from narwhals.utils import is_range diff --git a/narwhals/_compliant/expr.py b/narwhals/_compliant/expr.py index 389491da2b..e0aea89110 100644 --- a/narwhals/_compliant/expr.py +++ b/narwhals/_compliant/expr.py @@ -1,6 +1,5 @@ from __future__ import annotations -import sys from functools import partial from operator import methodcaller from typing import TYPE_CHECKING @@ -30,23 +29,14 @@ from narwhals._compliant.typing import EagerSeriesT from narwhals._compliant.typing import LazyExprT from narwhals._compliant.typing import NativeExprT +from narwhals._typing_compat import Protocol38 +from narwhals._typing_compat import deprecated from narwhals.dependencies import get_numpy from narwhals.dependencies import is_numpy_array from narwhals.dtypes import DType from narwhals.utils import _StoresCompliant -from narwhals.utils import deprecated from narwhals.utils import not_implemented -if not TYPE_CHECKING: # pragma: no cover - if sys.version_info >= (3, 9): - from typing import Protocol as Protocol38 - else: - from typing import Generic as Protocol38 -else: # pragma: no cover - # TODO @dangotbanned: Remove after dropping `3.8` (#2084) - # - https://github.com/narwhals-dev/narwhals/pull/2064#discussion_r1965921386 - from typing import Protocol as Protocol38 - if TYPE_CHECKING: from typing import Mapping diff --git a/narwhals/_compliant/group_by.py b/narwhals/_compliant/group_by.py index 0216fb12a3..3719d2ace2 100644 --- a/narwhals/_compliant/group_by.py +++ b/narwhals/_compliant/group_by.py @@ -1,7 +1,6 @@ from __future__ import annotations import re -import sys from typing import TYPE_CHECKING from typing import Any from typing import Callable @@ -26,6 +25,7 @@ from narwhals._compliant.typing import EagerExprT_contra from narwhals._compliant.typing import LazyExprT_contra from narwhals._compliant.typing import NativeExprT_co +from narwhals._typing_compat import Protocol38 from narwhals.utils import is_sequence_of if TYPE_CHECKING: @@ -34,16 +34,6 @@ _SameFrameT = TypeVar("_SameFrameT", CompliantDataFrameAny, CompliantLazyFrameAny) -if not TYPE_CHECKING: # pragma: no cover - if sys.version_info >= (3, 9): - from typing import Protocol as Protocol38 - else: - from typing import Generic as Protocol38 -else: # pragma: no cover - # 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__ = [ "CompliantGroupBy", "DepthTrackingGroupBy", diff --git a/narwhals/_compliant/selectors.py b/narwhals/_compliant/selectors.py index 8a7ef7b580..62d3cdd345 100644 --- a/narwhals/_compliant/selectors.py +++ b/narwhals/_compliant/selectors.py @@ -14,24 +14,12 @@ from typing import overload from narwhals._compliant.expr import CompliantExpr +from narwhals._typing_compat import Protocol38 from narwhals.utils import _parse_time_unit_and_time_zone from narwhals.utils import dtype_matches_time_unit_and_time_zone from narwhals.utils import get_column_names from narwhals.utils import is_compliant_dataframe -if not TYPE_CHECKING: # pragma: no cover - # TODO @dangotbanned: Remove after dropping `3.8` (#2084) - # - https://github.com/narwhals-dev/narwhals/pull/2064#discussion_r1965921386 - import sys - - if sys.version_info >= (3, 9): - from typing import Protocol as Protocol38 - else: - from typing import Generic as Protocol38 - -else: # pragma: no cover - from typing import Protocol as Protocol38 - if TYPE_CHECKING: from datetime import timezone diff --git a/narwhals/_compliant/when_then.py b/narwhals/_compliant/when_then.py index fbabcc5484..b6a42c5265 100644 --- a/narwhals/_compliant/when_then.py +++ b/narwhals/_compliant/when_then.py @@ -1,6 +1,5 @@ from __future__ import annotations -import sys from typing import TYPE_CHECKING from typing import Any from typing import Callable @@ -19,6 +18,7 @@ from narwhals._compliant.typing import LazyExprAny from narwhals._compliant.typing import NativeExprT from narwhals._compliant.typing import NativeSeriesT +from narwhals._typing_compat import Protocol38 if TYPE_CHECKING: from typing_extensions import Self @@ -31,15 +31,6 @@ from narwhals.utils import Version from narwhals.utils import _FullContext -if not TYPE_CHECKING: # pragma: no cover - if sys.version_info >= (3, 9): - from typing import Protocol as Protocol38 - else: - from typing import Generic as Protocol38 -else: # pragma: no cover - # 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__ = ["CompliantThen", "CompliantWhen", "EagerWhen", "LazyWhen"] diff --git a/narwhals/_translate.py b/narwhals/_translate.py index ea19e140ce..b4ca27c0d8 100644 --- a/narwhals/_translate.py +++ b/narwhals/_translate.py @@ -68,37 +68,13 @@ class OtherConvertible( from typing import Mapping from typing import Protocol +from narwhals._typing_compat import TypeVar + if TYPE_CHECKING: import pyarrow as pa from typing_extensions import Self from typing_extensions import TypeAlias from typing_extensions import TypeIs - from typing_extensions import TypeVar - - -else: # pragma: no cover - import sys - - if sys.version_info >= (3, 13): - from typing import TypeVar - else: - from typing import TypeVar as _TypeVar - - def TypeVar( # noqa: ANN202, N802 - name: str, - *constraints: Any, - bound: Any | None = None, - covariant: bool = False, - contravariant: bool = False, - **kwds: Any, # noqa: ARG001 - ): - return _TypeVar( - name, - *constraints, - bound=bound, - covariant=covariant, - contravariant=contravariant, - ) class ArrowStreamExportable(Protocol): diff --git a/narwhals/_typing_compat.py b/narwhals/_typing_compat.py new file mode 100644 index 0000000000..05ef604171 --- /dev/null +++ b/narwhals/_typing_compat.py @@ -0,0 +1,79 @@ +"""Backward compatibility for newer/less buggy typing features. + +## Important +Import from here to avoid introducing a runtime dependency on [`typing_extensions`] + +## Notes +- `Protocol38` + - https://github.com/narwhals-dev/narwhals/pull/2064#discussion_r1965921386 + - https://github.com/narwhals-dev/narwhals/pull/2294#discussion_r2014534830 +- `TypeVar` defaults + - https://typing.python.org/en/latest/spec/generics.html#type-parameter-defaults + - https://peps.python.org/pep-0696/ +- `@deprecated` + - https://docs.python.org/3/library/warnings.html#warnings.deprecated + - https://typing.python.org/en/latest/spec/directives.html#deprecated + - https://peps.python.org/pep-0702/ + +[`typing_extensions`]: https://github.com/python/typing_extensions +""" + +from __future__ import annotations + +# ruff: noqa: ARG001, ANN202, N802 +import sys +from typing import TYPE_CHECKING +from typing import Any + +if TYPE_CHECKING: + from typing import Callable + from typing import Protocol as Protocol38 + + if sys.version_info >= (3, 13): + from typing import TypeVar + from warnings import deprecated + else: + from typing_extensions import TypeVar + from typing_extensions import deprecated + + _Fn = TypeVar("_Fn", bound=Callable[..., Any]) + + +else: # pragma: no cover + if sys.version_info >= (3, 13): + from typing import TypeVar + from warnings import deprecated + else: + from typing import TypeVar as _TypeVar + + def TypeVar( + name: str, + *constraints: Any, + bound: Any | None = None, + covariant: bool = False, + contravariant: bool = False, + **kwds: Any, + ): + return _TypeVar( + name, + *constraints, + bound=bound, + covariant=covariant, + contravariant=contravariant, + ) + + def deprecated(message: str, /) -> Callable[[_Fn], _Fn]: + def wrapper(func: _Fn, /) -> _Fn: + return func + + return wrapper + + # TODO @dangotbanned: Remove after dropping `3.8` (#2084) + # - https://github.com/narwhals-dev/narwhals/pull/2064#discussion_r1965921386 + if sys.version_info >= (3, 9): + from typing import Protocol as Protocol38 + else: + from typing import Generic as Protocol38 + + +__all__ = ["Protocol38", "TypeVar", "deprecated"] diff --git a/narwhals/stable/v1/__init__.py b/narwhals/stable/v1/__init__.py index a45de72f64..ad5acbf046 100644 --- a/narwhals/stable/v1/__init__.py +++ b/narwhals/stable/v1/__init__.py @@ -15,6 +15,7 @@ from narwhals import dependencies from narwhals import exceptions from narwhals import selectors +from narwhals._typing_compat import TypeVar from narwhals.dataframe import DataFrame as NwDataFrame from narwhals.dataframe import LazyFrame as NwLazyFrame from narwhals.dependencies import get_polars @@ -89,7 +90,6 @@ from typing_extensions import ParamSpec from typing_extensions import Self - from typing_extensions import TypeVar from narwhals._translate import IntoArrowTable from narwhals.dataframe import MultiColSelector @@ -110,15 +110,11 @@ DataFrameT = TypeVar("DataFrameT", bound="DataFrame[Any]") LazyFrameT = TypeVar("LazyFrameT", bound="LazyFrame[Any]") SeriesT = TypeVar("SeriesT", bound="Series[Any]") - IntoSeriesT = TypeVar("IntoSeriesT", bound="IntoSeries", default=Any) T = TypeVar("T", default=Any) P = ParamSpec("P") R = TypeVar("R") -else: - from typing import TypeVar - IntoSeriesT = TypeVar("IntoSeriesT", bound="IntoSeries") - T = TypeVar("T") +IntoSeriesT = TypeVar("IntoSeriesT", bound="IntoSeries", default=Any) class DataFrame(NwDataFrame[IntoDataFrameT]): diff --git a/narwhals/utils.py b/narwhals/utils.py index 46f28f0266..17beca2b0f 100644 --- a/narwhals/utils.py +++ b/narwhals/utils.py @@ -27,6 +27,7 @@ from warnings import warn from narwhals._enum import NoAutoEnum +from narwhals._typing_compat import deprecated from narwhals.dependencies import get_cudf from narwhals.dependencies import get_dask from narwhals.dependencies import get_dask_dataframe @@ -1763,24 +1764,6 @@ def _is_naive_format(format: str) -> bool: return not any(x in format for x in ("%s", "%z", "Z")) -if TYPE_CHECKING: - import sys - - if sys.version_info >= (3, 13): - # NOTE: avoids `mypy` - # error: Module "narwhals.utils" does not explicitly export attribute "deprecated" [attr-defined] - from warnings import deprecated as deprecated # noqa: PLC0414 - else: - from typing_extensions import deprecated as deprecated # noqa: PLC0414 -else: - - def deprecated(message: str, /) -> Callable[[_Fn], _Fn]: # noqa: ARG001 - def wrapper(func: _Fn, /) -> _Fn: - return func - - return wrapper - - class not_implemented: # noqa: N801 """Mark some functionality as unsupported.