pycontrails 0.56.0__cp311-cp311-win_amd64.whl → 0.58.0__cp311-cp311-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pycontrails might be problematic. Click here for more details.

Files changed (40) hide show
  1. pycontrails/_version.py +3 -3
  2. pycontrails/core/aircraft_performance.py +1 -1
  3. pycontrails/core/cache.py +2 -2
  4. pycontrails/core/fleet.py +2 -7
  5. pycontrails/core/flight.py +2 -7
  6. pycontrails/core/interpolation.py +42 -64
  7. pycontrails/core/met.py +36 -16
  8. pycontrails/core/polygon.py +3 -3
  9. pycontrails/core/rgi_cython.cp311-win_amd64.pyd +0 -0
  10. pycontrails/core/vector.py +3 -8
  11. pycontrails/datalib/_met_utils/metsource.py +4 -7
  12. pycontrails/datalib/ecmwf/common.py +2 -2
  13. pycontrails/datalib/ecmwf/hres.py +2 -2
  14. pycontrails/datalib/ecmwf/ifs.py +1 -1
  15. pycontrails/datalib/geo_utils.py +261 -0
  16. pycontrails/datalib/gfs/gfs.py +59 -65
  17. pycontrails/datalib/goes.py +193 -399
  18. pycontrails/datalib/himawari/__init__.py +27 -0
  19. pycontrails/datalib/himawari/header_struct.py +266 -0
  20. pycontrails/datalib/himawari/himawari.py +667 -0
  21. pycontrails/datalib/leo_utils/sentinel_metadata.py +9 -9
  22. pycontrails/ext/synthetic_flight.py +2 -2
  23. pycontrails/models/cocip/cocip_uncertainty.py +1 -1
  24. pycontrails/models/cocip/contrail_properties.py +1 -1
  25. pycontrails/models/cocip/output_formats.py +1 -1
  26. pycontrails/models/cocipgrid/cocip_grid.py +3 -3
  27. pycontrails/models/dry_advection.py +1 -1
  28. pycontrails/models/extended_k15.py +4 -4
  29. pycontrails/models/humidity_scaling/humidity_scaling.py +2 -2
  30. pycontrails/models/ps_model/ps_grid.py +2 -2
  31. pycontrails/models/sac.py +1 -1
  32. pycontrails/models/tau_cirrus.py +1 -1
  33. pycontrails/physics/thermo.py +1 -1
  34. pycontrails/utils/iteration.py +1 -1
  35. {pycontrails-0.56.0.dist-info → pycontrails-0.58.0.dist-info}/METADATA +6 -6
  36. {pycontrails-0.56.0.dist-info → pycontrails-0.58.0.dist-info}/RECORD +40 -36
  37. {pycontrails-0.56.0.dist-info → pycontrails-0.58.0.dist-info}/top_level.txt +0 -1
  38. {pycontrails-0.56.0.dist-info → pycontrails-0.58.0.dist-info}/WHEEL +0 -0
  39. {pycontrails-0.56.0.dist-info → pycontrails-0.58.0.dist-info}/licenses/LICENSE +0 -0
  40. {pycontrails-0.56.0.dist-info → pycontrails-0.58.0.dist-info}/licenses/NOTICE +0 -0
pycontrails/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.56.0'
32
- __version_tuple__ = version_tuple = (0, 56, 0)
31
+ __version__ = version = '0.58.0'
32
+ __version_tuple__ = version_tuple = (0, 58, 0)
33
33
 
34
- __commit_id__ = commit_id = 'gec7f244f4'
34
+ __commit_id__ = commit_id = 'g142ff7355'
@@ -550,7 +550,7 @@ class AircraftPerformance(Model):
550
550
  if self.met is None:
551
551
  cond = np.isnan(u) & np.isnan(v)
552
552
  else:
553
- met_level_max = self.met.data["level"][-1].item() # type: ignore[union-attr]
553
+ met_level_max = self.met.data["level"][-1].item()
554
554
  cond = self.source.level > met_level_max
555
555
 
