Skip to content

Conversation

@dangotbanned
Copy link
Member

@dangotbanned dangotbanned commented Mar 24, 2025

What type of PR is this? (check all applicable)

  • 💾 Refactor
  • ✨ Feature
  • 🐛 Bug Fix
  • 🔧 Optimization
  • 📝 Documentation
  • ✅ Test
  • 🐳 Other

Related issues

Checklist

  • Code follows style guide (ruff)
  • Tests added
  • Documented the changes

If you have comments or can explain your changes, please do so below

  • This PR is the first big step towards (API: io functions for v2 #2116), following all of the work in (Formalise Compliant* protocols #2230)
  • I chose from_numpy as all the supported backends already had a to_numpy, to complete the NumpyConvertible pair
  • Experimenting with the possibility of typing support from functions.py
    • Everything checks out with pyright, but mypy had issues with defining overload(s) on Implementation
    • Another option could be having a nw.Namespace class to provide a scope for the single Implementation -> CompliantNamespace context

`to_numpy` is already handled via `__getattr__`
Don't need to explicitly define in `CompliantDataFrame`, as it gets it from `NumpyConvertible`
The module already has a top-level `pl` import
- I thought what I had was allowed
- but caused a runtime error in subclasses that *didn't* fill the missing `FromNumpyDT_contra`

> TypeError: Too few arguments for <class 'narwhals._compliant.dataframe.CompliantDataFrame'>; actual 3, expected at least 4
One step towards `CompliantNamespace.from_numpy`
Adding the annotation of `CompliantNamespace` revealed a lot of gaps
Towards fixing this error, but without subclassing
```py
narwhals/utils.py:363: error: Incompatible return value type (got "PolarsNamespace", expected "CompliantNamespace[Any, Any]")  [return-value]
                return PolarsNamespace(backend_version=backend_version, version=version)
                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
narwhals/utils.py:363: note: Following member(s) of "PolarsNamespace" have conflicts:
narwhals/utils.py:363: note:     selectors: expected "CompliantSelectorNamespace[Any, Any]", got "PolarsSelectors"
```
Now we've got access to both `from_numpy` constructors from `Implementation`!
- Important to this PR is `is_eager_allowed`
- Allows us to match `PolarsNamespace`, without defining redundant stuff in protocols/classes
- Spent too long trying to solve this
- `pyright` has no issues
- I can see moving `_to_compliant_namespace` into a function working
  - But I don't want that, and seems like a bug
@dangotbanned dangotbanned changed the title refactor: *(Namespace|DataFrame).from_numpy refactor(RFC): *(Namespace|DataFrame).from_numpy Mar 24, 2025
@dangotbanned dangotbanned marked this pull request as ready for review March 24, 2025 21:44
@dangotbanned
Copy link
Member Author

dangotbanned commented Mar 24, 2025

The longer-term goal would be to transition more of (functions|translate).py into things implemented via protocols defined in _translate.py

We might already have everything in place for new_series

@dangotbanned dangotbanned mentioned this pull request Mar 24, 2025
6 tasks
Copy link
Member

@MarcoGorelli MarcoGorelli left a comment

Choose a reason for hiding this comment

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

nice, i like this!

just some typing failure in ci but i'm sure you know how to fix that 😄

@dangotbanned
Copy link
Member Author

nice, i like this!

just some typing failure in ci but i'm sure you know how to fix that 😄

68747470733a2f2f692e666c756666792e63632f375758717072577a4474437062667072425a4731486e676e703748377066516d2e676966

Thanks @MarcoGorelli

Will investigate in #2283 (comment)

@dangotbanned dangotbanned changed the title refactor(RFC): *(Namespace|DataFrame).from_numpy refactor: *(Namespace|DataFrame).from_numpy Mar 26, 2025
Comment on lines 134 to 139
def concat(
self: Self,
items: Iterable[PolarsLazyFrame],
*,
how: Literal["vertical", "horizontal", "diagonal"],
) -> PolarsLazyFrame: ...

def concat(
self: Self,
items: Iterable[PolarsDataFrame] | Iterable[PolarsLazyFrame],
items: Iterable[FrameT],
*,
how: Literal["vertical", "horizontal", "diagonal"],
) -> PolarsDataFrame | PolarsLazyFrame:
Copy link
Member Author

@dangotbanned dangotbanned Mar 26, 2025

Choose a reason for hiding this comment

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

Resolves (#2283 (comment)) via (0e67a1e)

@MarcoGorelli
I had no idea a TypeVar could be used this way 🤔

One rule I thought was that a TypeVar on a parameter annotation must either:

  • Be present in the class def (e.g. Generic[T] or Protocol[T])
  • Or appear in the return type

FrameT = TypeVar("FrameT", PolarsDataFrame, PolarsLazyFrame)

FrameT doesn't meet either of those rules - so I'd have expected this to get rejected.

But why work??

My best guess is there seems to be an understanding of what FrameT represents - and how that is consistent with:

def concat(
self,
items: Iterable[CompliantFrameT],
*,
how: Literal["horizontal", "vertical", "diagonal"],
) -> CompliantFrameT: ...

CompliantFrameT = TypeVar("CompliantFrameT", bound=CompliantFrameAny)

CompliantDataFrameAny: TypeAlias = "CompliantDataFrame[Any, Any, Any]"
CompliantLazyFrameAny: TypeAlias = "CompliantLazyFrame[Any, Any]"
CompliantFrameAny: TypeAlias = "CompliantDataFrameAny | CompliantLazyFrameAny"

@dangotbanned dangotbanned merged commit c056b44 into main Mar 26, 2025
28 checks passed
@dangotbanned dangotbanned deleted the from-numpy-2d-ns branch March 26, 2025 12:27
dangotbanned added a commit that referenced this pull request Mar 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants