Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ci(typing): add pyright to ci #2035

Merged
merged 21 commits into from
Feb 20, 2025
Merged

ci(typing): add pyright to ci #2035

merged 21 commits into from
Feb 20, 2025

Conversation

dangotbanned
Copy link
Member

@dangotbanned dangotbanned commented Feb 17, 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

Comment on lines 41 to 44
- name: Run pyright
run: pyright
run: |
source .venv/bin/activate
pyright
Copy link
Member Author

@dangotbanned dangotbanned Feb 17, 2025

Choose a reason for hiding this comment

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

Expecting to get:

32 errors, 4 warnings, 0 informations

Update 1

Actually got:

25 errors, 4 warnings, 0 informations 

close enough

Copy link
Member Author

Choose a reason for hiding this comment

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

@MarcoGorelli that was easier than expected https://github.com/narwhals-dev/narwhals/actions/runs/13377926539/job/37360986379?pr=2035

Do you want me to fix these or merge with errors?

Copy link
Member Author

Choose a reason for hiding this comment

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

The ones that look like this would just need to be ignored, for now

image

Copy link
Member Author

@dangotbanned dangotbanned Feb 17, 2025

Choose a reason for hiding this comment

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

Copy link
Member Author

@dangotbanned dangotbanned Feb 18, 2025

Choose a reason for hiding this comment

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

Update 3

https://github.com/narwhals-dev/narwhals/actions/runs/13389097323/job/37392499933?pr=2035

8 errors, 0 warnings, 0 informations 

All of these seems to be roughly the same issue mentioned in (#2035 (comment)).

Will take a look at if there are any small changes that could be made on the protocol typing.

It pretty much boils down to defining that it should accept lazy and eager, but then later constraining to only eager or only lazy

class CompliantExpr(Protocol, Generic[CompliantSeriesT_co]):
_implementation: Implementation
_backend_version: tuple[int, ...]
_evaluate_output_names: Callable[
[CompliantDataFrame | CompliantLazyFrame], Sequence[str]
]

Edit

https://github.com/narwhals-dev/narwhals/actions/runs/13389346860/job/37393273341?pr=2035

See commit message: revert(typing): reintroduce arrow errors evaluate_output_names

10 errors, 0 warnings, 0 informations 

Copy link
Member Author

@dangotbanned dangotbanned Feb 18, 2025

Choose a reason for hiding this comment

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

@MarcoGorelli I think this is the missing piece in having all the typing tie in together nicely.

I've been playing around with this as a solution:

narwhals.typing diff

diff --git a/narwhals/typing.py b/narwhals/typing.py
index aa18a999..ca0a0607 100644
--- a/narwhals/typing.py
+++ b/narwhals/typing.py
@@ -70,24 +70,29 @@ class CompliantLazyFrame(Protocol):
         # (so, no broadcasting is necessary).
 
 
+CompliantFrameT_contra = TypeVar(
+    "CompliantFrameT_contra",
+    bound="CompliantDataFrame | CompliantLazyFrame",
+    contravariant=True,
+)
 CompliantSeriesT_co = TypeVar(
     "CompliantSeriesT_co", bound=CompliantSeries, covariant=True
 )
 
 
-class CompliantExpr(Protocol, Generic[CompliantSeriesT_co]):
+class CompliantExpr(Protocol, Generic[CompliantSeriesT_co, CompliantFrameT_contra]):
     _implementation: Implementation
     _backend_version: tuple[int, ...]
-    _evaluate_output_names: Callable[
-        [CompliantDataFrame | CompliantLazyFrame], Sequence[str]
-    ]
+    _evaluate_output_names: Callable[[CompliantFrameT_contra], Sequence[str]]
     _alias_output_names: Callable[[Sequence[str]], Sequence[str]] | None
     _depth: int
     _function_name: str
 
     def __call__(self, df: Any) -> Sequence[CompliantSeriesT_co]: ...
     def __narwhals_expr__(self) -> None: ...
-    def __narwhals_namespace__(self) -> CompliantNamespace[CompliantSeriesT_co]: ...
+    def __narwhals_namespace__(
+        self,
+    ) -> CompliantNamespace[CompliantSeriesT_co, CompliantFrameT_contra]: ...
     def is_null(self) -> Self: ...
     def alias(self, name: str) -> Self: ...
     def cast(self, dtype: DType) -> Self: ...
@@ -102,11 +107,13 @@ class CompliantExpr(Protocol, Generic[CompliantSeriesT_co]):
     def __pow__(self, other: Any) -> Self: ...
 
 
-class CompliantNamespace(Protocol, Generic[CompliantSeriesT_co]):
-    def col(self, *column_names: str) -> CompliantExpr[CompliantSeriesT_co]: ...
+class CompliantNamespace(Protocol, Generic[CompliantSeriesT_co, CompliantFrameT_contra]):
+    def col(
+        self, *column_names: str
+    ) -> CompliantExpr[CompliantSeriesT_co, CompliantFrameT_contra]: ...
     def lit(
         self, value: Any, dtype: DType | None
-    ) -> CompliantExpr[CompliantSeriesT_co]: ...
+    ) -> CompliantExpr[CompliantSeriesT_co, CompliantFrameT_contra]: ...
 
 
 class SupportsNativeNamespace(Protocol):