556
556
  # We DON'T overwrite the original u and v arrays already attached to the source
pycontrails/core/cache.py CHANGED
@@ -189,7 +189,7 @@ class DiskCacheStore(CacheStore):
189
189
  self,
190
190
  cache_dir: str | pathlib.Path | None = None,
191
191
  allow_clear: bool = False,
192
- ):
192
+ ) -> None:
193
193
  if cache_dir is None:
194
194
  # Avoid unnecessary import of platformdirs (called in _get_user_cache_dir)
195
195
  cache_dir = os.getenv("PYCONTRAILS_CACHE_DIR") or _get_user_cache_dir()
@@ -461,7 +461,7 @@ class GCPCacheStore(CacheStore):
461
461
  timeout: int = 300,
462
462
  show_progress: bool = False,
463
463
  chunk_size: int = 64 * 262144,
464
- ):
464
+ ) -> None:
465
465
  try:
466
466
  from google.cloud import storage
467
467
  except ModuleNotFoundError as e:
pycontrails/core/fleet.py CHANGED
@@ -5,12 +5,7 @@ from __future__ import annotations
5
5
  import sys
6
6
  import warnings
7
7
  from collections.abc import Iterable
8
- from typing import Any, NoReturn
9
-
10
- if sys.version_info >= (3, 11):
11
- from typing import Self
12
- else:
13
- from typing_extensions import Self
8
+ from typing import Any, NoReturn, Self
14
9
 
15
10
  if sys.version_info >= (3, 12):
16
11
  from typing import override
@@ -122,7 +117,7 @@ class Fleet(Flight):
122
117
  # Set default fl_attrs if not provided
123
118
  fl_attrs = fl_attrs or {}
124
119
  for flight_id in groups.index:
125
- fl_attrs.setdefault(flight_id, {}) # type: ignore[call-overload]
120
+ fl_attrs.setdefault(flight_id, {})
126
121
 
127
122
  extra = fl_attrs.keys() - groups.index
128
123
  if extra:
@@ -6,18 +6,13 @@ import enum
6
6
  import logging
7
7
  import sys
8
8
  import warnings
9
- from typing import TYPE_CHECKING, Any, NoReturn
9
+ from typing import TYPE_CHECKING, Any, NoReturn, Self
10
10
 
11
11
  if sys.version_info >= (3, 12):
12
12
  from typing import override
13
13
  else:
14
14
  from typing_extensions import override
15
15
 
16
- if sys.version_info >= (3, 11):
17
- from typing import Self
18
- else:
19
- from typing_extensions import Self
20
-
21
16
 
22
17
  import numpy as np
23
18
  import numpy.typing as npt
