anemoi-datasets 0.5.25__py3-none-any.whl → 0.5.27__py3-none-any.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.
Files changed (126) hide show
  1. anemoi/datasets/__init__.py +1 -2
  2. anemoi/datasets/_version.py +16 -3
  3. anemoi/datasets/commands/check.py +1 -1
  4. anemoi/datasets/commands/copy.py +1 -2
  5. anemoi/datasets/commands/create.py +1 -1
  6. anemoi/datasets/commands/grib-index.py +1 -1
  7. anemoi/datasets/commands/inspect.py +27 -35
  8. anemoi/datasets/commands/validate.py +59 -0
  9. anemoi/datasets/compute/recentre.py +3 -6
  10. anemoi/datasets/create/__init__.py +22 -25
  11. anemoi/datasets/create/check.py +10 -12
  12. anemoi/datasets/create/chunks.py +1 -2
  13. anemoi/datasets/create/config.py +3 -6
  14. anemoi/datasets/create/filter.py +21 -24
  15. anemoi/datasets/create/input/__init__.py +1 -2
  16. anemoi/datasets/create/input/action.py +3 -5
  17. anemoi/datasets/create/input/concat.py +5 -8
  18. anemoi/datasets/create/input/context.py +3 -6
  19. anemoi/datasets/create/input/data_sources.py +5 -8
  20. anemoi/datasets/create/input/empty.py +1 -2
  21. anemoi/datasets/create/input/filter.py +2 -3
  22. anemoi/datasets/create/input/function.py +1 -2
  23. anemoi/datasets/create/input/join.py +4 -5
  24. anemoi/datasets/create/input/misc.py +4 -6
  25. anemoi/datasets/create/input/repeated_dates.py +13 -18
  26. anemoi/datasets/create/input/result.py +29 -33
  27. anemoi/datasets/create/input/step.py +6 -24
  28. anemoi/datasets/create/input/template.py +3 -4
  29. anemoi/datasets/create/input/trace.py +1 -1
  30. anemoi/datasets/create/patch.py +1 -2
  31. anemoi/datasets/create/persistent.py +3 -5
  32. anemoi/datasets/create/size.py +1 -3
  33. anemoi/datasets/create/sources/accumulations.py +47 -52
  34. anemoi/datasets/create/sources/accumulations2.py +4 -8
  35. anemoi/datasets/create/sources/constants.py +1 -3
  36. anemoi/datasets/create/sources/empty.py +1 -2
  37. anemoi/datasets/create/sources/fdb.py +133 -0
  38. anemoi/datasets/create/sources/forcings.py +1 -2
  39. anemoi/datasets/create/sources/grib.py +6 -10
  40. anemoi/datasets/create/sources/grib_index.py +13 -15
  41. anemoi/datasets/create/sources/hindcasts.py +2 -5
  42. anemoi/datasets/create/sources/legacy.py +1 -1
  43. anemoi/datasets/create/sources/mars.py +17 -21
  44. anemoi/datasets/create/sources/netcdf.py +1 -2
  45. anemoi/datasets/create/sources/opendap.py +1 -3
  46. anemoi/datasets/create/sources/patterns.py +4 -6
  47. anemoi/datasets/create/sources/planetary_computer.py +44 -0
  48. anemoi/datasets/create/sources/recentre.py +8 -11
  49. anemoi/datasets/create/sources/source.py +3 -6
  50. anemoi/datasets/create/sources/tendencies.py +2 -5
  51. anemoi/datasets/create/sources/xarray.py +4 -6
  52. anemoi/datasets/create/sources/xarray_support/__init__.py +15 -32
  53. anemoi/datasets/create/sources/xarray_support/coordinates.py +16 -12
  54. anemoi/datasets/create/sources/xarray_support/field.py +17 -16
  55. anemoi/datasets/create/sources/xarray_support/fieldlist.py +11 -15
  56. anemoi/datasets/create/sources/xarray_support/flavour.py +83 -45
  57. anemoi/datasets/create/sources/xarray_support/grid.py +15 -9
  58. anemoi/datasets/create/sources/xarray_support/metadata.py +19 -128
  59. anemoi/datasets/create/sources/xarray_support/patch.py +47 -6
  60. anemoi/datasets/create/sources/xarray_support/time.py +10 -13
  61. anemoi/datasets/create/sources/xarray_support/variable.py +27 -23
  62. anemoi/datasets/create/sources/xarray_zarr.py +1 -2
  63. anemoi/datasets/create/sources/zenodo.py +3 -5
  64. anemoi/datasets/create/statistics/__init__.py +3 -6
  65. anemoi/datasets/create/testing.py +2 -74
  66. anemoi/datasets/create/typing.py +1 -2
  67. anemoi/datasets/create/utils.py +1 -2
  68. anemoi/datasets/create/zarr.py +7 -2
  69. anemoi/datasets/data/__init__.py +15 -6
  70. anemoi/datasets/data/complement.py +52 -23
  71. anemoi/datasets/data/concat.py +5 -8
  72. anemoi/datasets/data/dataset.py +42 -47
  73. anemoi/datasets/data/debug.py +7 -9
  74. anemoi/datasets/data/ensemble.py +4 -6
  75. anemoi/datasets/data/fill_missing.py +7 -10
  76. anemoi/datasets/data/forwards.py +30 -28
  77. anemoi/datasets/data/grids.py +12 -16
  78. anemoi/datasets/data/indexing.py +9 -12
  79. anemoi/datasets/data/interpolate.py +7 -15
  80. anemoi/datasets/data/join.py +8 -12
  81. anemoi/datasets/data/masked.py +6 -11
  82. anemoi/datasets/data/merge.py +5 -9
  83. anemoi/datasets/data/misc.py +41 -45
  84. anemoi/datasets/data/missing.py +11 -16
  85. anemoi/datasets/data/observations/__init__.py +8 -14
  86. anemoi/datasets/data/padded.py +3 -5
  87. anemoi/datasets/data/records/backends/__init__.py +2 -2
  88. anemoi/datasets/data/rescale.py +5 -12
  89. anemoi/datasets/data/select.py +13 -16
  90. anemoi/datasets/data/statistics.py +4 -7
  91. anemoi/datasets/data/stores.py +23 -77
  92. anemoi/datasets/data/subset.py +8 -11
  93. anemoi/datasets/data/unchecked.py +7 -11
  94. anemoi/datasets/data/xy.py +25 -21
  95. anemoi/datasets/dates/__init__.py +13 -18
  96. anemoi/datasets/dates/groups.py +7 -10
  97. anemoi/datasets/grids.py +11 -12
  98. anemoi/datasets/testing.py +93 -7
  99. anemoi/datasets/validate.py +598 -0
  100. {anemoi_datasets-0.5.25.dist-info → anemoi_datasets-0.5.27.dist-info}/METADATA +5 -4
  101. anemoi_datasets-0.5.27.dist-info/RECORD +134 -0
  102. anemoi/datasets/create/filters/__init__.py +0 -33
  103. anemoi/datasets/create/filters/empty.py +0 -37
  104. anemoi/datasets/create/filters/legacy.py +0 -93
  105. anemoi/datasets/create/filters/noop.py +0 -37
  106. anemoi/datasets/create/filters/orog_to_z.py +0 -58
  107. anemoi/datasets/create/filters/pressure_level_relative_humidity_to_specific_humidity.py +0 -83
  108. anemoi/datasets/create/filters/pressure_level_specific_humidity_to_relative_humidity.py +0 -84
  109. anemoi/datasets/create/filters/rename.py +0 -205
  110. anemoi/datasets/create/filters/rotate_winds.py +0 -105
  111. anemoi/datasets/create/filters/single_level_dewpoint_to_relative_humidity.py +0 -78
  112. anemoi/datasets/create/filters/single_level_relative_humidity_to_dewpoint.py +0 -84
  113. anemoi/datasets/create/filters/single_level_relative_humidity_to_specific_humidity.py +0 -163
  114. anemoi/datasets/create/filters/single_level_specific_humidity_to_relative_humidity.py +0 -451
  115. anemoi/datasets/create/filters/speeddir_to_uv.py +0 -95
  116. anemoi/datasets/create/filters/sum.py +0 -68
  117. anemoi/datasets/create/filters/transform.py +0 -51
  118. anemoi/datasets/create/filters/unrotate_winds.py +0 -105
  119. anemoi/datasets/create/filters/uv_to_speeddir.py +0 -94
  120. anemoi/datasets/create/filters/wz_to_w.py +0 -98
  121. anemoi/datasets/utils/__init__.py +0 -8
  122. anemoi_datasets-0.5.25.dist-info/RECORD +0 -150
  123. {anemoi_datasets-0.5.25.dist-info → anemoi_datasets-0.5.27.dist-info}/WHEEL +0 -0
  124. {anemoi_datasets-0.5.25.dist-info → anemoi_datasets-0.5.27.dist-info}/entry_points.txt +0 -0
  125. {anemoi_datasets-0.5.25.dist-info → anemoi_datasets-0.5.27.dist-info}/licenses/LICENSE +0 -0
  126. {anemoi_datasets-0.5.25.dist-info → anemoi_datasets-0.5.27.dist-info}/top_level.txt +0 -0
