Skip to content
Draft
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
d5a126d
fix(typing): Huuuuuuuge progress
dangotbanned Sep 12, 2025
39bb6b3
test(typing): Add `pa.ChunkedArray` into the mix
dangotbanned Sep 12, 2025
c7cf5ae
test: Rename variables
dangotbanned Sep 12, 2025
6f56dc0
test: Cover w/ `pass_through=True` as well
dangotbanned Sep 12, 2025
d72ce2d
test: Use `assert_type` and runtime checks
dangotbanned Sep 12, 2025
94029ed
test: rename test
dangotbanned Sep 12, 2025
0358cc9
test: Insane `LazyFrame` coverage
dangotbanned Sep 12, 2025
43ceb3f
revert: wont be using that one
dangotbanned Sep 12, 2025
ceaf9e7
refactor(typing): Factor out another pair
dangotbanned Sep 12, 2025
d988a24
refactor(typing): Add single `pass_through` case
dangotbanned Sep 12, 2025
cc5588b
revert: never used them
dangotbanned Sep 12, 2025
1cab6d0
refactor: Invert `NotRequired` -> `Required`
dangotbanned Sep 12, 2025
ff6258d
refactor: Rename all `TypedDict`s
dangotbanned Sep 12, 2025
a0095b1
style: Remove whitespace
dangotbanned Sep 12, 2025
06d74a3
Merge remote-tracking branch 'upstream/main' into from-native-overloads
dangotbanned Sep 12, 2025
c02ab37
Merge branch 'main' into from-native-overloads
dangotbanned Sep 13, 2025
5b6ca5f
Merge branch 'main' into from-native-overloads
dangotbanned Oct 6, 2025
4142e43
Merge remote-tracking branch 'upstream/main' into from-native-overloads
dangotbanned Oct 20, 2025
2e7a0ae
chore: fix merge conflicts
dangotbanned Oct 20, 2025
435beb4
refactor: Move new `TypedDict`s to `_translate.py`
dangotbanned Oct 20, 2025
0605936
fix(typing): More gracefully handle narwhals in overloads
dangotbanned Oct 20, 2025
67bb52f
revert: Remove now-unused `kwds`
dangotbanned Oct 20, 2025
fd7ab16
refactor(typing): Update `v2`
dangotbanned Oct 20, 2025
9a8c025
refactor(DRAFT): Start shrinking `v1`
dangotbanned Oct 20, 2025
c61af03
refactor(typing): Add `AllowAny` variants
dangotbanned Oct 20, 2025
252bcdb
refactor(typing): Add `AllowSeries` variants
dangotbanned Oct 20, 2025
c887618
refactor(typing): Add `OnlySeries` variants
dangotbanned Oct 20, 2025
99527d5
refactor(typing): Add `ExcludeSeries` variants
dangotbanned Oct 20, 2025
1dc56f0
refactor(typing): Add `AllowLazy` variants
dangotbanned Oct 20, 2025
10e7ddf
refactor(typing): Add `OnlyEagerOrInterchange` variants
dangotbanned Oct 20, 2025
1c498aa
chore: remove notes
dangotbanned Oct 20, 2025
4e37df6
chore: Fix those imports for `v1` too
dangotbanned Oct 20, 2025
f372696
oop missed a spot
dangotbanned Oct 20, 2025
459598f
chore: remove some comments
dangotbanned Oct 20, 2025
58590e4
Merge remote-tracking branch 'upstream/main' into from-native-overloads
dangotbanned Oct 20, 2025
bd90c1f
Merge branch 'main' into from-native-overloads
dangotbanned Oct 23, 2025
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
8 changes: 7 additions & 1 deletion narwhals/_native.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,15 +190,21 @@ def columns(self) -> Any: ...
def join(self, *args: Any, **kwargs: Any) -> Any: ...


class NativeDataFrame(Sized, NativeFrame, Protocol): ...
class NativeDataFrame(Sized, NativeFrame, Protocol):
def drop(self, *args: Any, **kwargs: Any) -> Any: ...


class NativeLazyFrame(NativeFrame, Protocol):
def explain(self, *args: Any, **kwargs: Any) -> Any: ...


# Needs to have something `NativeDataFrame` doesn't?
class NativeSeries(Sized, Iterable[Any], Protocol):
def filter(self, *args: Any, **kwargs: Any) -> Any: ...
# `pd.DataFrame` has this - the others don't
def value_counts(self, *args: Any, **kwargs: Any) -> Any: ...
# `pl.DataFrame` has this - the others don't
def unique(self, *args: Any, **kwargs: Any) -> Any: ...


