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

Fix static typing with Matplotlib 3.8 #8030

Merged
merged 25 commits into from
Sep 17, 2023
Merged

Conversation

headtr1ck
Copy link
Collaborator

@headtr1ck
Copy link
Collaborator Author

Any Idea how I can run mypy vs. upstream?
Do we add a new workflow?

@Illviljan Illviljan added the run-upstream Run upstream CI label Jul 30, 2023
@Illviljan
Copy link
Contributor

The label run-upstream also runs a mypy check now.
There's a few errors left:

xarray/core/options.py:12: error: Cannot assign to a type  [misc]
xarray/core/options.py:12: error: Incompatible types in assignment (expression has type "type[str]", variable has type "type[Colormap]")  [assignment]
xarray/plot/utils.py:813: error: Argument 1 to "set_xlim" of "_AxesBase" has incompatible type "_SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]"; expected "float | tuple[float, float] | None"  [arg-type]
xarray/plot/utils.py:815: error: Argument 1 to "set_ylim" of "_AxesBase" has incompatible type "_SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]"; expected "float | tuple[float, float] | None"  [arg-type]
xarray/plot/utils.py:1349: error: Unsupported operand types for * ("_SupportsArray[dtype[Any]]" and "float")  [operator]
xarray/plot/utils.py:1349: error: Unsupported operand types for * ("_NestedSequence[_SupportsArray[dtype[Any]]]" and "float")  [operator]
xarray/plot/utils.py:1349: error: Unsupported operand types for * ("_NestedSequence[bool | int | float | complex | str | bytes]" and "float")  [operator]
xarray/plot/utils.py:1349: note: Left operand is of type "_SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]"
xarray/plot/utils.py:1349: error: Unsupported operand types for * ("str" and "float")  [operator]
xarray/plot/utils.py:1349: error: Unsupported operand types for * ("bytes" and "float")  [operator]
xarray/plot/utils.py:1350: error: Item "_SupportsArray[dtype[Any]]" of "_SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]" has no attribute "mask"  [union-attr]
xarray/plot/utils.py:1350: error: Item "_NestedSequence[_SupportsArray[dtype[Any]]]" of "_SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]" has no attribute "mask"  [union-attr]
xarray/plot/utils.py:1350: error: Item "int" of "_SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]" has no attribute "mask"  [union-attr]
xarray/plot/utils.py:1350: error: Item "float" of "_SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]" has no attribute "mask"  [union-attr]
xarray/plot/utils.py:1350: error: Item "complex" of "_SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]" has no attribute "mask"  [union-attr]
xarray/plot/utils.py:1350: error: Item "str" of "_SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]" has no attribute "mask"  [union-attr]
xarray/plot/utils.py:1350: error: Item "bytes" of "_SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]" has no attribute "mask"  [union-attr]
xarray/plot/utils.py:1350: error: Item "_NestedSequence[bool | int | float | complex | str | bytes]" of "_SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]" has no attribute "mask"  [union-attr]
xarray/plot/utils.py:1351: error: Item "_SupportsArray[dtype[Any]]" of "_SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]" has no attribute "mask"  [union-attr]
xarray/plot/utils.py:1351: error: Item "_NestedSequence[_SupportsArray[dtype[Any]]]" of "_SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]" has no attribute "mask"  [union-attr]
xarray/plot/utils.py:1351: error: Item "int" of "_SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]" has no attribute "mask"  [union-attr]
xarray/plot/utils.py:1351: error: Item "float" of "_SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]" has no attribute "mask"  [union-attr]
xarray/plot/utils.py:1351: error: Item "complex" of "_SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]" has no attribute "mask"  [union-attr]
xarray/plot/utils.py:1351: error: Item "str" of "_SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]" has no attribute "mask"  [union-attr]
xarray/plot/utils.py:1351: error: Item "bytes" of "_SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]" has no attribute "mask"  [union-attr]
xarray/plot/utils.py:1351: error: Item "_NestedSequence[bool | int | float | complex | str | bytes]" of "_SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | int | float | complex | str | bytes | _NestedSequence[bool | int | float | complex | str | bytes]" has no attribute "mask"  [union-attr]
xarray/plot/facetgrid.py:684: error: "FigureCanvasBase" has no attribute "get_renderer"  [attr-defined]
xarray/plot/accessor.py:182: error: Overloaded function signatures 1 and 2 overlap with incompatible return types  [misc]
xarray/plot/accessor.py:182: error: Overloaded function signatures 1 and 3 overlap with incompatible return types  [misc]
xarray/plot/accessor.py:309: error: Overloaded function signatures 1 and 2 overlap with incompatible return types  [misc]
xarray/plot/accessor.py:309: error: Overloaded function signatures 1 and 3 overlap with incompatible return types  [misc]
xarray/plot/accessor.py:428: error: Overloaded function implementation cannot produce return type of signature 2  [misc]
xarray/plot/accessor.py:428: error: Overloaded function implementation cannot produce return type of signature 3  [misc]
xarray/plot/accessor.py:433: error: Overloaded function signatures 1 and 2 overlap with incompatible return types  [misc]
xarray/plot/accessor.py:433: error: Overloaded function signatures 1 and 3 overlap with incompatible return types  [misc]
xarray/plot/accessor.py:552: error: Overloaded function implementation cannot produce return type of signature 2  [misc]
xarray/plot/accessor.py:552: error: Overloaded function implementation cannot produce return type of signature 3  [misc]
xarray/plot/accessor.py:557: error: Overloaded function signatures 1 and 2 overlap with incompatible return types  [misc]
xarray/plot/accessor.py:557: error: Overloaded function signatures 1 and 3 overlap with incompatible return types  [misc]
xarray/plot/accessor.py:676: error: Overloaded function implementation cannot produce return type of signature 2  [misc]
xarray/plot/accessor.py:676: error: Overloaded function implementation cannot produce return type of signature 3  [misc]
xarray/plot/accessor.py:681: error: Overloaded function signatures 1 and 2 overlap with incompatible return types  [misc]
xarray/plot/accessor.py:681: error: Overloaded function signatures 1 and 3 overlap with incompatible return types  [misc]
xarray/plot/accessor.py:800: error: Overloaded function implementation cannot produce return type of signature 2  [misc]
xarray/plot/accessor.py:800: error: Overloaded function implementation cannot produce return type of signature 3  [misc]
xarray/plot/accessor.py:948: error: Overloaded function signatures 1 and 2 overlap with incompatible return types  [misc]
xarray/plot/accessor.py:948: error: Overloaded function signatures 1 and 3 overlap with incompatible return types  [misc]
xarray/plot/accessor.py:1075: error: Overloaded function signatures 1 and 2 overlap with incompatible return types  [misc]
xarray/plot/accessor.py:1075: error: Overloaded function signatures 1 and 3 overlap with incompatible return types  [misc]
xarray/plot/accessor.py:1190: error: Overloaded function signatures 1 and 2 overlap with incompatible return types  [misc]
xarray/plot/accessor.py:1190: error: Overloaded function signatures 1 and 3 overlap with incompatible return types  [misc]
xarray/core/coordinates.py:144: error: Module has no attribute "cumproduct"  [attr-defined]
xarray/plot/dataset_plot.py:324: error: Overloaded function signatures 1 and 2 overlap with incompatible return types  [misc]
xarray/plot/dataset_plot.py:324: error: Overloaded function signatures 1 and 3 overlap with incompatible return types  [misc]
xarray/plot/dataset_plot.py:478: error: Overloaded function signatures 1 and 2 overlap with incompatible return types  [misc]
xarray/plot/dataset_plot.py:478: error: Overloaded function signatures 1 and 3 overlap with incompatible return types  [misc]
xarray/plot/dataset_plot.py:649: error: Function gets multiple values for keyword argument "x"  [misc]
xarray/plot/dataset_plot.py:649: error: Function gets multiple values for keyword argument "y"  [misc]
xarray/plot/dataset_plot.py:649: error: Function gets multiple values for keyword argument "u"  [misc]
xarray/plot/dataset_plot.py:649: error: Function gets multiple values for keyword argument "v"  [misc]
xarray/plot/dataset_plot.py:649: error: Function gets multiple values for keyword argument "density"  [misc]
xarray/plot/dataset_plot.py:649: error: Function gets multiple values for keyword argument "linewidth"  [misc]
xarray/plot/dataset_plot.py:649: error: Function gets multiple values for keyword argument "color"  [misc]
xarray/plot/dataset_plot.py:649: error: Function gets multiple values for keyword argument "cmap"  [misc]
xarray/plot/dataset_plot.py:649: error: Function gets multiple values for keyword argument "norm"  [misc]
xarray/plot/dataset_plot.py:649: error: Function gets multiple values for keyword argument "arrowsize"  [misc]
xarray/plot/dataset_plot.py:649: error: Function gets multiple values for keyword argument "arrowstyle"  [misc]
xarray/plot/dataset_plot.py:649: error: Function gets multiple values for keyword argument "minlength"  [misc]
xarray/plot/dataset_plot.py:649: error: Function gets multiple values for keyword argument "transform"  [misc]
xarray/plot/dataset_plot.py:649: error: Function gets multiple values for keyword argument "zorder"  [misc]
xarray/plot/dataset_plot.py:649: error: Function gets multiple values for keyword argument "start_points"  [misc]
xarray/plot/dataset_plot.py:649: error: Function gets multiple values for keyword argument "maxlength"  [misc]
xarray/plot/dataset_plot.py:649: error: Function gets multiple values for keyword argument "integration_direction"  [misc]
xarray/plot/dataset_plot.py:649: error: Function gets multiple values for keyword argument "broken_streamlines"  [misc]
xarray/plot/dataset_plot.py:649: error: Argument 1 has incompatible type "*list[ndarray[Any, Any]]"; expected "float | tuple[float, float]"  [arg-type]
xarray/plot/dataset_plot.py:649: error: Argument 1 has incompatible type "*list[ndarray[Any, Any]]"; expected "str | Colormap | None"  [arg-type]
xarray/plot/dataset_plot.py:649: error: Argument 1 has incompatible type "*list[ndarray[Any, Any]]"; expected "str | Normalize | None"  [arg-type]
xarray/plot/dataset_plot.py:649: error: Argument 1 has incompatible type "*list[ndarray[Any, Any]]"; expected "float"  [arg-type]
xarray/plot/dataset_plot.py:649: error: Argument 1 has incompatible type "*list[ndarray[Any, Any]]"; expected "str | ArrowStyle"  [arg-type]
xarray/plot/dataset_plot.py:649: error: Argument 1 has incompatible type "*list[ndarray[Any, Any]]"; expected "Transform | None"  [arg-type]
xarray/plot/dataset_plot.py:649: error: Argument 1 has incompatible type "*list[ndarray[Any, Any]]"; expected "float | None"  [arg-type]
xarray/plot/dataset_plot.py:649: error: Argument 1 has incompatible type "*list[ndarray[Any, Any]]"; expected "Literal['forward', 'backward', 'both']"  [arg-type]
xarray/plot/dataset_plot.py:649: error: Argument 1 has incompatible type "*list[ndarray[Any, Any]]"; expected "bool"  [arg-type]
xarray/plot/dataset_plot.py:751: error: Overloaded function signatures 1 and 2 overlap with incompatible return types  [misc]
xarray/plot/dataset_plot.py:751: error: Overloaded function signatures 1 and 3 overlap with incompatible return types  [misc]
xarray/plot/dataarray_plot.py:718: error: Incompatible return value type (got "tuple[ndarray[Any, Any] | list[ndarray[Any, Any]], ndarray[Any, Any], BarContainer | Polygon | list[BarContainer | Polygon]]", expected "tuple[ndarray[Any, Any], ndarray[Any, Any], BarContainer]")  [return-value]
xarray/plot/dataarray_plot.py:1114: error: Overloaded function signatures 1 and 2 overlap with incompatible return types  [misc]
xarray/plot/dataarray_plot.py:1114: error: Overloaded function signatures 1 and 3 overlap with incompatible return types  [misc]
xarray/plot/dataarray_plot.py:1269: error: Argument 1 to "scatter" of "Axes" has incompatible type "*list[ndarray[Any, Any]]"; expected "Sequence[tuple[float, float, float] | str | str | tuple[float, float, float, float] | tuple[tuple[float, float, float] | str, float] | tuple[tuple[float, float, float, float], float]] | tuple[float, float, float] | str | str | tuple[float, float, float, float] | tuple[tuple[float, float, float] | str, float] | tuple[tuple[float, float, float, float], float] | None"  [arg-type]
xarray/plot/dataarray_plot.py:1269: error: Argument 1 to "scatter" of "Axes" has incompatible type "*list[ndarray[Any, Any]]"; expected "str | Path | MarkerStyle | None"  [arg-type]
xarray/plot/dataarray_plot.py:1269: error: Argument 1 to "scatter" of "Axes" has incompatible type "*list[ndarray[Any, Any]]"; expected "str | Colormap | None"  [arg-type]
xarray/plot/dataarray_plot.py:1269: error: Argument 1 to "scatter" of "Axes" has incompatible type "*list[ndarray[Any, Any]]"; expected "str | Normalize | None"  [arg-type]
xarray/plot/dataarray_plot.py:1269: error: Argument 1 to "scatter" of "Axes" has incompatible type "*list[ndarray[Any, Any]]"; expected "float | None"  [arg-type]
xarray/plot/dataarray_plot.py:1269: error: Argument 1 to "scatter" of "Axes" has incompatible type "*list[ndarray[Any, Any]]"; expected "float | Sequence[float] | None"  [arg-type]
xarray/plot/dataarray_plot.py:1270: error: Argument 2 to "_add_labels" has incompatible type "list[ndarray[Any, Any]]"; expected "Iterable[DataArray]"  [arg-type]
xarray/plot/dataarray_plot.py:1664: error: Overloaded function signatures 1 and 2 overlap with incompatible return types  [misc]
xarray/plot/dataarray_plot.py:1664: error: Overloaded function signatures 1 and 3 overlap with incompatible return types  [misc]
xarray/plot/dataarray_plot.py:1883: error: Overloaded function signatures 1 and 2 overlap with incompatible return types  [misc]
xarray/plot/dataarray_plot.py:1883: error: Overloaded function signatures 1 and 3 overlap with incompatible return types  [misc]
xarray/plot/dataarray_plot.py:2019: error: Overloaded function signatures 1 and 2 overlap with incompatible return types  [misc]
xarray/plot/dataarray_plot.py:2019: error: Overloaded function signatures 1 and 3 overlap with incompatible return types  [misc]
xarray/plot/dataarray_plot.py:2155: error: Overloaded function signatures 1 and 2 overlap with incompatible return types  [misc]
xarray/plot/dataarray_plot.py:2155: error: Overloaded function signatures 1 and 3 overlap with incompatible return types  [misc]
xarray/tests/test_groupby.py:731: error: Argument 1 to "groupby" of "Dataset" has incompatible type "ndarray[Any, dtype[signedinteger[Any]]]"; expected "Hashable | DataArray | IndexVariable"  [arg-type]
xarray/tests/test_groupby.py:731: note: Following member(s) of "ndarray[Any, dtype[signedinteger[Any]]]" have conflicts:
xarray/tests/test_groupby.py:731: note:     __hash__: expected "Callable[[], int]", got "None"
xarray/tests/test_coding_strings.py:36: error: Unused "type: ignore" comment  [unused-ignore]