@@ -10,9 +10,6 @@
10
10
  import datetime
11
11
  from collections import defaultdict
12
12
  from typing import Any
13
- from typing import Dict
14
- from typing import List
15
- from typing import Tuple
16
13
 
17
14
  from earthkit.data.core.temporary import temp_file
18
15
  from earthkit.data.readers.grib.output import new_grib_output
@@ -63,7 +60,7 @@ def normalise_time_delta(t: Any) -> datetime.timedelta:
63
60
  return t
64
61
 
65
62
 
66
- def group_by_field(ds: Any) -> Dict[Tuple, List[Any]]:
63
+ def group_by_field(ds: Any) -> dict[tuple, list[Any]]:
67
64
  """Groups fields by their metadata excluding 'date', 'time', and 'step'.
68
65
 
69
66
  Parameters
@@ -87,7 +84,7 @@ def group_by_field(ds: Any) -> Dict[Tuple, List[Any]]:
87
84
 
88
85
 
89
86
  @legacy_source(__file__)
90
- def tendencies(dates: List[datetime.datetime], time_increment: Any, **kwargs: Any) -> Any:
87
+ def tendencies(dates: list[datetime.datetime], time_increment: Any, **kwargs: Any) -> Any:
91
88
  """Computes tendencies for the given dates and time increment.