@@ -306,7 +313,7 @@ if TYPE_CHECKING:
     # This one needs to be in TYPE_CHECKING to pass on 3.9,
     # and can only be defined after CompliantExpr has been defined
     IntoCompliantExpr: TypeAlias = (
-        CompliantExpr[CompliantSeriesT_co] | CompliantSeriesT_co
+        CompliantExpr[CompliantSeriesT_co, CompliantFrameT_contra] | CompliantSeriesT_co
     )

This checks out, but would require lots of small changes everywhere that CompliantNamespace and CompliantExpr are used.

Where?

FYI, I'm just exploring here, not planning to include this in the PR πŸ™‚

image

The reason I say "missing piece", is because it would fill in the only non-generic hole in https://github.com/narwhals-dev/narwhals/blob/9571ad67e0da05d753e3595363b1537a3cef40bf/narwhals/_expression_parsing.py
I think you then might be able to start moving some more of the methods up to the Compliant*-level.
Or at-least their signatures?

Copy link
Member Author

@dangotbanned dangotbanned Feb 18, 2025

Choose a reason for hiding this comment

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

Which is just +1 commit on top of (#2035)

Passes pyright and mypy fully for me locally

Fixes:
```py
to_py_scalar_test.py:46:39 - error: Second argument to "isinstance" must be a class or tuple of classes
     Generic type with type arguments not allowed for instance or class checks
```
docs state `str` is allowed
Shape typing is an ongoing issue numpy/numpy#28076
@@ -15,7 +15,7 @@
def test_with_columns(constructor_eager: ConstructorEager) -> None:
result = (
nw.from_native(constructor_eager(data))
.with_columns(d=np.array([4, 5]))
.with_columns(d=np.array([4, 5])) # pyright: ignore[reportArgumentType]
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 Author

Choose a reason for hiding this comment

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

Not that happy about this.
Might need to change _(1|2)DArray to just both alias _AnyDArray

narwhals/narwhals/typing.py

Lines 270 to 273 in 9571ad6

_NDArray: TypeAlias = "np.ndarray[_ShapeT, Any]"
_1DArray: TypeAlias = "_NDArray[tuple[int]]" # noqa: PYI042, PYI047
_2DArray: TypeAlias = "_NDArray[tuple[int, int]]" # noqa: PYI042, PYI047
_AnyDArray: TypeAlias = "_NDArray[tuple[int, ...]]" # noqa: PYI047

Maybe with a doc linking back to the numpy issue and a description of what we actually mean?

The problem stems from most functions returning shape: tuple[int, ...] - which fails both _(1|2)DArray.

- Now passes both type checkers, without ignore comments
- Pass `polars` an iterator directly
- `aggs` is only iterated over **once** on the `polars` side, rather than **twice** in `narwhals` + again in `polars`

#2035 (comment)
To demonstrate that this appears on every use of `CompliantExpr`
#2035 (comment)
@dangotbanned dangotbanned changed the title ci(DRAFT): add pyright to ci? ci(typing): add pyright to ci Feb 18, 2025
@dangotbanned dangotbanned marked this pull request as ready for review February 18, 2025 13:15
dangotbanned added a commit that referenced this pull request Feb 18, 2025
- Will always be the same result on every call to the inner `evaluate_output_names`
- Unsure how frequent it would be called though, but noticed in (#2035 (comment))
no runtime change, but now we get syntax highlighting
`dask_expr` already imports this for `wraps` (and loads of dependencies), so we didn't even get a performance boost
@MarcoGorelli
Copy link
Member

thanks - generally looks good, if we can get it green i think we can merge!

@dangotbanned
Copy link
Member Author

dangotbanned commented Feb 18, 2025

thanks - generally looks good, if we can get it green i think we can merge!

Thanks @MarcoGorelli, did you see this? (#2035 (comment))

That gets everything green, but it requires a bigger change - which I split out into another branch for comparison

@dangotbanned
Copy link
Member Author

thanks - generally looks good, if we can get it green i think we can merge!

Thanks @MarcoGorelli, did you see this? (#2035 (comment))

That gets everything green, but it requires a bigger change - which I split out into another branch for comparison

@MarcoGorelli could we merge this if I just add this ignore (b26c94c) for all the current errors?

We can solve these with either (or maybe both?):

But I'd rather get this part in first because it doesn't redefine any protocols/typevars

@MarcoGorelli
Copy link
Member

sure, happy to do things incrementally πŸ‘

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.

feel free to ship it once ignores are in and CI is green

@dangotbanned
Copy link
Member Author

sure, happy to do things incrementally πŸ‘

Thanks @MarcoGorelli

@dangotbanned dangotbanned merged commit 2abed29 into main Feb 20, 2025
28 checks passed
@dangotbanned dangotbanned deleted the dev-pyright branch February 20, 2025 10:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants