Skip to content

Commit dbba76a

Browse files
authored
depr: deprecate strict in from_native / to_native in favour of pass_through (#1308)
1 parent b9720b2 commit dbba76a

9 files changed

+450
-91
lines changed

docs/backcompat.md

+21-2
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,27 @@ before making any change.
9999

100100
### After `stable.v1`
101101

102-
- `Datetime` and `Duration` dtypes hash using both `time_unit` and `time_zone`.
103-
The effect of this can be seen when doing `dtype in {...}` checks:
102+
- Since Narwhals 1.13.0, the `strict` parameter in `from_native`, `to_native`, and `narwhalify`
103+
has been deprecated in favour of `pass_through`. This is because several users expressed
104+
confusion/surprise over what `strict=False` did.
105+
```python
106+
# v1 syntax:
107+
nw.from_native(df, strict=False)
108+
109+
# main namespace (and, when we get there, v2) syntax:
110+
nw.from_native(df, pass_through=True)
111+
```
112+
If you are using Narwhals>=1.13.0, then we recommend using `pass_through`, as that
113+
works consistently across namespaces.
114+
115+
In the future:
116+
117+
- in the main Narwhals namespace, `strict` will be removed in favour of `pass_through`
118+
- in `stable.v1`, we will keep both `strict` and `pass_through`
119+
120+
- Since Narwhals 1.9.0, `Datetime` and `Duration` dtypes hash using both `time_unit` and
121+
`time_zone`.
122+
The effect of this can be seen when placing these dtypes in sets:
104123

105124
```python exec="1" source="above" session="backcompat"
106125
import narwhals.stable.v1 as nw_v1

narwhals/dataframe.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2874,7 +2874,7 @@ def to_native(self) -> FrameT:
28742874
└─────┴─────┴─────┘
28752875
"""
28762876

2877-
return to_native(narwhals_object=self, strict=True)
2877+
return to_native(narwhals_object=self, pass_through=False)
28782878

28792879
# inherited
28802880
def pipe(self, function: Callable[[Any], Self], *args: Any, **kwargs: Any) -> Self:

narwhals/functions.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ def _from_dict_impl(
361361
else:
362362
msg = "Calling `from_dict` without `native_namespace` is only supported if all input values are already Narwhals Series"
363363
raise TypeError(msg)
364-
data = {key: to_native(value, strict=False) for key, value in data.items()}
364+
data = {key: to_native(value, pass_through=True) for key, value in data.items()}
365365
implementation = Implementation.from_native_namespace(native_namespace)
366366

367367
if implementation is Implementation.POLARS:

narwhals/stable/v1/__init__.py

+232-9
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
from narwhals.utils import maybe_get_index
6464
from narwhals.utils import maybe_reset_index
6565
from narwhals.utils import maybe_set_index
66+
from narwhals.utils import validate_strict_and_pass_though
6667

6768
if TYPE_CHECKING:
6869
from types import ModuleType
@@ -775,12 +776,212 @@ def from_native(
775776
"""
776777

777778

779+
@overload
780+
def from_native(
781+
native_object: IntoDataFrameT | IntoSeriesT,
782+
*,
783+
pass_through: Literal[True],
784+
eager_only: None = ...,
785+
eager_or_interchange_only: Literal[True],
786+
series_only: None = ...,
787+
allow_series: Literal[True],
788+
) -> DataFrame[IntoDataFrameT]: ...
789+
790+
791+
@overload
792+
def from_native(
793+
native_object: IntoDataFrameT | IntoSeriesT,
794+
*,
795+
pass_through: Literal[True],
796+
eager_only: Literal[True],
797+
eager_or_interchange_only: None = ...,
798+
series_only: None = ...,
799+
allow_series: Literal[True],
800+
) -> DataFrame[IntoDataFrameT] | Series: ...
801+
802+
803+
@overload
804+
def from_native(
805+
native_object: IntoDataFrameT,
806+
*,
807+
pass_through: Literal[True],
808+
eager_only: None = ...,
809+
eager_or_interchange_only: Literal[True],
810+
series_only: None = ...,
811+
allow_series: None = ...,
812+
) -> DataFrame[IntoDataFrameT]: ...
813+
814+
815+
@overload
816+
def from_native(
817+
native_object: T,
818+
*,
819+
pass_through: Literal[True],
820+
eager_only: None = ...,
821+
eager_or_interchange_only: Literal[True],
822+
series_only: None = ...,
823+
allow_series: None = ...,
824+
) -> T: ...
825+
826+
827+
@overload
828+
def from_native(
829+
native_object: IntoDataFrameT,
830+
*,
831+
pass_through: Literal[True],
832+
eager_only: Literal[True],
833+
eager_or_interchange_only: None = ...,
834+
series_only: None = ...,
835+
allow_series: None = ...,
836+
) -> DataFrame[IntoDataFrameT]: ...
837+
838+
839+
@overload
840+
def from_native(
841+
native_object: T,
842+
*,
843+
pass_through: Literal[True],
844+
eager_only: Literal[True],
845+
eager_or_interchange_only: None = ...,
846+
series_only: None = ...,
847+
allow_series: None = ...,
848+
) -> T: ...
849+
850+
851+
@overload
852+
def from_native(
853+
native_object: IntoFrameT | IntoSeriesT,
854+
*,
855+
pass_through: Literal[True],
856+
eager_only: None = ...,
857+
eager_or_interchange_only: None = ...,
858+
series_only: None = ...,
859+
allow_series: Literal[True],
860+
) -> DataFrame[IntoFrameT] | LazyFrame[IntoFrameT] | Series: ...
861+
862+
863+
@overload
864+
def from_native(
865+
native_object: IntoSeriesT,
866+
*,
867+
pass_through: Literal[True],
868+
eager_only: None = ...,
869+
eager_or_interchange_only: None = ...,
870+
series_only: Literal[True],
871+
allow_series: None = ...,
872+
) -> Series: ...
873+
874+
875+
@overload
876+
def from_native(
877+
native_object: IntoFrameT,
878+
*,
879+
pass_through: Literal[True],
880+
eager_only: None = ...,
881+
eager_or_interchange_only: None = ...,
882+
series_only: None = ...,
883+
allow_series: None = ...,
884+
) -> DataFrame[IntoFrameT] | LazyFrame[IntoFrameT]: ...
885+
886+
887+
@overload
888+
def from_native(
889+
native_object: T,
890+
*,
891+
pass_through: Literal[True],
892+
eager_only: None = ...,
893+
eager_or_interchange_only: None = ...,
894+
series_only: None = ...,
895+
allow_series: None = ...,
896+
) -> T: ...
897+
898+
899+
@overload
900+
def from_native(
901+
native_object: IntoDataFrameT,
902+
*,
903+
pass_through: Literal[False] = ...,
904+
eager_only: None = ...,
905+
eager_or_interchange_only: Literal[True],
906+
series_only: None = ...,
907+
allow_series: None = ...,
908+
) -> DataFrame[IntoDataFrameT]:
909+
"""
910+
from_native(df, pass_through=False, eager_or_interchange_only=True)
911+
from_native(df, eager_or_interchange_only=True)
912+
"""
913+
914+
915+
@overload
916+
def from_native(
917+
native_object: IntoDataFrameT,
918+
*,
919+
pass_through: Literal[False] = ...,
920+
eager_only: Literal[True],
921+
eager_or_interchange_only: None = ...,
922+
series_only: None = ...,
923+
allow_series: None = ...,
924+
) -> DataFrame[IntoDataFrameT]:
925+
"""
926+
from_native(df, pass_through=False, eager_only=True)
927+
from_native(df, eager_only=True)
928+
"""
929+
930+
931+
@overload
932+
def from_native(
933+
native_object: IntoFrameT | IntoSeriesT,
934+
*,
935+
pass_through: Literal[False] = ...,
936+
eager_only: None = ...,
937+
eager_or_interchange_only: None = ...,
938+
series_only: None = ...,
939+
allow_series: Literal[True],
940+
) -> DataFrame[Any] | LazyFrame[Any] | Series:
941+
"""
942+
from_native(df, pass_through=False, allow_series=True)
943+
from_native(df, allow_series=True)
944+
"""
945+
946+
947+
@overload
948+
def from_native(
949+
native_object: IntoSeriesT,
950+
*,
951+
pass_through: Literal[False] = ...,
952+
eager_only: None = ...,
953+
eager_or_interchange_only: None = ...,
954+
series_only: Literal[True],
955+
allow_series: None = ...,
956+
) -> Series:
957+
"""
958+
from_native(df, pass_through=False, series_only=True)
959+
from_native(df, series_only=True)
960+
"""
961+
962+
963+
@overload
964+
def from_native(
965+
native_object: IntoFrameT,
966+
*,
967+
pass_through: Literal[False] = ...,
968+
eager_only: None = ...,
969+
eager_or_interchange_only: None = ...,
970+
series_only: None = ...,
971+
allow_series: None = ...,
972+
) -> DataFrame[IntoFrameT] | LazyFrame[IntoFrameT]:
973+
"""
974+
from_native(df, pass_through=False)
975+
from_native(df)
976+
"""
977+
978+
778979
# All params passed in as variables
779980
@overload
780981
def from_native(
781982
native_object: Any,
782983
*,
783-
strict: bool,
984+
pass_through: bool,
784985
eager_only: bool | None,
785986
eager_or_interchange_only: bool | None = None,
786987
series_only: bool | None,
@@ -791,7 +992,8 @@ def from_native(
791992
def from_native(
792993
native_object: Any,
793994
*,
794-
strict: bool = True,
995+
strict: bool | None = None,
996+
pass_through: bool | None = None,
795997
eager_only: bool | None = None,
796998
eager_or_interchange_only: bool | None = None,
797999
series_only: bool | None = None,
@@ -811,8 +1013,19 @@ def from_native(
8111013
- pandas.Series
8121014
- polars.Series
8131015
- anything with a `__narwhals_series__` method
814-
strict: Whether to raise if object can't be converted (default) or
815-
to just leave it as-is.
1016+
strict: Determine what happens if the object isn't supported by Narwhals:
1017+
1018+
- `True` (default): raise an error
1019+
- `False`: pass object through as-is
1020+
1021+
**Deprecated** (v1.13.0):
1022+
Please use `pass_through` instead. Note that `strict` is still available
1023+
(and won't emit a deprecation warning) if you use `narwhals.stable.v1`,
1024+
see [perfect backwards compatibility policy](https://narwhals-dev.github.io/narwhals/backcompat/).
1025+
pass_through: Determine what happens if the object isn't supported by Narwhals:
1026+
1027+
- `False` (default): raise an error
1028+
- `True`: pass object through as-is
8161029
eager_only: Whether to only allow eager objects.
8171030
eager_or_interchange_only: Whether to only allow eager objects or objects which
8181031
implement the Dataframe Interchange Protocol.
@@ -829,9 +1042,14 @@ def from_native(
8291042
return native_object
8301043
if isinstance(native_object, Series) and (series_only or allow_series):
8311044
return native_object
1045+
1046+
pass_through = validate_strict_and_pass_though(
1047+
strict, pass_through, pass_through_default=False, emit_deprecation_warning=False
1048+
)
1049+
8321050
result = _from_native_impl(
8331051
native_object,
834-
strict=strict,
1052+
pass_through=pass_through,
8351053
eager_only=eager_only,
8361054
eager_or_interchange_only=eager_or_interchange_only,
8371055
series_only=series_only,
@@ -844,7 +1062,8 @@ def from_native(
8441062
def narwhalify(
8451063
func: Callable[..., Any] | None = None,
8461064
*,
847-
strict: bool = False,
1065+
strict: bool | None = None,
1066+
pass_through: bool | None = None,
8481067
eager_only: bool | None = False,
8491068
eager_or_interchange_only: bool | None = False,
8501069
series_only: bool | None = False,
@@ -905,13 +1124,17 @@ def func(df):
9051124
allow_series: Whether to allow series (default is only dataframe / lazyframe).
9061125
"""
9071126

1127+
pass_through = validate_strict_and_pass_though(
1128+
strict, pass_through, pass_through_default=True, emit_deprecation_warning=False
1129+
)
1130+
9081131
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
9091132
@wraps(func)
9101133
def wrapper(*args: Any, **kwargs: Any) -> Any:
9111134
args = [
9121135
from_native(
9131136
arg,
914-
strict=strict,
1137+
pass_through=pass_through,
9151138
eager_only=eager_only,
9161139
eager_or_interchange_only=eager_or_interchange_only,
9171140
series_only=series_only,
@@ -923,7 +1146,7 @@ def wrapper(*args: Any, **kwargs: Any) -> Any:
9231146
kwargs = {
9241147
name: from_native(
9251148
value,
926-
strict=strict,
1149+
pass_through=pass_through,
9271150
eager_only=eager_only,
9281151
eager_or_interchange_only=eager_or_interchange_only,
9291152
series_only=series_only,
@@ -944,7 +1167,7 @@ def wrapper(*args: Any, **kwargs: Any) -> Any:
9441167

9451168
result = func(*args, **kwargs)
9461169

947-
return to_native(result, strict=strict)
1170+
return to_native(result, pass_through=pass_through)
9481171

9491172
return wrapper
9501173

0 commit comments

Comments
 (0)