92
89
 
93
90
  Parameters
@@ -8,8 +8,6 @@
8
8
  # nor does it submit to any jurisdiction.
9
9
 
10
10
  from typing import Any
11
- from typing import Dict
12
- from typing import Optional
13
11
 
14
12
  import earthkit.data as ekd
15
13
 
@@ -28,11 +26,11 @@ class XarraySourceBase(Source):
28
26
 
29
27
  emoji = "✖️" # For tracing
30
28
 
31
- options: Optional[Dict[str, Any]] = None
32
- flavour: Optional[Dict[str, Any]] = None
33
- patch: Optional[Dict[str, Any]] = None
29
+ options: dict[str, Any] | None = None
30
+ flavour: dict[str, Any] | None = None
31
+ patch: dict[str, Any] | None = None
34
32
 
35
- path_or_url: Optional[str] = None
33
+ path_or_url: str | None = None
36
34
 
37
35
  def __init__(self, context: Any, path: str = None, url: str = None, *args: Any, **kwargs: Any):
38
36
  """Initialise the source.
@@ -10,17 +10,12 @@
10
10
  import datetime
11
11
  import logging
12
12
  from typing import Any
13
- from typing import Dict
14
- from typing import List
15
- from typing import Optional
16
- from typing import Union
17
13
 
18
14
  import earthkit.data as ekd
19
15
  import xarray as xr
20
16
  from earthkit.data.core.fieldlist import MultiFieldList
21
17
 
22
18
  from anemoi.datasets.create.sources.patterns import iterate_patterns
23
- from anemoi.datasets.data.stores import name_to_zarr_store
24
19
 
25
20
  from ..legacy import legacy_source
26
21
  from .fieldlist import XarrayFieldList
@@ -28,7 +23,7 @@ from .fieldlist import XarrayFieldList
28
23
  LOG = logging.getLogger(__name__)
29
24
 
30
25
 
31
- def check(what: str, ds: xr.Dataset, paths: List[str], **kwargs: Any) -> None:
26
+ def check(what: str, ds: xr.Dataset, paths: list[str], **kwargs: Any) -> None:
32
27
  """Checks if the dataset has the expected number of fields.