@@ -2138,7 +2133,7 @@ def segment_rocd(
2138
2133
  T_correction[:-1] = (air_temperature[:-1] + air_temperature[1:]) / (T_isa[:-1] + T_isa[1:])
2139
2134
  T_correction[-1] = np.nan
2140
2135
 
2141
- return T_correction * out # type: ignore[return-value]
2136
+ return T_correction * out
2142
2137
 
2143
2138
 
2144
2139
  def _resample_to_freq_or_time(
@@ -26,57 +26,74 @@ class PycontrailsRegularGridInterpolator(scipy.interpolate.RegularGridInterpolat
26
26
 
27
27
  This class is a thin wrapper around the
28
28
  :class:`scipy.interpolate.RegularGridInterpolator` in order to make typical
29
- ``pycontrails`` use-cases more efficient.
29
+ ``pycontrails`` linear interpolation use-cases more performant:
30
30
 
31
- #. Avoid ``RegularGridInterpolator`` constructor validation. In :func:`interp`,
32
- parameters are carefully crafted to fit into the intended form, thereby making
33
- validation unnecessary.
31
+ #. Avoid ``RegularGridInterpolator`` constructor validation when `method="linear"`.
32
+ In :func:`interp`, parameters are carefully crafted to fit into the intended form,
33
+ thereby making validation unnecessary.
34
34
  #. Override the :meth:`_evaluate_linear` method with a faster implementation. See
35
- the :meth:`_evaluate_linear` docstring for more information.
35
+ the :meth:`_evaluate_linear` docstring for more information.
36
36
 
37
- This class should not be used directly. Instead, use the :func:`interp` function.
37
+ **This class should not be used directly. Instead, use the ``interp`` function.**
38
38
 
39
39
  .. versionchanged:: 0.40.0
40
40
 
41
41
  The :meth:`_evaluate_linear` method now uses a Cython implementation. The dtype
42
42
  of the output is now consistent with the dtype of the underlying :attr:`values`
43
43
 
44
+ .. versionchanged:: 0.58.0
45
+
46
+ Any ``method`` other than ``"linear"`` now uses the
47
+ :class:`scipy.interpolate.RegularGridInterpolator` implementation. This
48
+ allows for greater flexibility in the ``method`` parameter.
49
+
44
50
  Parameters
45
51
  ----------
46
52
  points : tuple[npt.NDArray[np.floating], ...]
47
53
  Coordinates of the grid points.
48
54
  values : npt.NDArray[np.floating]
49
55
  Grid values. The shape of this array must be compatible with the
50
- coordinates. An error is raised if the dtype is not ``np.float32``
51
- or ``np.float64``.
56
+ coordinates.
52
57
  method : str
53
58
  Passed into :class:`scipy.interpolate.RegularGridInterpolator`
54
59
  bounds_error : bool
55
60
  Passed into :class:`scipy.interpolate.RegularGridInterpolator`
56
61
  fill_value : float | np.float64 | None
57
62
  Passed into :class:`scipy.interpolate.RegularGridInterpolator`
63
+
64
+ See Also
65
+ --------
66
+ scipy.interpolate.RegularGridInterpolator
67
+ interp
58
68
  """
59
69
 
60
70
  def __init__(
61
71
  self,
62
72
  points: tuple[npt.NDArray[np.floating], ...],
63
73
  values: npt.NDArray[np.floating],
74
+ *,
64
75
  method: str,
65
76
  bounds_error: bool,
66
77
  fill_value: float | np.float64 | None,
67
- ):
68
- if values.dtype not in (np.float32, np.float64):
69
- msg = f"values must be a float array, not {values.dtype}"
70
- raise ValueError(msg)
71
-
78
+ ) -> None:
79
+ if method != "linear" or values.dtype not in (np.float32, np.float64):
80
+ # Slow path: use parent class
81
+ super().__init__(
82
+ points,
83
+ values,
84
+ method=method,
85
+ bounds_error=bounds_error,
86
+ fill_value=fill_value,
87
+ )
88
+ return
89
+
90
+ # Fast path: no validation
72
91
  self.grid = points
73
92
  self.values = values
74
- # TODO: consider supporting updated tensor-product spline methods
75
- # see https://github.com/scipy/scipy/releases/tag/v1.13.0
76
- self.method = _pick_method(scipy.__version__, method)
93
+ self.method = method
77
94
  self.bounds_error = bounds_error
78
95
  self.fill_value = fill_value
79
- self._spline = None
96
+ self._spline = None # XXX: setting private attribute on RGI
80
97
 
81
98
  def _prepare_xi_simple(self, xi: npt.NDArray[np.floating]) -> npt.NDArray[np.bool_]:
82
99
  """Run looser version of :meth:`_prepare_xi`.
@@ -103,7 +120,7 @@ class PycontrailsRegularGridInterpolator(scipy.interpolate.RegularGridInterpolat
103
120
 
104
121
  return np.zeros(xi.shape[0], dtype=bool)
105
122
 
106
- return self._find_out_of_bounds(xi.T)
123
+ return self._find_out_of_bounds(xi.T) # XXX: calling private method on RGI
107
124
 
108
125
  def __call__(
109
126
  self, xi: npt.NDArray[np.floating], method: str | None = None
@@ -130,7 +147,7 @@ class PycontrailsRegularGridInterpolator(scipy.interpolate.RegularGridInterpolat
130
147
  return super().__call__(xi, method)
131
148
 
132
149
  out_of_bounds = self._prepare_xi_simple(xi)
133
- xi_indices, norm_distances = rgi_cython.find_indices(self.grid, xi.T)
150
+ xi_indices, norm_distances = self._find_indices(xi.T) # XXX: calling private method on RGI
134
151
 
135
152
  out = self._evaluate_linear(xi_indices, norm_distances)
136
153
  return self._set_out_of_bounds(out, out_of_bounds)
@@ -223,45 +240,6 @@ class PycontrailsRegularGridInterpolator(scipy.interpolate.RegularGridInterpolat
223
240
  raise ValueError(msg)
224
241
 
225
242
 
226
- def _pick_method(scipy_version: str, method: str) -> str:
227
- """Select an interpolation method.
228
-
229
- For scipy versions 1.13.0 and later, fall back on legacy implementations
230
- of tensor-product spline methods. The default implementations in 1.13.0
231
- and later are incompatible with this class.
232
-
233
- Parameters
234
- ----------
235
- scipy_version : str
236
- scipy version (major.minor.patch)
237
-
238
- method : str
239
- Interpolation method. Passed into :class:`scipy.interpolate.RegularGridInterpolator`
240
- as-is unless ``scipy_version`` is 1.13.0 or later and ``method`` is ``"slinear"``,
241
- ``"cubic"``, or ``"quintic"``. In this case, ``"_legacy"`` is appended to ``method``.
242
-
243
- Returns
244
- -------
245
- str
246
- Interpolation method adjusted for compatibility with this class.
247
- """
248
- if method == "linear":
249
- return method
250
-
251
- try:
252
- version = scipy_version.split(".")
253
- major = int(version[0])
254
- minor = int(version[1])
255
- except (IndexError, ValueError) as exc:
256
- msg = f"Failed to parse major and minor version from {scipy_version}"
257
- raise ValueError(msg) from exc
258
-
259
- reimplemented_methods = ["slinear", "cubic", "quintic"]
260
- if major > 1 or ((major == 1 and minor >= 13) and method in reimplemented_methods):
261
- return method + "_legacy"
262
- return method
263
-
264
-
265
243
  def _floatize_time(
266
244
  time: npt.NDArray[np.datetime64], offset: np.datetime64
267
245
  ) -> npt.NDArray[np.floating]:
@@ -431,7 +409,7 @@ def interp(
431
409
  Include ``indices`` and ``return_indices`` experimental parameters.
432
410
  Currently, nan values in ``longitude``, ``latitude``, ``level``, or ``time``
433
411
  are always propagated through to the output, regardless of ``bounds_error``.
434
- In other words, a ValueError for an out of bounds coordinate is only raised
412
+ In other words, a ``ValueError`` for an out of bounds coordinate is only raised
435
413
  if a non-nan value is out of bounds.
436
414
 
437
415
  .. versionchanged:: 0.40.0
@@ -480,9 +458,9 @@ def interp(
480
458
 
481
459
  See Also
482
460
  --------
483
- - :meth:`MetDataArray.interpolate`
484
- - :func:`scipy.interpolate.interpn`
485
- - :class:`scipy.interpolate.RegularGridInterpolator`
461
+ pycontrails.MetDataArray.interpolate
462
+ scipy.interpolate.interpn
463
+ scipy.interpolate.RegularGridInterpolator
486
464
  """
487
465
  if localize:
488
466
  coords = {"longitude": longitude, "latitude": latitude, "level": level, "time": time}
@@ -579,7 +557,7 @@ def _linear_interp_with_indices(
579
557
  if indices is None:
580
558
  assert xi is not None, "xi must be provided if indices is None"
581
559
  out_of_bounds = interp._prepare_xi_simple(xi)
582
- xi_indices, norm_distances = rgi_cython.find_indices(interp.grid, xi.T)
560
+ xi_indices, norm_distances = interp._find_indices(xi.T)
583
561
  indices = RGIArtifacts(xi_indices, norm_distances, out_of_bounds)
584
562
 
585
563
  out = interp._evaluate_linear(indices.xi_indices, indices.norm_distances)
@@ -606,7 +584,7 @@ class EmissionsProfileInterpolator:
606
584
 
607
585
  This class simply wraps :func:`numpy.interp` with fixed values for the
608
586
  ``xp`` and ``fp`` arguments. Unlike :class:`xarray.DataArray` interpolation,
609
- the `numpy.interp` automatically clips values outside the range of the
587
+ the :func:`numpy.interp` automatically clips values outside the range of the
610
588
  ``xp`` array.
611
589
 
612
590
  Parameters
pycontrails/core/met.py CHANGED
@@ -26,15 +26,11 @@ from typing import (
26
26
  Any,
27
27
  Generic,
28
28
  Literal,
29
+ Self,
29
30
  TypeVar,
30
31
  overload,
31
32
  )
32
33
 
33
- if sys.version_info >= (3, 11):
34
- from typing import Self
35
- else:
36
- from typing_extensions import Self
37
-
38
34
  if sys.version_info >= (3, 12):
39
35
  from typing import override
40
36
  else:
@@ -211,14 +207,22 @@ class MetBase(ABC, Generic[XArrayType]):
211
207
  Raises
212
208
  ------
213
209
  ValueError
214
- If one of the coordinates is not sorted.
210
+ If one of the coordinates is not sorted or contains duplicate values.
215
211
  """
216
212
  indexes = self.indexes
217
- if not np.all(np.diff(indexes["time"]) > np.timedelta64(0, "ns")):
218
- raise ValueError("Coordinate 'time' not sorted. Instantiate with 'copy=True'.")
219
- for coord in self.dim_order[:3]: # exclude time, the 4th dimension
220
- if not np.all(np.diff(indexes[coord]) > 0.0):
221
- raise ValueError(f"Coordinate '{coord}' not sorted. Instantiate with 'copy=True'.")
213
+ for coord in self.dim_order:
214
+ arr = indexes[coord]
215
+ d = np.diff(arr)
216
+ zero = np.zeros((), dtype=d.dtype) # ensure same dtype
217
+
218
+ if np.any(d <= zero):
219
+ if np.any(d == zero):
220
+ msg = f"Coordinate '{coord}' contains duplicate values."
221
+ else:
222
+ msg = f"Coordinate '{coord}' not sorted."
223
+
224
+ msg += " Instantiate with 'copy=True'."
225
+ raise ValueError(msg)
222
226
 
223
227
  def _validate_transpose(self) -> None:
224
228
  """Check that data is transposed according to :attr:`dim_order`."""
@@ -271,6 +275,10 @@ class MetBase(ABC, Generic[XArrayType]):
271
275
  Auxiliary coordinates (altitude and air_pressure) are now cast to the same
272
276
  dtype as the underlying grid data.
273
277
 
278
+ .. versionchanged:: 0.58.0
279
+
280
+ Duplicate dimension values are dropped, keeping the first occurrence.
281
+
274
282
 
275
283
  Parameters
276
284
  ----------
@@ -297,6 +305,18 @@ class MetBase(ABC, Generic[XArrayType]):
297
305
  # sortby to ensure each coordinate has ascending order
298
306
  self.data = self.data.sortby(list(self.dim_order), ascending=True)
299
307
 
308
+ # Drop any duplicated dimension values
309
+ indexes = self.indexes
310
+ for coord in self.dim_order:
311
+ arr = indexes[coord]
312
+ d = np.diff(arr)
313
+ zero = np.zeros((), dtype=d.dtype) # ensure same dtype
314
+
315
+ if np.any(d == zero):
316
+ # Remove duplicates
317
+ filt = np.r_[True, d > zero] # prepend True keeps the first occurrence
318
+ self.data = self.data.isel({coord: filt})
319
+
300
320
  if not self.is_wrapped:
301
321
  # Ensure longitude is contained in interval [-180, 180)
302
322
  # If longitude has value at 180, we might not want to shift it?
@@ -1087,7 +1107,7 @@ class MetDataset(MetBase):
1087
1107
  out[key] = da.values.ravel() # type: ignore[index]
1088
1108
 
1089
1109
  if transfer_attrs:
1090
- out.attrs.update(self.attrs) # type: ignore[arg-type]
1110
+ out.attrs.update(self.attrs)
1091
1111
 
1092
1112
  return out
1093
1113
 
@@ -1300,7 +1320,7 @@ class MetDataset(MetBase):
1300
1320
  coords: dict[str, np.ndarray] = {}
1301
1321
  for key, val in input_data.items():
1302
1322
  dtype = "datetime64[ns]" if key == "time" else COORD_DTYPE
1303
- arr: np.ndarray = np.asarray(val, dtype=dtype) # type: ignore[call-overload]
1323
+ arr: np.ndarray = np.asarray(val, dtype=dtype)
1304
1324
 
1305
1325
  if arr.ndim == 0:
1306
1326
  arr = arr.reshape(1)
@@ -1889,7 +1909,7 @@ class MetDataArray(MetBase):
1889
1909
  if not self.binary:
1890
1910
  raise NotImplementedError("proportion method is only implemented for binary fields")
1891
1911
 
1892
- return self.data.sum().values.item() / self.data.count().values.item() # type: ignore[operator]
1912
+ return self.data.sum().values.item() / self.data.count().values.item()
1893
1913
 
1894
1914
  def find_edges(self) -> Self:
1895
1915
  """Find edges of regions.
@@ -2598,9 +2618,9 @@ def _extract_2d_arr_and_altitude(
2598
2618
  except KeyError:
2599
2619
  altitude = None
2600
2620
  else:
2601
- altitude = round(altitude) # type: ignore[call-overload]
2621
+ altitude = round(altitude)
2602
2622
 
2603
- return arr, altitude # type: ignore[return-value]
2623
+ return arr, altitude
2604
2624
 
2605
2625
 
2606
2626
  def downselect(data: XArrayType, bbox: tuple[float, ...]) -> XArrayType:
@@ -238,7 +238,7 @@ def _contours_to_polygons(
238
238
  latitude=latitude,
239
239
  precision=precision,
240
240
  buffer=buffer,
241
- i=child_i, # type: ignore[arg-type]
241
+ i=child_i,
242
242
  )
243
243
 
244
244
  candidate = shapely.Polygon(polygon.exterior, [h.exterior for h in holes])
@@ -354,11 +354,11 @@ def find_multipolygon(
354
354
  return shapely.MultiPolygon()
355
355
 
356
356
  assert len(hierarchy) == 1
357
- hierarchy = hierarchy[0] # type: ignore[index]
357
+ hierarchy = hierarchy[0]
358
358
 
359
359
  polygons = _contours_to_polygons(
360
360
  contours, # type: ignore[arg-type]
361
- hierarchy, # type: ignore[arg-type]
361
+ hierarchy,
362
362
  min_area,
363
363
  convex_hull,
364
364
  epsilon,
@@ -8,12 +8,7 @@ import logging
8
8
  import sys
9
9
  import warnings
10
10
  from collections.abc import Generator, Iterable, Iterator, Sequence
11
- from typing import Any, overload
12
-
13
- if sys.version_info >= (3, 11):
14
- from typing import Self
15
- else:
16
- from typing_extensions import Self
11
+ from typing import Any, Self, overload
17
12
 
18
13
  if sys.version_info >= (3, 12):
19
14
  from typing import override
@@ -315,7 +310,7 @@ class VectorDataset: # noqa: PLW1641
315
310
  # Set attributes: always shallow copy
316
311
  # -----------------------------------
317
312
 
318
- self.attrs = AttrDict(attrs or {}) # type: ignore[arg-type]
313
+ self.attrs = AttrDict(attrs or {})
319
314
  self.attrs.update(attrs_kwargs)
320
315
 
321
316
  @classmethod
@@ -1389,7 +1384,7 @@ class GeoVectorDataset(VectorDataset):
1389
1384
  ):
1390
1385
  keys = *self.required_keys, "altitude"
1391
1386
  self.data = VectorDataDict(_empty_vector_dict(keys))
1392
- self.attrs = AttrDict(attrs or {}) # type: ignore[arg-type]
1387
+ self.attrs = AttrDict(attrs or {})
1393
1388
  self.attrs.update(attrs_kwargs)
1394
1389
  return
1395
1390
 
@@ -175,16 +175,13 @@ def parse_pressure_levels(
175
175
 
176
176
  out = arr.tolist()
177
177
  if supported is None:
178
- return out # type: ignore[return-value]
178
+ return out
179
179
 
180
- if missing := set(out).difference(supported): # type: ignore[arg-type]
181
- msg = (
182
- f"Pressure levels {sorted(missing)} are not supported. " # type: ignore[type-var]
183
- f"Supported levels: {supported}"
184
- )
180
+ if missing := set(out).difference(supported):
181
+ msg = f"Pressure levels {sorted(missing)} are not supported. Supported levels: {supported}"
185
182
  raise ValueError(msg)
186
183
 
187
- return out # type: ignore[return-value]
184
+ return out
188
185
 
189
186
 
190
187
  def parse_variables(variables: VariableInput, supported: list[MetVariable]) -> list[MetVariable]:
@@ -61,14 +61,14 @@ class ECMWFAPI(metsource.MetDataSource):
61
61
 
62
62
  # downselect times
63
63
  if not self.timesteps:
64
- self.timesteps = ds["time"].values.astype("datetime64[ns]").tolist() # type: ignore[assignment]
64
+ self.timesteps = ds["time"].values.astype("datetime64[ns]").tolist()
65
65
  else:
66
66
  try:
67
67
  ds = ds.sel(time=self.timesteps)
68
68
  except KeyError as exc:
69
69
  # this snippet shows the missing times for convenience
70
70
  np_timesteps = {np.datetime64(t, "ns") for t in self.timesteps}
71
- missing_times = sorted(np_timesteps.difference(ds["time"].values)) # type: ignore[type-var]
71
+ missing_times = sorted(np_timesteps.difference(ds["time"].values))
72
72
  msg = f"Input dataset is missing time coordinates {[str(t) for t in missing_times]}"
73
73
  raise KeyError(msg) from exc
74
74
 
@@ -708,7 +708,7 @@ class HRES(ECMWFAPI):
708
708
 
709
709
  # set forecast time if it's not defined (this occurs when only the paths param is provided)
710
710
  if not hasattr(self, "forecast_time"):
711
- self.forecast_time = ds["time"].values.astype("datetime64[s]").tolist() # type: ignore[assignment]
711
+ self.forecast_time = ds["time"].values.astype("datetime64[s]").tolist()
712
712
 
713
713
  # check that forecast_time is correct if defined
714
714
  # note the "time" coordinate here is the HRES forecast_time
@@ -723,7 +723,7 @@ class HRES(ECMWFAPI):
723
723
  # set timesteps if not defined
724
724
  # note that "time" is now the actual timestep coordinates
725
725
  if not self.timesteps:
726
- self.timesteps = ds["time"].values.astype("datetime64[s]").tolist() # type: ignore[assignment]
726
+ self.timesteps = ds["time"].values.astype("datetime64[s]").tolist()
727
727
 
728
728
  self.cache_dataset(ds)
729
729
 
@@ -149,7 +149,7 @@ class IFS(metsource.MetDataSource):
149
149
  else:
150
150
  # set timesteps from dataset "time" coordinates
151
151
  # np.datetime64 doesn't covert to list[datetime] unless its unit is us
152
- self.timesteps = ds["time"].values.astype("datetime64[us]").tolist() # type: ignore[assignment]
152
+ self.timesteps = ds["time"].values.astype("datetime64[us]").tolist()
153
153
 
154
154
  # downselect hyam/hybm coefficients by the "lev" coordinate
155
155
  # (this is a 1-indexed verison of nhym)