@headtr1ck
Copy link
Collaborator Author

The label run-upstream also runs a mypy check now.

Oh cool, thanks!

There's a few errors left

I'll have to finish this after the holidays :)
Trying to solve the scatter list unpacking thing was taking me too long. Still don't know how to best approach it .

@ksunden
Copy link
Contributor

ksunden commented Aug 2, 2023

It looks intimidating, but its actually only a small number of (similar) tweaks to solve most of the flags.

The biggest/most complicated ones are the overloaded signatures (which I don't think are directly matplotlib, just perhaps its able to check them a bit more thoroughly now.) The core is that None is hashable, and thus doesn't quite differentiate the overloads... I thought it would just match the first one it saw, but I guess not?

I think most of the remaining mpl ones are "real" from our perspective, though I do have a patch that I'm preparing to PR on our end for a good number of the utils.py:1349 issues at least.

More detailed analysis/breakdown
xarray/core/options.py:12
   - mypy is not happy with the try/except which seems to be guarding against mpl not being installed at type check time, but not sure why that is needed

xarray/plot/accessor.py:

- Many instances of overloaded functions being incompatible
   - Mostly due to `None` being `Hashable`, but that being the distinction to differentiate return type
   - Not directly mpl, I don't think, but related
   - Problem recurs in dataarray_plot and dataset_plot for many of the mpl wrappers
   - Not actually sure why this shows up now and not before

   - Overloaded function implementation cannot produce return type of signature
      - Some are missing a `| FacetGrid` in the implementation definition (I think it would also be valid to just remove that type hint as the point of overload is to serve as the type hints for a more generic impl)
      - Some (in particular in other files, I think) may be occluded by decorators

xarray/plot/utils.py:

- xlim/ylim
   - matplotlib/matplotlib#25711 fixed the acceptance of (float, float) tuples, xarray may still be too broad, though (i.e. a list or ndarray is not _quite_ right, as length-2 is important)
- Norms as masked arrays in ~line 1350
   - We were being a bit broad, we can actually specify that the return of `Normalize.__call__` is a masked array, fixes all of those 
   - Will PR mpl shortly


xarray/plot/facetgrid.py:684
   - get_renderer not provided for all backends FigureCanvas objects (only provided by agg, cairo, pgf)
   - See discussion [here](https://github.com/pydata/xarray/pull/7787#issuecomment-1528091492)

xarray/core/coordinates.py:144
   - not mpl, but numpy 2.0 preparedness, `s/cumproduct/cumprod`

xarray/plot/dataset_plot.py:649
- *args passed to streamplot, typechecker unsatisfied
-  Changing `args` from a list to a tuple resolves most of it, as the length/which args are passed positionally is known to mypy
   - Mypy still thinks the first four arguments can be repeated, probably from `**kwargs` (pyright seems to not complain)
   - May just need to be `# type: ignore`'d at which point up to you if you want to tuple-ify it
- We have some thoughts towards improving type hinting *args/**kwargs, but it is fairly complicated and unlikely to be done for mpl3.8

xarray/plot/dataarray_plot.py:718
- technically could be an overload, but some of our machinery for propegating types to pyplot doesn't handle those
- based on the `histtype` arg, which is technically available (via **kwargs)

xarray/plot/dataarray_plot.py:1269
- *args passed to scatter, typchecker unsatisfied (similar to datset_plot:649)
- Not as easy to just convert to a tuple where you can say "there are between 2 and 3 items in this" so that the `*args` knows
- 3D axes scatter untyped but have a different signature (because of Z) than 2d axes scatter

xarray/plot/dataarray_plot.py:1270
- actually incorrect according to the signature of the private method, it is preprocessed to a numpy array but the private function expects a DataArray
A diff that may help, but not sure its precisely what you want

Does a couple things:

  • attempts to use ParamSpec to annotate the _plot1d decorator, but not sure it's quite right.
  • Does something for scatter, but probably not quite right (it handles 2D and 3D, but not cases where only one dataarray is provided. I'll let you decide if it is a route you wish to continue... it works and mostly doesn't complain, but its not the prettiest code
  • Makes streamplot variable a tuple instead of a list
    • fixes most (but not quite all) of the flags from that area
diff --git a/xarray/plot/dataarray_plot.py b/xarray/plot/dataarray_plot.py
index 4318b3db..ce8c4e87 100644
--- a/xarray/plot/dataarray_plot.py
+++ b/xarray/plot/dataarray_plot.py
@@ -34,6 +34,8 @@ from xarray.plot.utils import (
 )
 
 if TYPE_CHECKING:
+    from typing import ParamSpec, TypeVar
+
     from matplotlib.axes import Axes
     from matplotlib.collections import PathCollection, QuadMesh
     from matplotlib.colors import Colormap, Normalize
@@ -53,6 +55,9 @@ if TYPE_CHECKING:
     )
     from xarray.plot.facetgrid import FacetGrid
 
+    P = ParamSpec("P")
+    R = TypeVar("R")
+
 _styles: MutableMapping[str, Any] = {
     # Add a white border to make it easier seeing overlapping markers:
     "scatter.edgecolors": "w",
@@ -718,7 +723,7 @@ def hist(
     return primitive
 
 
-def _plot1d(plotfunc):
+def _plot1d(plotfunc: Callable[P, R]) -> Callable[P, R | FacetGrid[DataArray]]:
     """Decorator for common 1d plotting logic."""
     commondoc = """
     Parameters
@@ -1075,9 +1080,9 @@ def _plot1d(plotfunc):
     # we want to actually expose the signature of newplotfunc
     # and not the copied **kwargs from the plotfunc which
     # and not the copied **kwargs from the plotfunc which
     from matplotlib.axes import Axes
     from matplotlib.collections import PathCollection, QuadMesh
     from matplotlib.colors import Colormap, Normalize
@@ -1238,8 +1243,8 @@ def scatter(
 
 @_plot1d
 def scatter(
-    xplt: DataArray | None,
-    yplt: DataArray | None,
+    xplt: DataArray,
+    yplt: DataArray,
     ax: Axes,
     add_labels: bool | Iterable[bool] = True,
     **kwargs,
@@ -1261,13 +1266,15 @@ def scatter(
     if sizeplt is not None:
         kwargs.update(s=sizeplt.to_numpy().ravel())
 
-    axis_order = ["x", "y", "z"]
-
-    plts_dict: dict[str, DataArray | None] = dict(x=xplt, y=yplt, z=zplt)
-    plts_or_none = [plts_dict[v] for v in axis_order]
-    plts = [p.to_numpy().ravel() for p in plts_or_none if p is not None]
-    primitive = ax.scatter(*plts, **kwargs)
-    _add_labels(add_labels, plts, ("", "", ""), (True, False, False), ax)
+    xplt_np = xplt.to_numpy().ravel()
+    yplt_np = yplt.to_numpy().ravel()
+    if zplt is None:
+        primitive = ax.scatter(xplt_np, yplt_np, **kwargs)
+        _add_labels(add_labels, [xplt, yplt], ("", "", ""), (True, False, False), ax)
+    else:
+        zplt_np = zplt.to_numpy().ravel()
+        primitive = ax.scatter(xplt_np, yplt_np, zplt_np, **kwargs)
+        _add_labels(add_labels, [xplt, yplt], ("", "", ""), (True, False, False), ax)
 
     return primitive
 
diff --git a/xarray/plot/dataset_plot.py b/xarray/plot/dataset_plot.py
index b0774c31..7a70f1d4 100644
--- a/xarray/plot/dataset_plot.py
+++ b/xarray/plot/dataset_plot.py
@@ -632,7 +632,7 @@ def streamplot(
         du = du.transpose(ydim, xdim)
         dv = dv.transpose(ydim, xdim)
 
-    args = [dx.values, dy.values, du.values, dv.values]
+    args = (dx.values, dy.values, du.values, dv.values)
     hue = kwargs.pop("hue")
     cmap_params = kwargs.pop("cmap_params")
 

ksunden added a commit to ksunden/matplotlib that referenced this pull request Aug 2, 2023
xref pydata/xarray#8030

We were perhaps overly permissive in the return type, I'm pretty sure we always return Masked Array, so may as well say so, so that downstream can rely on that (which they already are, to be clear...)
@headtr1ck
Copy link
Collaborator Author

headtr1ck commented Aug 3, 2023

@ksunden thanks for the detailed breakdown. I'm currently on holiday (mobile only) so I will not continue this for another three weeks. If you or someone else is motivated in the meantime, feel free to continue where I left.

Just some remarks:
The None is hashable problem we have encountered already a few times. For the overloads usually an ignore is the way to go (this was even recommended by the mypy defs) and should even correctly resolve the trying.

We didn't use ParamSpec so far because it is python 3.10 only, but I guess typing_extensions should have it as well. (In that regard, you should check if your type annotations work on the oldest python version that mpl supports, we had some complaints about this in the past).

Edit: actually we just started using Self which is python 3.11, so it should definitely work.

I am not sure if restricting xlim to a length two tuple is a good idea. I guess it depends on what you want to achieve with type annotations. I assume many people will pass a list to this method and would get errors on code that actually runs just fine. If your goal is inline editor hints on what to pass, I agree that the tuple is more clear.

@ksunden
Copy link
Contributor

ksunden commented Aug 3, 2023

WRT ParamSpec, we only use it a handful of times, and do import from typing_extensions when we do (and are guarded by if TYPE_CHECKING blocks or in pyi stub files, so while I was slightly lazy in my playing around to use from typing, I expect that if it were to be included, it would be similar (either guarded and/or explicitly from typing_extensions)

WRT xlim tuples, I do wish there was a better way to say "length-2 sequence", but that is not supported by the current type system (and probably won't be). But when other length sequences will break, I still end up on the side of "more useful than harmful", but not as solidly as I once was, admittedly. xref python/mypy#8441 It's been proposed an thought of, but is low priority.

@headtr1ck
Copy link
Collaborator Author

@ksunden I'm finally continuing this PR here :)

Would you mind changing the return type of matplotlib.colors.Normalize.__call__ to np.ma.MaskedArray, which I believe is the correct one.

It is difficult to work with ArrayLike return values, because this is a huge Union of different types and basically always creates problems.
I remember reading somewhere that Unions should not be used as return types unless they can be resolved via overloads. If it is unavoidable, use Any instead. Anyway, better to use the actual type here :)

@headtr1ck
Copy link
Collaborator Author

Would you mind changing the return type of matplotlib.colors.Normalize.__call__ to np.ma.MaskedArray, which I believe is the correct one.

Nvmd, that has happened already, just forgot to update my local mpl repo...

@headtr1ck headtr1ck marked this pull request as ready for review September 11, 2023 19:49
@headtr1ck
Copy link
Collaborator Author

headtr1ck commented Sep 11, 2023

@Illviljan Maybe you could do a quick review already and help with the remaining errors.

CI Additional:
I'm a bit puzzled on how we treat the "unused-ignore" errors that occur because upstream requires them but the released version does not. One could add type: ignore[xxx, unused-ignore] but somehow that defeats the whole purpose?

Ci Upstream:

xarray/plot/accessor.py:431: error: Overloaded function implementation cannot produce return type of signature 2 [misc]

etc. The functools.wraps seems to not work anymore :/
This is so difficult to get right...
Maybe for the accessor module we should move to a .pyi file and simply copy-paste the correct types?

@headtr1ck
Copy link
Collaborator Author

headtr1ck commented Sep 11, 2023

@ksunden I think I have resolved all parts that are dependent on matplotlib.
One remaining improvement could be done:
You could add overloads to hist (Possibly to other plot functions as well). The Unions in the return types are a bit annoying.
If you prefer, I can open a ticket for this.

Thanks for the typing improvements!

@ksunden
Copy link
Contributor

ksunden commented Sep 11, 2023

You could add overloads to hist (Possibly to other plot functions as well). The Unions in the return types are a bit annoying.

Unfortunately that is not super trivial because of how we have some generated code for pyplot that is not set up to handle overloads yet (I think it would just get the first signature right now)

Not impossible, but a bit larger of a task than it may seem at first. I don't think that the overloads would have saved you here, though, either.

Also the list vs non-list outputs are potentially hard to differentiate because it is based on the shape rather than the value... (Sequence[ArrayLike] often counts as ArrayLike, for one, but also if you pass a single-item list in, it will in fact treat it the same as if you didn't pass a list)... So the point being that while we might be able to do better, may not have the tools to completely disambiguate the output type...

We could potentially acquiesce that that return type is more trouble than it is worth and just fall back to Any

Feel free to open an issue, I don't think it will make it into 3.8.0 (which I'd like to get out asap)

Copy link
Contributor

@Illviljan Illviljan left a comment

Choose a reason for hiding this comment

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

You probably saw this thread as well but have you tried changing the order of the overloads?
python/mypy#5407

xarray/plot/dataarray_plot.py Outdated Show resolved Hide resolved
@@ -179,7 +182,7 @@ def step(self, *args, **kwargs) -> list[Line3D] | FacetGrid[DataArray]:
return dataarray_plot.step(self._da, *args, **kwargs)

@overload
def scatter(
def scatter( # type: ignore[misc] # None is hashable :(
Copy link
Contributor

@Illviljan Illviljan Sep 12, 2023

Choose a reason for hiding this comment

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

Have you tried Optional[Hashable] ?

I saw in the NamedArray PR we sometimes use this trick:

from xarray.core.utils import Default, _default

def _as_sparse(
        self: T_NamedArray,
        sparse_format: str | Default = _default,
        fill_value=dtypes.NA,
    ) -> T_NamedArray:


ef _replace(
        self: T_NamedArray, dims=_default, data=_default, attrs=_default
    ) -> T_NamedArray:
        if dims is _default:
            dims = copy.copy(self._dims)
        if data is _default:
            data = copy.copy(self._data)
        if attrs is _default:
            attrs = copy.copy(self._attrs)
        return type(self)(dims, data, attrs)

I am not sure how scary it would be to just change to this method?

Copy link
Collaborator Author

@headtr1ck headtr1ck Sep 12, 2023

Choose a reason for hiding this comment

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

I saw in the NamedArray PR we sometimes use this trick:
I am not sure how scary it would be to just change to this method?

I don't think one should deviate from None as default if it is preventable.

This issue if exactly this: python/mypy#13805
Not sure why for some methods it is only surfacing now and not already before.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Have you tried Optional[Hashable] ?

That is exactly the same as Hashable | None, and tools like pyupgrade will also replace that.

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess the question is; does xarray allow None as a dim/coordinate? If we do, we have to use other defaults.

Considering our insistence to use Hashable for dims it seems we want to:

a = xr.Variable(data=[3,4], dims=(None,))
print(a)
<xarray.Variable (None: 2)>
array([3, 4])

c = xr.DataArray([1,2]).assign_coords(coords={"dim_0": [3,4]}).rename({"dim_0": None})
print(c)
<xarray.DataArray (None: 2)>
array([1, 2])
Coordinates:
  * None     (None) int32 3 4

I suppose this is just another issue with dim(s) haven't been consistently typed before.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

While None is technically hashable probably half of xarrays functions use it as default for dimension arguments, so it would be quite a large breaking change to allow it as a dimension/variable/coordinate "name".

I think it is not worth it to introduce such a breaking change just to allow such a corner case.
If something, we should probably add a check for not None in the constructors of xarray objects.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If you want, you can open a follow up issue for that.
Definitely this is out of scope for this PR.

@headtr1ck
Copy link
Collaborator Author

You probably saw this thread as well but have you tried changing the order of the overloads? python/mypy#5407

Yes, I tried but same result.

@headtr1ck
Copy link
Collaborator Author

Current Mypy is successful, upstream mypy only shows numpy errors.
I think this is good for a final review :)

xarray/plot/accessor.py Outdated Show resolved Hide resolved
xarray/plot/accessor.py Outdated Show resolved Hide resolved
xarray/plot/accessor.py Outdated Show resolved Hide resolved
xarray/plot/accessor.py Outdated Show resolved Hide resolved
xarray/plot/accessor.py Outdated Show resolved Hide resolved
xarray/plot/accessor.py Outdated Show resolved Hide resolved
xarray/plot/accessor.py Outdated Show resolved Hide resolved
xarray/plot/accessor.py Outdated Show resolved Hide resolved
xarray/plot/accessor.py Outdated Show resolved Hide resolved
xarray/plot/accessor.py Outdated Show resolved Hide resolved
@headtr1ck headtr1ck merged commit babe9ff into pydata:main Sep 17, 2023
22 of 26 checks passed
@headtr1ck
Copy link
Collaborator Author

@ksunden thanks for the help!

max-sixty pushed a commit to max-sixty/xarray that referenced this pull request Sep 17, 2023
* fix several typing issues with mpl3.8

* fix scatter plot typing and remove funky pyplot import

* fix some more typing errors

* fix some import errors

* undo some typing errors

* fix xylim typing

* add forgotten import

* ignore plotting overloads because None is Hashable

* add whats-new entry

* fix return type of hist

* fix another xylim type

* fix some more xylim types

* change to T_DataArray

* change accessor xylim to tuple

* add missing return types

* fix a typing error only on new mpl

* add unused-ignore to error codes for old mpl

* add more unused-ignore to error codes for old mpl

* replace type: ignore[attr-defined] with assert hasattr

* apply code review suggestions

---------

Co-authored-by: Illviljan <14371165+Illviljan@users.noreply.github.com>
@headtr1ck headtr1ck deleted the mpltypes branch September 26, 2023 19:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
plan to merge Final call for comments run-upstream Run upstream CI topic-plotting
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Mypy errors with matplotlib 3.8
3 participants