33
28
 
34
29
  Parameters
@@ -54,12 +49,12 @@ def check(what: str, ds: xr.Dataset, paths: List[str], **kwargs: Any) -> None:
54
49
  def load_one(
55
50
  emoji: str,
56
51
  context: Any,
57
- dates: List[str],
58
- dataset: Union[str, xr.Dataset],
52
+ dates: list[str],
53
+ dataset: str | xr.Dataset,
59
54
  *,
60
- options: Optional[Dict[str, Any]] = None,
61
- flavour: Optional[str] = None,
62
- patch: Optional[Any] = None,
55
+ options: dict[str, Any] | None = None,
56
+ flavour: str | None = None,
57
+ patch: Any | None = None,
63
58
  **kwargs: Any,
64
59
  ) -> ekd.FieldList:
65
60
  """Loads a single dataset.
@@ -89,28 +84,17 @@ def load_one(
89
84
  The loaded dataset.
90
85
  """
91
86
 
92
- """
93
- We manage the S3 client ourselves, bypassing fsspec and s3fs layers, because sometimes something on the stack
94
- zarr/fsspec/s3fs/boto3 (?) seem to flags files as missing when they actually are not (maybe when S3 reports some sort of
95
- connection error). In that case, Zarr will silently fill the chunks that could not be downloaded with NaNs.
96
- See https://github.com/pydata/xarray/issues/8842
97
-
98
- We have seen this bug triggered when we run many clients in parallel, for example, when we create a new dataset using `xarray-zarr`.
99
- """
100
-
101
87
  if options is None:
102
88
  options = {}
103
89
 
104
90
  context.trace(emoji, dataset, options, kwargs)
105
91
 
106
- if isinstance(dataset, str) and ".zarr" in dataset:
107
- data = xr.open_zarr(name_to_zarr_store(dataset), **options)
108
- elif "planetarycomputer" in dataset:
109
- store = name_to_zarr_store(dataset)
110
- if "store" in store:
111
- data = xr.open_zarr(**store)
112
- if "filename_or_obj" in store:
113
- data = xr.open_dataset(**store)
92
+ if isinstance(dataset, str) and dataset.endswith(".zarr"):
93
+ # If the dataset is a zarr store, we need to use the zarr engine
94
+ options["engine"] = "zarr"
95
+
96
+ if isinstance(dataset, xr.Dataset):
97
+ data = dataset
114
98
  else:
115
99
  data = xr.open_dataset(dataset, **options)
116
100
 