class _BasePandasLike(Sized, Protocol):
Expand Down
124 changes: 119 additions & 5 deletions narwhals/_translate.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""[Protocols] defining conversion methods between representations.
"""[Protocols] defining conversion methods between representations, and related [structural] typing.

These come in 3 flavors and are [generic] to promote reuse.
The protocols come in 3 flavors and are [generic] to promote reuse.

The following examples use the placeholder types `Narwhal` and `Other`:
These examples use the placeholder types `Narwhal` and `Other`:
- `Narwhal`: some class written in `narwhals`.
- `Other`: any other class, could be native, compliant, or a builtin.

Expand Down Expand Up @@ -53,6 +53,7 @@ class OtherConvertible(

[Protocols]: https://typing.python.org/en/latest/spec/protocol.html
[generic]: https://typing.python.org/en/latest/spec/generics.html
[structural]: https://typing.python.org/en/latest/spec/glossary.html#term-structural
[upstream signature]: https://numpy.org/doc/stable/user/basics.interoperability.html#the-array-method
[positional-only]: https://peps.python.org/pep-0570/
[moist]: https://mypy.readthedocs.io/en/stable/generics.html#variance-of-generic-types
Expand All @@ -63,13 +64,13 @@ class OtherConvertible(
from __future__ import annotations

from collections.abc import Iterable, Mapping
from typing import TYPE_CHECKING, Any, Protocol
from typing import TYPE_CHECKING, Any, Literal, Protocol, TypedDict

from narwhals._typing_compat import TypeVar

if TYPE_CHECKING:
import pyarrow as pa
from typing_extensions import Self, TypeAlias, TypeIs
from typing_extensions import Required, Self, TypeAlias, TypeIs


class ArrowStreamExportable(Protocol):
Expand Down Expand Up @@ -184,3 +185,116 @@ class ToNarwhals(Protocol[ToNarwhalsT_co]):
def to_narwhals(self) -> ToNarwhalsT_co:
"""Convert into public representation."""
...


class _ExcludeSeries(TypedDict, total=False):
eager_only: bool
series_only: Literal[False]
allow_series: Literal[False] | None


class ExcludeSeries(_ExcludeSeries, total=False):
pass_through: bool


class _AllowSeries(TypedDict, total=False):
eager_only: bool
series_only: Literal[False]
allow_series: Required[Literal[True]]


class AllowSeries(_AllowSeries, total=False):
pass_through: bool


class _OnlySeries(TypedDict, total=False):
eager_only: bool
series_only: Required[Literal[True]]
allow_series: bool | None


class OnlySeries(_OnlySeries, total=False):
pass_through: bool


class _OnlyEager(TypedDict, total=False):
eager_only: Required[Literal[True]]
series_only: bool
allow_series: bool | None


class OnlyEager(_OnlyEager, total=False):
pass_through: bool


class _AllowLazy(TypedDict, total=False):
eager_only: Literal[False]
series_only: Literal[False]
allow_series: bool | None


class AllowLazy(_AllowLazy, total=False):
pass_through: bool


class _AllowAny(TypedDict, total=False):
eager_only: Literal[False]
series_only: Literal[False]
allow_series: Required[Literal[True]]


class AllowAny(_AllowAny, total=False):
pass_through: bool


class _Unknown(TypedDict, total=False):
eager_only: bool
series_only: bool
allow_series: bool | None


class PassThroughUnknown(_Unknown, total=False):
pass_through: Required[Literal[True]]


class PassThroughUnknownV1(_Unknown, total=False):
pass_through: Required[Literal[True]]
eager_or_interchange_only: bool


class StrictUnknownV1(_Unknown, total=False):
strict: Required[Literal[False]]
eager_or_interchange_only: bool


# NOTE: Define everything *without* `pass_through`
# inherit to add that and then independently for v1 for `strict`
# which also needs `eager_or_interchange_only`
class V1(TypedDict, total=False):
# mutually exclusive and both include `None` here? :sad:
# - Ok
# - None, None
# - bool, None
# - None, bool
# - Bad
# - bool, bool
strict: bool | None # None
pass_through: bool | None # None

# these guys are mutually exclusive *only* when both are `True`
# - Ok
# - False, False
# - True, False
# - False, True
# - Bad
# - True, True
# - bool, bool
# - True, bool
# - bool, True
eager_only: bool # False
# no lazyframe
eager_or_interchange_only: bool # False

# same as others
series_only: bool # False
allow_series: bool | None # None
86 changes: 12 additions & 74 deletions narwhals/stable/v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,13 @@
from collections.abc import Iterable, Mapping, Sequence
from types import ModuleType

from typing_extensions import ParamSpec, Self
from typing_extensions import ParamSpec, Self, Unpack

from narwhals._translate import IntoArrowTable
from narwhals._translate import (
IntoArrowTable,
PassThroughUnknownV1 as PassThroughUnknown,
StrictUnknownV1 as StrictUnknown,
)
from narwhals._typing import (
Arrow,
Backend,
Expand Down Expand Up @@ -552,18 +556,6 @@ def from_native(
) -> DataFrame[IntoDataFrameT]: ...


@overload
def from_native(
native_object: T,
*,
strict: Literal[False],
eager_only: Literal[False] = ...,
eager_or_interchange_only: Literal[True],
series_only: Literal[False] = ...,
allow_series: None = ...,
) -> T: ...


@overload
def from_native(
native_object: IntoDataFrameT,
Expand All @@ -576,18 +568,6 @@ def from_native(
) -> DataFrame[IntoDataFrameT]: ...


@overload
def from_native(
native_object: T,
*,
strict: Literal[False],
eager_only: Literal[True],
eager_or_interchange_only: Literal[False] = ...,
series_only: Literal[False] = ...,
allow_series: None = ...,
) -> T: ...


@overload
def from_native(
native_object: IntoDataFrameT | IntoLazyFrameT | IntoSeriesT,
Expand Down Expand Up @@ -636,18 +616,6 @@ def from_native(
) -> LazyFrame[IntoLazyFrameT]: ...


@overload
def from_native(
native_object: T,
*,
strict: Literal[False],
eager_only: Literal[False] = ...,
eager_or_interchange_only: Literal[False] = ...,
series_only: Literal[False] = ...,
allow_series: None = ...,
) -> T: ...


@overload
def from_native(
native_object: IntoDataFrameT,
Expand Down Expand Up @@ -756,18 +724,6 @@ def from_native(
) -> DataFrame[IntoDataFrameT]: ...


@overload
def from_native(
native_object: T,
*,
pass_through: Literal[True],
eager_only: Literal[False] = ...,
eager_or_interchange_only: Literal[True],
series_only: Literal[False] = ...,
allow_series: None = ...,
) -> T: ...


@overload
def from_native(
native_object: IntoDataFrameT,
Expand All @@ -780,18 +736,6 @@ def from_native(
) -> DataFrame[IntoDataFrameT]: ...


@overload
def from_native(
native_object: T,
*,
pass_through: Literal[True],
eager_only: Literal[True],
eager_or_interchange_only: Literal[False] = ...,
series_only: Literal[False] = ...,
allow_series: None = ...,
) -> T: ...


@overload
def from_native(
native_object: IntoDataFrameT | IntoLazyFrameT | IntoSeriesT,
Expand Down Expand Up @@ -828,18 +772,6 @@ def from_native(
) -> DataFrame[IntoDataFrameT] | LazyFrame[IntoLazyFrameT]: ...


@overload
def from_native(
native_object: T,
*,
pass_through: Literal[True],
eager_only: Literal[False] = ...,
eager_or_interchange_only: Literal[False] = ...,
series_only: Literal[False] = ...,
allow_series: None = ...,
) -> T: ...


@overload
def from_native(
native_object: IntoDataFrameT,
Expand Down Expand Up @@ -912,6 +844,12 @@ def from_native(
) -> LazyFrame[IntoLazyFrameT]: ...


@overload
def from_native(native_object: T, **kwds: Unpack[PassThroughUnknown]) -> T: ...
@overload
def from_native(native_object: T, **kwds: Unpack[StrictUnknown]) -> T: ...


# All params passed in as variables
@overload
def from_native(
Expand Down
4 changes: 2 additions & 2 deletions narwhals/stable/v1/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
if TYPE_CHECKING:
from typing_extensions import TypeAlias

from narwhals._native import NativeDataFrame, NativeLazyFrame
from narwhals._native import NativeDataFrame, NativeDuckDB, NativeLazyFrame
from narwhals.stable.v1 import DataFrame, Expr, LazyFrame, Series

class DataFrameLike(Protocol):
Expand All @@ -24,7 +24,7 @@ def __dataframe__(self, *args: Any, **kwargs: Any) -> Any: ...
`nw.Expr`, e.g. `df.select('a')`.
"""

IntoDataFrame: TypeAlias = Union["NativeDataFrame", "DataFrameLike"]
IntoDataFrame: TypeAlias = Union["NativeDataFrame", "DataFrameLike", "NativeDuckDB"]
"""Anything which can be converted to a Narwhals DataFrame.

Use this if your function accepts a narwhalifiable object but doesn't care about its backend.
Expand Down
Loading
Loading