From 6cdae75aa00fefc069ffe056e08c3661a4958892 Mon Sep 17 00:00:00 2001 From: Mathias Hauser Date: Sun, 29 Nov 2020 09:51:29 +0100 Subject: [PATCH] don't type check __getattr__ --- doc/whats-new.rst | 5 ++++ xarray/core/common.py | 62 ++++++++++++++++++++++++++++++++++++----- xarray/core/dataset.py | 9 ++---- xarray/core/weighted.py | 3 +- 4 files changed, 65 insertions(+), 14 deletions(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 308ed33f0bc..83e927b8991 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -137,6 +137,11 @@ Internal Changes By `Yash Saboo `_, `Nirupam K N `_ and `Mathias Hauser `_. + - Added ``@no_type_check`` to ``__getattr__`` of :py:class:`Dataset` and :py:class:`DataArray`. + This can help to identify missing methods but may lead to typing errors in downstream libraries. + The recommendation in this case is to use the index operator when accessing variables or + coordinates, e.g. ``da["longitude"]``, and to add ``#type: ignore`` for accessors, e.g. + ``da.geo # type: ignore`` (:issue:`4601`). By `Mathias Hauser `_. .. _whats-new.0.16.1: diff --git a/xarray/core/common.py b/xarray/core/common.py index 148ea76ee3f..83f0195a273 100644 --- a/xarray/core/common.py +++ b/xarray/core/common.py @@ -14,6 +14,7 @@ Tuple, TypeVar, Union, + no_type_check, ) import numpy as np @@ -218,6 +219,7 @@ def _item_sources(self) -> List[Mapping[Hashable, Any]]: """List of places to look-up items for key-autocompletion""" return [] + @no_type_check # so missing methods raise a type error def __getattr__(self, name: str) -> Any: if name not in {"__dict__", "__setstate__"}: # this avoids an infinite loop when pickle looks for the @@ -365,7 +367,7 @@ def squeeze( numpy.squeeze """ dims = get_squeeze_dims(self, dim, axis) - return self.isel(drop=drop, **{d: 0 for d in dims}) + return self.isel({d: 0 for d in dims}, drop=drop) def get_index(self, key: Hashable) -> pd.Index: """Get an index for a dimension, with fall-back to a default RangeIndex""" @@ -693,7 +695,7 @@ def groupby(self, group, squeeze: bool = True, restore_coord_dims: bool = None): f"`squeeze` must be True or False, but {squeeze} was supplied" ) - return self._groupby_cls( + return self._groupby_cls( # type: ignore self, group, squeeze=squeeze, restore_coord_dims=restore_coord_dims ) @@ -756,7 +758,7 @@ def groupby_bins( ---------- .. [1] http://pandas.pydata.org/pandas-docs/stable/generated/pandas.cut.html """ - return self._groupby_cls( + return self._groupby_cls( # type: ignore self, group, squeeze=squeeze, @@ -787,7 +789,7 @@ def weighted(self, weights): Missing values can be replaced by ``weights.fillna(0)``. """ - return self._weighted_cls(self, weights) + return self._weighted_cls(self, weights) # type: ignore def rolling( self, @@ -861,7 +863,7 @@ def rolling( """ dim = either_dict_or_kwargs(dim, window_kwargs, "rolling") - return self._rolling_cls( + return self._rolling_cls( # type: ignore self, dim, min_periods=min_periods, center=center, keep_attrs=keep_attrs ) @@ -972,7 +974,7 @@ def coarsen( keep_attrs = _get_keep_attrs(default=False) dim = either_dict_or_kwargs(dim, window_kwargs, "coarsen") - return self._coarsen_cls( + return self._coarsen_cls( # type: ignore self, dim, boundary=boundary, @@ -1139,7 +1141,7 @@ def resample( group = DataArray( dim_coord, coords=dim_coord.coords, dims=dim_coord.dims, name=RESAMPLE_DIM ) - resampler = self._resample_cls( + resampler = self._resample_cls( # type: ignore self, group=group, dim=dim_name, @@ -1376,6 +1378,52 @@ def __getitem__(self, value): # implementations of this class should implement this method raise NotImplementedError() + def isel( + self, + indexers: Mapping[Hashable, Any] = None, + drop: bool = False, + missing_dims: str = "raise", + **indexers_kwargs: Any, + ): + # implementations of this class should implement this method / for type checking + raise NotImplementedError() + + @property + def dims(self): + # implementations of this class should implement this method / for type checking + raise NotImplementedError() + + @property + def indexes(self): + # implementations of this class should implement this method / for type checking + raise NotImplementedError() + + @property + def sizes(self): + # implementations of this class should implement this method / for type checking + raise NotImplementedError() + + def copy(self, deep=True, data=None): + # implementations of this class should implement this method / for type checking + raise NotImplementedError() + + @property + def encoding(self) -> Dict: + # implementations of this class should implement this method / for type checking + raise NotImplementedError() + + def reindex( + self, + indexers: Mapping[Hashable, Any] = None, + method: str = None, + tolerance=None, + copy: bool = True, + fill_value: Any = dtypes.NA, + **indexers_kwargs: Any, + ): + # implementations of this class should implement this method / for type checking + raise NotImplementedError() + def full_like(other, fill_value, dtype: DTypeLike = None): """Return a new object with the same shape and type as a given object. diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index 04974c58113..2a32186c5de 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -3784,12 +3784,8 @@ def merge( ) return self._replace(**merge_result._asdict()) - def _assert_all_in_dataset( - self, names: Iterable[Hashable], virtual_okay: bool = False - ) -> None: + def _assert_all_in_dataset(self, names: Iterable[Hashable]) -> None: bad_names = set(names) - set(self._variables) - if virtual_okay: - bad_names -= self.virtual_variables if bad_names: raise ValueError( "One or more of the specified variables " @@ -6132,7 +6128,8 @@ def polyfit( # deficient ranks nor does it output the "full" info (issue dask/dask#6516) skipna_da = True elif skipna is None: - skipna_da = np.any(da.isnull()) + # no type checking because ops are injected + skipna_da = np.any(da.isnull()) # type: ignore dims_to_stack = [dimname for dimname in da.dims if dimname != dim] stacked_coords: Dict[Hashable, DataArray] = {} diff --git a/xarray/core/weighted.py b/xarray/core/weighted.py index ab4a0958866..789635a32ec 100644 --- a/xarray/core/weighted.py +++ b/xarray/core/weighted.py @@ -154,7 +154,8 @@ def _sum_of_weights( """ Calculate the sum of weights, accounting for missing values """ # we need to mask data values that are nan; else the weights are wrong - mask = da.notnull() + # no type checking because ops are injected + mask = da.notnull() # type: ignore # bool -> int, because ``xr.dot([True, True], [True, True])`` -> True # (and not 2); GH4074