@@ -119,7 +103,6 @@ def load_one(
119
103
  if len(dates) == 0:
120
104
  result = fs.sel(**kwargs)
121
105
  else:
122
- print("dates", dates, kwargs)
123
106
  result = MultiFieldList([fs.sel(valid_datetime=date, **kwargs) for date in dates])
124
107
 
125
108
  if len(result) == 0:
@@ -130,7 +113,7 @@ def load_one(
130
113
  a = ["valid_datetime", k.metadata("valid_datetime", default=None)]
131
114
  for n in kwargs.keys():
132
115
  a.extend([n, k.metadata(n, default=None)])
133
- print([str(x) for x in a])
116
+ LOG.warning(f"{[str(x) for x in a]}")
134
117
 
135
118
  if i > 16:
136
119
  break
@@ -140,7 +123,7 @@ def load_one(
140
123
  return result
141
124
 
142
125
 
143
- def load_many(emoji: str, context: Any, dates: List[datetime.datetime], pattern: str, **kwargs: Any) -> ekd.FieldList:
126
+ def load_many(emoji: str, context: Any, dates: list[datetime.datetime], pattern: str, **kwargs: Any) -> ekd.FieldList:
144
127
  """Loads multiple datasets.
145
128
 
146
129
  Parameters
@@ -170,7 +153,7 @@ def load_many(emoji: str, context: Any, dates: List[datetime.datetime], pattern:
170
153
 
171
154
 
172
155
  @legacy_source("xarray")
173
- def execute(context: Any, dates: List[str], url: str, *args: Any, **kwargs: Any) -> ekd.FieldList:
156
+ def execute(context: Any, dates: list[str], url: str, *args: Any, **kwargs: Any) -> ekd.FieldList:
174
157
  """Executes the loading of datasets.
175
158
 
176
159
  Parameters
@@ -13,10 +13,6 @@ from __future__ import annotations
13
13
  import datetime
14
14
  import logging
15
15
  from typing import Any
16
- from typing import Dict
17
- from typing import Optional
18
- from typing import Tuple
19
- from typing import Union
20
16
 
21
17
  import numpy as np
22
18
  import xarray as xr
@@ -95,6 +91,7 @@ class Coordinate:
95
91
  is_member = False
96
92
  is_x = False
97
93
  is_y = False
94
+ is_point = False
98
95
 
99
96
  def __init__(self, variable: xr.DataArray) -> None:
100
97
  """Initialize the coordinate.
@@ -106,7 +103,7 @@ class Coordinate:
106
103
  """
107
104
  self.variable = variable
108
105
  self.scalar = is_scalar(variable)
109
- self.kwargs: Dict[str, Any] = {} # Used when creating a new coordinate (reduced method)
106
+ self.kwargs: dict[str, Any] = {} # Used when creating a new coordinate (reduced method)
110
107
 
111
108
  def __len__(self) -> int:
112
109
  """Get the length of the coordinate.
@@ -126,7 +123,7 @@ class Coordinate:
126
123
  str
127
124
  The string representation of the coordinate.
128
125
  """
129
- return "%s[name=%s,values=%s,shape=%s]" % (
126
+ return "{}[name={},values={},shape={}]".format(
130
127
  self.__class__.__name__,
131
128
  self.variable.name,
132
129
  self.variable.values if self.scalar else len(self),
@@ -151,7 +148,7 @@ class Coordinate:
151
148
  **self.kwargs,
152
149
  )
153
150
 
154
- def index(self, value: Union[Any, list, tuple]) -> Optional[Union[int, list]]:
151
+ def index(self, value: Any | list | tuple) -> int | list | None:
155
152
  """Return the index of the value in the coordinate.
156
153
 
157
154
  Parameters
@@ -171,7 +168,7 @@ class Coordinate:
171
168
  return self._index_multiple(value)
172
169
  return self._index_single(value)
173
170
 
174
- def _index_single(self, value: Any) -> Optional[int]:
171
+ def _index_single(self, value: Any) -> int | None:
175
172
  """Return the index of a single value in the coordinate.
176
173
 
177
174
  Parameters
@@ -204,7 +201,7 @@ class Coordinate:
204
201
 
205
202
  return None
206
203
 
207
- def _index_multiple(self, value: list) -> Optional[list]:
204
+ def _index_multiple(self, value: list) -> list | None:
208
205
  """Return the indices of multiple values in the coordinate.
209
206
 
210
207
  Parameters
@@ -274,7 +271,7 @@ class TimeCoordinate(Coordinate):
274
271
  is_time = True
275
272
  mars_names = ("valid_datetime",)
276
273
 
277
- def index(self, time: datetime.datetime) -> Optional[int]:
274
+ def index(self, time: datetime.datetime) -> int | None:
278
275
  """Return the index of the time in the coordinate.
279
276
 
280
277
  Parameters
@@ -296,7 +293,7 @@ class DateCoordinate(Coordinate):
296
293
  is_date = True
297
294
  mars_names = ("date",)
298
295
 
299
- def index(self, date: datetime.datetime) -> Optional[int]:
296
+ def index(self, date: datetime.datetime) -> int | None:
300
297
  """Return the index of the date in the coordinate.
301
298
 
302
299
  Parameters
@@ -390,6 +387,13 @@ class EnsembleCoordinate(Coordinate):
390
387
  return value
391
388
 
392
389
 
390
+ class PointCoordinate(Coordinate):
391
+ """Coordinate class for point data."""
392
+
393
+ is_point = True
394
+ mars_names = ("point",)
395
+
396
+
393
397
  class LongitudeCoordinate(Coordinate):
394
398
  """Coordinate class for longitude."""
395
399
 
@@ -428,7 +432,7 @@ class ScalarCoordinate(Coordinate):
428
432
  is_grid = False
429
433
 
430
434
  @property
431
- def mars_names(self) -> Tuple[str, ...]:
435
+ def mars_names(self) -> tuple[str, ...]:
432
436
  """Get the MARS names for the coordinate."""
433
437
  return (self.variable.name,)
434
438
 
@@ -12,9 +12,6 @@ import datetime
12
12
  import logging
13
13
  from functools import cached_property
14
14
  from typing import Any
15
- from typing import Dict
16
- from typing import Optional
17
- from typing import Tuple
18
15
 
19
16
  from earthkit.data import Field
20
17
  from earthkit.data.core.fieldlist import math
@@ -80,29 +77,33 @@ class XArrayField(Field):
80
77
  # Copy the metadata from the owner
81
78
  self._md = owner._metadata.copy()
82
79
 
80
+ aliases = {}
83
81
  for coord_name, coord_value in self.selection.coords.items():
84
82
  if is_scalar(coord_value):
85
83
  # Extract the single value from the scalar dimension
86
84
  # and store it in the metadata
87
85
  coordinate = owner.by_name[coord_name]
88
- self._md[coord_name] = coordinate.normalise(extract_single_value(coord_value))
86
+ normalised = coordinate.normalise(extract_single_value(coord_value))
87
+ self._md[coord_name] = normalised
88
+ for alias in coordinate.mars_names:
89
+ aliases[alias] = normalised
90
+
91
+ # Add metadata aliases (e.g. levelist == level) only if they are not already present
92
+ for alias, value in aliases.items():
93
+ if alias not in self._md:
94
+ self._md[alias] = value
89
95
 
90
- # print(values.ndim, values.shape, selection.dims)
91
96
  # By now, the only dimensions should be latitude and longitude
92
97
  self._shape = tuple(list(self.selection.shape)[-2:])
93
98
  if math.prod(self._shape) != math.prod(self.selection.shape):
94
- print(self.selection.ndim, self.selection.shape)
95
- print(self.selection)
96
- raise ValueError("Invalid shape for selection")
99
+ raise ValueError(f"Invalid shape for selection {self._shape=}, {self.selection.shape=} {self.selection=}")
97
100
 
98
101
  @property
99
- def shape(self) -> Tuple[int, int]:
102
+ def shape(self) -> tuple[int, int]:
100
103
  """Return the shape of the field."""
101
104
  return self._shape
102
105
 
103
- def to_numpy(
104
- self, flatten: bool = False, dtype: Optional[type] = None, index: Optional[int] = None
105
- ) -> NDArray[Any]:
106
+ def to_numpy(self, flatten: bool = False, dtype: type | None = None, index: int | None = None) -> NDArray[Any]:
106
107
  """Convert the selection to a numpy array.
107
108
 
108
109
  Returns
@@ -140,7 +141,7 @@ class XArrayField(Field):
140
141
  """Return the grid points of the field."""
141
142
  return self.owner.grid_points()
142
143
 
143
- def to_latlon(self, flatten: bool = True) -> Dict[str, Any]:
144
+ def to_latlon(self, flatten: bool = True) -> dict[str, Any]:
144
145
  """Convert the selection to latitude and longitude coordinates.
145
146
 
146
147
  Returns
@@ -157,7 +158,7 @@ class XArrayField(Field):
157
158
  return dict(lat=self.latitudes, lon=self.longitudes)
158
159
 
159
160
  @property
160
- def resolution(self) -> Optional[Any]:
161
+ def resolution(self) -> Any | None:
161
162
  """Return the resolution of the field."""
162
163
  return None
163
164
 
@@ -188,9 +189,9 @@ class XArrayField(Field):
188
189
 
189
190
  def __repr__(self) -> str:
190
191
  """Return a string representation of the field."""
191
- return repr(self._metadata)
192
+ return f"XArrayField({self._metadata})"
192
193
 
193
- def _values(self, dtype: Optional[type] = None) -> Any:
194
+ def _values(self, dtype: type | None = None) -> Any:
194
195
  """Return the values of the selection.
195
196
 
196
197
  Returns
@@ -11,10 +11,6 @@
11
11
  import json
12
12
  import logging
13
13
  from typing import Any
14
- from typing import Dict
15
- from typing import List
16
- from typing import Optional
17
- from typing import Union
18
14
 
19
15
  import xarray as xr
20
16
  import yaml
@@ -33,7 +29,7 @@ LOG = logging.getLogger(__name__)
33
29
  class XarrayFieldList(FieldList):
34
30
  """A class to represent a list of fields from an xarray Dataset."""
35
31
 
36
- def __init__(self, ds: xr.Dataset, variables: List[Variable]) -> None:
32
+ def __init__(self, ds: xr.Dataset, variables: list[Variable]) -> None:
37
33
  """Initialize the XarrayFieldList.
38
34
 
39
35
  Parameters
@@ -44,7 +40,7 @@ class XarrayFieldList(FieldList):
44
40
  The list of variables.
45
41
  """
46
42
  self.ds: xr.Dataset = ds
47
- self.variables: List[Variable] = variables.copy()
43
+ self.variables: list[Variable] = variables.copy()
48
44
  self.total_length: int = sum(v.length for v in variables)
49
45
 
50
46
  def __repr__(self) -> str:
@@ -90,8 +86,8 @@ class XarrayFieldList(FieldList):
90
86
  cls,
91
87
  ds: xr.Dataset,
92
88
  *,
93
- flavour: Optional[Union[str, Dict[str, Any]]] = None,
94
- patch: Optional[Dict[str, Any]] = None,
89
+ flavour: str | dict[str, Any] | None = None,
90
+ patch: dict[str, Any] | None = None,
95
91
  ) -> "XarrayFieldList":
96
92
  """Create an XarrayFieldList from an xarray Dataset.
97
93
 
@@ -112,7 +108,7 @@ class XarrayFieldList(FieldList):
112
108
  if patch is not None:
113
109
  ds = patch_dataset(ds, patch)
114
110
 
115
- variables: List[Variable] = []
111
+ variables: list[Variable] = []
116
112
 
117
113
  if isinstance(flavour, str):
118
114
  with open(flavour) as f:
@@ -121,9 +117,9 @@ class XarrayFieldList(FieldList):
121
117
  else:
122
118
  flavour = json.load(f)
123
119
 
124
- if isinstance(flavour, Dict):
125
- flavour_coords: List[str] = [coords["name"] for coords in flavour["rules"].values()]
126
- ds_dims: List[str] = [dim for dim in ds._dims]
120
+ if isinstance(flavour, dict):
121
+ flavour_coords: list[str] = [coords["name"] for coords in flavour["rules"].values()]
122
+ ds_dims: list[str] = [dim for dim in ds._dims]
127
123
  for dim in ds_dims:
128
124
  if dim in flavour_coords and dim not in ds._coord_names:
129
125
  ds = ds.assign_coords({dim: ds[dim]})
@@ -154,7 +150,7 @@ class XarrayFieldList(FieldList):
154
150
  continue
155
151
 
156
152
  variable = ds[name]
157
- coordinates: List[Any] = []
153
+ coordinates: list[Any] = []
158
154
 
159
155
  for coord in variable.coords:
160
156
 
@@ -210,7 +206,7 @@ class XarrayFieldList(FieldList):
210
206
  So we get an extra chance to filter the fields by the metadata.
211
207
  """
212
208
 
213
- variables: List[Variable] = []
209
+ variables: list[Variable] = []
214
210
  count: int = 0
215
211
 
216
212
  for v in self.variables:
@@ -223,7 +219,7 @@ class XarrayFieldList(FieldList):
223
219
 
224
220
  if match:
225
221
  count += 1
226
- missing: Dict[str, Any] = {}
222
+ missing: dict[str, Any] = {}
227
223
 
228
224
  # Select from the variable's coordinates (time, level, number, ....)
229
225
  # This may return a new variable with a isel() slice of the selection