anemoi-datasets 0.5.26__py3-none-any.whl → 0.5.28__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 (116) 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/inspect.py +27 -35
  7. anemoi/datasets/commands/recipe/__init__.py +93 -0
  8. anemoi/datasets/commands/recipe/format.py +55 -0
  9. anemoi/datasets/commands/recipe/migrate.py +555 -0
  10. anemoi/datasets/commands/validate.py +59 -0
  11. anemoi/datasets/compute/recentre.py +3 -6
  12. anemoi/datasets/create/__init__.py +64 -26
  13. anemoi/datasets/create/check.py +10 -12
  14. anemoi/datasets/create/chunks.py +1 -2
  15. anemoi/datasets/create/config.py +5 -6
  16. anemoi/datasets/create/input/__init__.py +44 -65
  17. anemoi/datasets/create/input/action.py +296 -238
  18. anemoi/datasets/create/input/context/__init__.py +71 -0
  19. anemoi/datasets/create/input/context/field.py +54 -0
  20. anemoi/datasets/create/input/data_sources.py +7 -9
  21. anemoi/datasets/create/input/misc.py +2 -75
  22. anemoi/datasets/create/input/repeated_dates.py +11 -130
  23. anemoi/datasets/{utils → create/input/result}/__init__.py +10 -1
  24. anemoi/datasets/create/input/{result.py → result/field.py} +36 -120
  25. anemoi/datasets/create/input/trace.py +1 -1
  26. anemoi/datasets/create/patch.py +1 -2
  27. anemoi/datasets/create/persistent.py +3 -5
  28. anemoi/datasets/create/size.py +1 -3
  29. anemoi/datasets/create/sources/accumulations.py +120 -145
  30. anemoi/datasets/create/sources/accumulations2.py +20 -53
  31. anemoi/datasets/create/sources/anemoi_dataset.py +46 -42
  32. anemoi/datasets/create/sources/constants.py +39 -40
  33. anemoi/datasets/create/sources/empty.py +22 -19
  34. anemoi/datasets/create/sources/fdb.py +133 -0
  35. anemoi/datasets/create/sources/forcings.py +29 -29
  36. anemoi/datasets/create/sources/grib.py +94 -78
  37. anemoi/datasets/create/sources/grib_index.py +57 -55
  38. anemoi/datasets/create/sources/hindcasts.py +57 -59
  39. anemoi/datasets/create/sources/legacy.py +10 -62
  40. anemoi/datasets/create/sources/mars.py +121 -149
  41. anemoi/datasets/create/sources/netcdf.py +28 -25
  42. anemoi/datasets/create/sources/opendap.py +28 -26
  43. anemoi/datasets/create/sources/patterns.py +4 -6
  44. anemoi/datasets/create/sources/recentre.py +46 -48
  45. anemoi/datasets/create/sources/repeated_dates.py +44 -0
  46. anemoi/datasets/create/sources/source.py +26 -51
  47. anemoi/datasets/create/sources/tendencies.py +68 -98
  48. anemoi/datasets/create/sources/xarray.py +4 -6
  49. anemoi/datasets/create/sources/xarray_support/__init__.py +40 -36
  50. anemoi/datasets/create/sources/xarray_support/coordinates.py +8 -12
  51. anemoi/datasets/create/sources/xarray_support/field.py +20 -16
  52. anemoi/datasets/create/sources/xarray_support/fieldlist.py +11 -15
  53. anemoi/datasets/create/sources/xarray_support/flavour.py +42 -42
  54. anemoi/datasets/create/sources/xarray_support/grid.py +15 -9
  55. anemoi/datasets/create/sources/xarray_support/metadata.py +19 -128
  56. anemoi/datasets/create/sources/xarray_support/patch.py +4 -6
  57. anemoi/datasets/create/sources/xarray_support/time.py +10 -13
  58. anemoi/datasets/create/sources/xarray_support/variable.py +21 -21
  59. anemoi/datasets/create/sources/xarray_zarr.py +28 -25
  60. anemoi/datasets/create/sources/zenodo.py +43 -41
  61. anemoi/datasets/create/statistics/__init__.py +3 -6
  62. anemoi/datasets/create/testing.py +4 -0
  63. anemoi/datasets/create/typing.py +1 -2
  64. anemoi/datasets/create/utils.py +0 -43
  65. anemoi/datasets/create/zarr.py +7 -2
  66. anemoi/datasets/data/__init__.py +15 -6
  67. anemoi/datasets/data/complement.py +7 -12
  68. anemoi/datasets/data/concat.py +5 -8
  69. anemoi/datasets/data/dataset.py +48 -47
  70. anemoi/datasets/data/debug.py +7 -9
  71. anemoi/datasets/data/ensemble.py +4 -6
  72. anemoi/datasets/data/fill_missing.py +7 -10
  73. anemoi/datasets/data/forwards.py +22 -26
  74. anemoi/datasets/data/grids.py +12 -168
  75. anemoi/datasets/data/indexing.py +9 -12
  76. anemoi/datasets/data/interpolate.py +7 -15
  77. anemoi/datasets/data/join.py +8 -12
  78. anemoi/datasets/data/masked.py +6 -11
  79. anemoi/datasets/data/merge.py +5 -9
  80. anemoi/datasets/data/misc.py +41 -45
  81. anemoi/datasets/data/missing.py +11 -16
  82. anemoi/datasets/data/observations/__init__.py +8 -14
  83. anemoi/datasets/data/padded.py +3 -5
  84. anemoi/datasets/data/records/backends/__init__.py +2 -2
  85. anemoi/datasets/data/rescale.py +5 -12
  86. anemoi/datasets/data/rolling_average.py +141 -0
  87. anemoi/datasets/data/select.py +13 -16
  88. anemoi/datasets/data/statistics.py +4 -7
  89. anemoi/datasets/data/stores.py +22 -29
  90. anemoi/datasets/data/subset.py +8 -11
  91. anemoi/datasets/data/unchecked.py +7 -11
  92. anemoi/datasets/data/xy.py +25 -21
  93. anemoi/datasets/dates/__init__.py +15 -18
  94. anemoi/datasets/dates/groups.py +7 -10
  95. anemoi/datasets/dumper.py +76 -0
  96. anemoi/datasets/grids.py +4 -185
  97. anemoi/datasets/schemas/recipe.json +131 -0
  98. anemoi/datasets/testing.py +93 -7
  99. anemoi/datasets/validate.py +598 -0
  100. {anemoi_datasets-0.5.26.dist-info → anemoi_datasets-0.5.28.dist-info}/METADATA +7 -4
  101. anemoi_datasets-0.5.28.dist-info/RECORD +134 -0
  102. anemoi/datasets/create/filter.py +0 -48
  103. anemoi/datasets/create/input/concat.py +0 -164
  104. anemoi/datasets/create/input/context.py +0 -89
  105. anemoi/datasets/create/input/empty.py +0 -54
  106. anemoi/datasets/create/input/filter.py +0 -118
  107. anemoi/datasets/create/input/function.py +0 -233
  108. anemoi/datasets/create/input/join.py +0 -130
  109. anemoi/datasets/create/input/pipe.py +0 -66
  110. anemoi/datasets/create/input/step.py +0 -177
  111. anemoi/datasets/create/input/template.py +0 -162
  112. anemoi_datasets-0.5.26.dist-info/RECORD +0 -131
  113. {anemoi_datasets-0.5.26.dist-info → anemoi_datasets-0.5.28.dist-info}/WHEEL +0 -0
  114. {anemoi_datasets-0.5.26.dist-info → anemoi_datasets-0.5.28.dist-info}/entry_points.txt +0 -0
  115. {anemoi_datasets-0.5.26.dist-info → anemoi_datasets-0.5.28.dist-info}/licenses/LICENSE +0 -0
  116. {anemoi_datasets-0.5.26.dist-info → anemoi_datasets-0.5.28.dist-info}/top_level.txt +0 -0
@@ -12,8 +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
15
 
18
16
  from anemoi.utils.dates import as_datetime
19
17
  from earthkit.data.core.geography import Geography
@@ -23,87 +21,6 @@ from earthkit.data.utils.projections import Projection
23
21
  LOG = logging.getLogger(__name__)
24
22
 
25
23
 
26
- class _MDMapping:
27
- """A class to handle metadata mapping for variables.
28
-
29
- Attributes
30
- ----------
31
- variable : Any
32
- The variable to map.
33
- time : Any
34
- The time associated with the variable.
35
- mapping : Dict[str, str]
36
- A dictionary mapping keys to variable names.
37
- """
38
-
39
- def __init__(self, variable: Any) -> None:
40
- """Initialize the _MDMapping class.
41
-
42
- Parameters
43
- ----------
44
- variable : Any
45
- The variable to map.
46
- """
47
- self.variable = variable
48
- self.time = variable.time
49
- self.mapping = dict()
50
- # Aliases
51
-
52
- def _from_user(self, key: str) -> str:
53
- """Get the internal key corresponding to a user-provided key.
54
-
55
- Parameters
56
- ----------
57
- key : str
58
- The user-provided key.
59
-
60
- Returns
61
- -------
62
- str
63
- The internal key corresponding to the user-provided key.
64
- """
65
- return self.mapping.get(key, key)
66
-
67
- def from_user(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
68
- """Convert user-provided keys to internal keys.
69
-
70
- Parameters
71
- ----------
72
- kwargs : Dict[str, Any]
73
- A dictionary of user-provided keys and values.
74
-
75
- Returns
76
- -------
77
- Dict[str, Any]
78
- A dictionary with internal keys and original values.
79
- """
80
- return {self._from_user(k): v for k, v in kwargs.items()}
81
-
82
- def __repr__(self) -> str:
83
- """Return a string representation of the _MDMapping object.
84
-
85
- Returns
86
- -------
87
- str
88
- String representation of the _MDMapping object.
89
- """
90
- return f"MDMapping({self.mapping})"
91
-
92
- def fill_time_metadata(self, field: Any, md: Dict[str, Any]) -> None:
93
- """Fill the time metadata for a field.
94
-
95
- Parameters
96
- ----------
97
- field : Any
98
- The field to fill metadata for.
99
- md : Dict[str, Any]
100
- The metadata dictionary to update.
101
- """
102
- valid_datetime = self.variable.time.fill_time_metadata(field._md, md)
103
- if valid_datetime is not None:
104
- md["valid_datetime"] = as_datetime(valid_datetime).isoformat()
105
-
106
-
107
24
  class XArrayMetadata(RawMetadata):
108
25
  """A class to handle metadata for XArray fields.
109
26
 
@@ -129,10 +46,16 @@ class XArrayMetadata(RawMetadata):
129
46
  field : Any
130
47
  The field to extract metadata from.
131
48
  """
49
+ from .field import XArrayField
50
+
51
+ assert isinstance(field, XArrayField), type(field)
132
52
  self._field = field
133
53
  md = field._md.copy()
134
- self._mapping = _MDMapping(field.owner)
135
- self._mapping.fill_time_metadata(field, md)
54
+
55
+ valid_datetime = field.owner.time.fill_time_metadata(field._md, md)
56
+ if valid_datetime is not None:
57
+ md["valid_datetime"] = as_datetime(valid_datetime).isoformat()
58
+
136
59
  super().__init__(md)
137
60
 
138
61
  @cached_property
@@ -140,7 +63,7 @@ class XArrayMetadata(RawMetadata):
140
63
  """Get the geography information for the field."""
141
64
  return XArrayFieldGeography(self._field, self._field.owner.grid)
142
65
 
143
- def as_namespace(self, namespace: Optional[str] = None) -> Dict[str, Any]:
66
+ def as_namespace(self, namespace: str | None = None) -> dict[str, Any]:
144
67
  """Get the metadata as a specific namespace.
145
68
 
146
69
  Parameters
@@ -162,7 +85,7 @@ class XArrayMetadata(RawMetadata):
162
85
  elif namespace == "mars":
163
86
  return self._as_mars()
164
87
 
165
- def _as_mars(self) -> Dict[str, Any]:
88
+ def _as_mars(self) -> dict[str, Any]:
166
89
  """Get the metadata as MARS namespace.
167
90
 
168
91
  Returns
@@ -172,7 +95,7 @@ class XArrayMetadata(RawMetadata):
172
95
  """
173
96
  return {}
174
97
 
175
- def _base_datetime(self) -> Optional[datetime.datetime]:
98
+ def _base_datetime(self) -> datetime.datetime | None:
176
99
  """Get the base datetime for the field.
177
100
 
178
101
  Returns
@@ -182,7 +105,7 @@ class XArrayMetadata(RawMetadata):
182
105
  """
183
106
  return self._field.forecast_reference_time
184
107
 
185
- def _valid_datetime(self) -> Optional[datetime.datetime]:
108
+ def _valid_datetime(self) -> datetime.datetime | None:
186
109
  """Get the valid datetime for the field.
187
110
 
188
111
  Returns
@@ -192,38 +115,6 @@ class XArrayMetadata(RawMetadata):
192
115
  """
193
116
  return self._get("valid_datetime")
194
117
 
195
- def get(self, key: str, astype: Optional[type] = None, **kwargs: Any) -> Any:
196
- """Get a metadata value by key.
197
-
198
- Parameters
199
- ----------
200
- key : str
201
- The key to get the value for.
202
- astype : Optional[type]
203
- The type to cast the value to.
204
- **kwargs : Any
205
- Additional keyword arguments.
206
-
207
- Returns
208
- -------
209
- Any
210
- The value for the specified key, optionally cast to the specified type.
211
- """
212
-
213
- if key == "levelist":
214
- # Special case for levelist, for compatibility with GRIB
215
- if key not in self._d and "level" in self._d:
216
- key = "level"
217
-
218
- if key in self._d:
219
- if astype is not None:
220
- return astype(self._d[key])
221
- return self._d[key]
222
-
223
- key = self._mapping._from_user(key)
224
-
225
- return super().get(key, astype=astype, **kwargs)
226
-
227
118
 
228
119
  class XArrayFieldGeography(Geography):
229
120
  """A class to handle geography information for XArray fields.
@@ -280,7 +171,7 @@ class XArrayFieldGeography(Geography):
280
171
  """
281
172
  raise NotImplementedError()
282
173
 
283
- def latitudes(self, dtype: Optional[type] = None) -> Any:
174
+ def latitudes(self, dtype: type | None = None) -> Any:
284
175
  """Get the latitudes for the field.
285
176
 
286
177
  Parameters
@@ -298,7 +189,7 @@ class XArrayFieldGeography(Geography):
298
189
  return result.astype(dtype)
299
190
  return result
300
191
 
301
- def longitudes(self, dtype: Optional[type] = None) -> Any:
192
+ def longitudes(self, dtype: type | None = None) -> Any:
302
193
  """Get the longitudes for the field.
303
194
 
304
195
  Parameters
@@ -316,7 +207,7 @@ class XArrayFieldGeography(Geography):
316
207
  return result.astype(dtype)
317
208
  return result
318
209
 
319
- def resolution(self) -> Optional[Any]:
210
+ def resolution(self) -> Any | None:
320
211
  """Get the resolution for the field.
321
212
 
322
213
  Returns
@@ -327,7 +218,7 @@ class XArrayFieldGeography(Geography):
327
218
  # TODO: implement resolution
328
219
  return None
329
220
 
330
- def mars_grid(self) -> Optional[Any]:
221
+ def mars_grid(self) -> Any | None:
331
222
  """Get the MARS grid for the field.
332
223
 
333
224
  Returns
@@ -338,7 +229,7 @@ class XArrayFieldGeography(Geography):
338
229
  # TODO: implement mars_grid
339
230
  return None
340
231
 
341
- def mars_area(self) -> Optional[Any]:
232
+ def mars_area(self) -> Any | None:
342
233
  """Get the MARS area for the field.
343
234
 
344
235
  Returns
@@ -350,7 +241,7 @@ class XArrayFieldGeography(Geography):
350
241
  # return [self.north, self.west, self.south, self.east]
351
242
  return None
352
243
 
353
- def x(self, dtype: Optional[type] = None) -> None:
244
+ def x(self, dtype: type | None = None) -> None:
354
245
  """Get the x-coordinates for the field.
355
246
 
356
247
  Parameters
@@ -365,7 +256,7 @@ class XArrayFieldGeography(Geography):
365
256
  """
366
257
  raise NotImplementedError()
367
258
 
368
- def y(self, dtype: Optional[type] = None) -> None:
259
+ def y(self, dtype: type | None = None) -> None:
369
260
  """Get the y-coordinates for the field.
370
261
 
371
262
  Parameters
@@ -10,15 +10,13 @@
10
10
 
11
11
  import logging
12
12
  from typing import Any
13
- from typing import Dict
14
- from typing import List
15
13
 
16
14
  import xarray as xr
17
15
 
18
16
  LOG = logging.getLogger(__name__)
19
17
 
20
18
 
21
- def patch_attributes(ds: xr.Dataset, attributes: Dict[str, Dict[str, Any]]) -> Any:
19
+ def patch_attributes(ds: xr.Dataset, attributes: dict[str, dict[str, Any]]) -> Any:
22
20
  """Patch the attributes of the dataset.
23
21
 
24
22
  Parameters
@@ -40,7 +38,7 @@ def patch_attributes(ds: xr.Dataset, attributes: Dict[str, Dict[str, Any]]) -> A
40
38
  return ds
41
39
 
42
40
 
43
- def patch_coordinates(ds: xr.Dataset, coordinates: List[str]) -> Any:
41
+ def patch_coordinates(ds: xr.Dataset, coordinates: list[str]) -> Any:
44
42
  """Patch the coordinates of the dataset.
45
43
 
46
44
  Parameters
@@ -79,7 +77,7 @@ def patch_rename(ds: xr.Dataset, renames: dict[str, str]) -> Any:
79
77
  return ds.rename(renames)
80
78
 
81
79
 
82
- def patch_sort_coordinate(ds: xr.Dataset, sort_coordinates: List[str]) -> Any:
80
+ def patch_sort_coordinate(ds: xr.Dataset, sort_coordinates: list[str]) -> Any:
83
81
  """Sort the coordinates of the dataset.
84
82
 
85
83
  Parameters
@@ -108,7 +106,7 @@ PATCHES = {
108
106
  }
109
107
 
110
108
 
111
- def patch_dataset(ds: xr.Dataset, patch: Dict[str, Dict[str, Any]]) -> Any:
109
+ def patch_dataset(ds: xr.Dataset, patch: dict[str, dict[str, Any]]) -> Any:
112
110
  """Patch the dataset.
113
111
 
114
112
  Parameters
@@ -13,9 +13,6 @@ import logging
13
13
  from abc import ABC
14
14
  from abc import abstractmethod
15
15
  from typing import Any
16
- from typing import Dict
17
- from typing import List
18
- from typing import Optional
19
16
 
20
17
  from anemoi.utils.dates import as_datetime
21
18
 
@@ -29,7 +26,7 @@ class Time(ABC):
29
26
  """Base class for different time representations."""
30
27
 
31
28
  @classmethod
32
- def from_coordinates(cls, coordinates: List[Coordinate]) -> "Time":
29
+ def from_coordinates(cls, coordinates: list[Coordinate]) -> "Time":
33
30
  """Create a Time instance from a list of coordinates.
34
31
 
35
32
  Returns
@@ -85,7 +82,7 @@ class Time(ABC):
85
82
  raise NotImplementedError(f"{len(date_coordinate)=} {len(time_coordinate)=} {len(step_coordinate)=}")
86
83
 
87
84
  @abstractmethod
88
- def select_valid_datetime(self, variable: Variable) -> Optional[str]:
85
+ def select_valid_datetime(self, variable: Variable) -> str | None:
89
86
  """Select the valid datetime for a given variable.
90
87
 
91
88
  Parameters
@@ -101,7 +98,7 @@ class Time(ABC):
101
98
  pass
102
99
 
103
100
  @abstractmethod
104
- def fill_time_metadata(self, coords_values: Dict[str, Any], metadata: Dict[str, Any]) -> None:
101
+ def fill_time_metadata(self, coords_values: dict[str, Any], metadata: dict[str, Any]) -> None:
105
102
  """Fill metadata with time information.
106
103
 
107
104
  Args
@@ -118,7 +115,7 @@ class Time(ABC):
118
115
  class Constant(Time):
119
116
  """Represents a constant time."""
120
117
 
121
- def fill_time_metadata(self, coords_values: Dict[str, Any], metadata: Dict[str, Any]) -> None:
118
+ def fill_time_metadata(self, coords_values: dict[str, Any], metadata: dict[str, Any]) -> None:
122
119
  """Fill metadata with time information.
123
120
 
124
121
  Parameters
@@ -154,7 +151,7 @@ class Analysis(Time):
154
151
  """
155
152
  self.time_coordinate_name = time_coordinate.variable.name
156
153
 
157
- def fill_time_metadata(self, coords_values: Dict[str, Any], metadata: Dict[str, Any]) -> Any:
154
+ def fill_time_metadata(self, coords_values: dict[str, Any], metadata: dict[str, Any]) -> Any:
158
155
  """Fill metadata with time information.
159
156
 
160
157
  Parameters
@@ -197,7 +194,7 @@ class ForecastFromValidTimeAndStep(Time):
197
194
  """Represents a forecast time derived from valid time and step."""
198
195
 
199
196
  def __init__(
200
- self, time_coordinate: Coordinate, step_coordinate: Coordinate, date_coordinate: Optional[Coordinate] = None
197
+ self, time_coordinate: Coordinate, step_coordinate: Coordinate, date_coordinate: Coordinate | None = None
201
198
  ) -> None:
202
199
  """Initialize ForecastFromValidTimeAndStep with time, step, and optional date coordinates.
203
200
 
@@ -214,7 +211,7 @@ class ForecastFromValidTimeAndStep(Time):
214
211
  self.step_coordinate_name = step_coordinate.variable.name
215
212
  self.date_coordinate_name = date_coordinate.variable.name if date_coordinate else None
216
213
 
217
- def fill_time_metadata(self, coords_values: Dict[str, Any], metadata: Dict[str, Any]) -> Any:
214
+ def fill_time_metadata(self, coords_values: dict[str, Any], metadata: dict[str, Any]) -> Any:
218
215
  """Fill metadata with time information.
219
216
 
220
217
  Returns
@@ -285,7 +282,7 @@ class ForecastFromValidTimeAndBaseTime(Time):
285
282
  self.date_coordinate_name = date_coordinate.name
286
283
  self.time_coordinate_name = time_coordinate.name
287
284
 
288
- def fill_time_metadata(self, coords_values: Dict[str, Any], metadata: Dict[str, Any]) -> Any:
285
+ def fill_time_metadata(self, coords_values: dict[str, Any], metadata: dict[str, Any]) -> Any:
289
286
  """Fill metadata with time information.
290
287
 
291
288
  Returns
@@ -346,7 +343,7 @@ class ForecastFromBaseTimeAndDate(Time):
346
343
  self.date_coordinate_name = date_coordinate.name
347
344
  self.step_coordinate_name = step_coordinate.name
348
345
 
349
- def fill_time_metadata(self, coords_values: Dict[str, Any], metadata: Dict[str, Any]) -> Any:
346
+ def fill_time_metadata(self, coords_values: dict[str, Any], metadata: dict[str, Any]) -> Any:
350
347
  """Fill metadata with time information.
351
348
 
352
349
  Returns
@@ -375,7 +372,7 @@ class ForecastFromBaseTimeAndDate(Time):
375
372
 
376
373
  return date + step
377
374
 
378
- def select_valid_datetime(self, variable: Variable) -> Optional[str]:
375
+ def select_valid_datetime(self, variable: Variable) -> str | None:
379
376
  """Select the valid datetime for a given variable.
380
377
 
381
378
  Parameters
@@ -12,10 +12,7 @@ import logging
12
12
  import math
13
13
  from functools import cached_property
14
14
  from typing import Any
15
- from typing import Dict
16
- from typing import List
17
15
  from typing import Optional
18
- from typing import Tuple
19
16
 
20
17
  import numpy as np
21
18
  import xarray as xr
@@ -49,10 +46,10 @@ class Variable:
49
46
  *,
50
47
  ds: xr.Dataset,
51
48
  variable: xr.DataArray,
52
- coordinates: List[Any],
49
+ coordinates: list[Any],
53
50
  grid: Any,
54
51
  time: Any,
55
- metadata: Dict[str, Any],
52
+ metadata: dict[str, Any],
56
53
  ):
57
54
  """Initialize the Variable object.
58
55
 
@@ -111,7 +108,7 @@ class Variable:
111
108
  return self.length
112
109
 
113
110
  @property
114
- def grid_mapping(self) -> Optional[Dict[str, Any]]:
111
+ def grid_mapping(self) -> dict[str, Any] | None:
115
112
  """Return the grid mapping of the variable."""
116
113
  grid_mapping = self.variable.attrs.get("grid_mapping", None)
117
114
  if grid_mapping is None:
@@ -146,7 +143,7 @@ class Variable:
146
143
  str
147
144
  A string representation of the variable.
148
145
  """
149
- return "Variable[name=%s,coordinates=%s,metadata=%s]" % (
146
+ return "Variable[name={},coordinates={},metadata={}]".format(
150
147
  self.variable.name,
151
148
  self.coordinates,
152
149
  self._metadata,
@@ -177,7 +174,7 @@ class Variable:
177
174
  kwargs = {k: v for k, v in zip(self.names, coords)}
178
175
  return XArrayField(self, self.variable.isel(kwargs))
179
176
 
180
- def sel(self, missing: Dict[str, Any], **kwargs: Any) -> Optional["Variable"]:
177
+ def sel(self, missing: dict[str, Any], **kwargs: Any) -> Optional["Variable"]:
181
178
  """Select a subset of the variable based on the given coordinates.
182
179
 
183
180
  Parameters
@@ -221,23 +218,26 @@ class Variable:
221
218
  LOG.warning(f"Could not find {k}={v} in {c}")
222
219
  return None
223
220
 
224
- coordinates = [x.reduced(i) if c is x else x for x in self.coordinates]
221
+ if c.scalar and i == 0:
222
+ variable = self
223
+ else:
224
+ coordinates = [x.reduced(i) if c is x else x for x in self.coordinates]
225
225
 
226
- metadata = self._metadata.copy()
227
- metadata.update({k: v})
226
+ metadata = self._metadata.copy()
227
+ metadata.update({k: v})
228
228
 
229
- variable = Variable(
230
- ds=self.ds,
231
- variable=self.variable.isel({k: i}),
232
- coordinates=coordinates,
233
- grid=self.grid,
234
- time=self.time,
235
- metadata=metadata,
236
- )
229
+ variable = Variable(
230
+ ds=self.ds,
231
+ variable=self.variable.isel({k: i}),
232
+ coordinates=coordinates,
233
+ grid=self.grid,
234
+ time=self.time,
235
+ metadata=metadata,
236
+ )
237
237
 
238
238
  return variable.sel(missing, **kwargs)
239
239
 
240
- def match(self, **kwargs: Any) -> Tuple[bool, Optional[Dict[str, Any]]]:
240
+ def match(self, **kwargs: Any) -> tuple[bool, dict[str, Any] | None]:
241
241
  """Match the variable based on the given metadata.
242
242
 
243
243
  Parameters
@@ -289,7 +289,7 @@ class FilteredVariable:
289
289
  self.kwargs = kwargs
290
290
 
291
291
  @cached_property
292
- def fields(self) -> List["XArrayField"]:
292
+ def fields(self) -> list["XArrayField"]:
293
293
  """Filter the fields of a variable based on metadata."""
294
294
  return [
295
295
  field
@@ -8,34 +8,37 @@
8
8
  # nor does it submit to any jurisdiction.
9
9
 
10
10
  from typing import Any
11
- from typing import List
12
11
 
13
12
  import earthkit.data as ekd
14
13
 
15
- from .legacy import legacy_source
14
+ from . import source_registry
15
+ from .legacy import LegacySource
16
16
  from .xarray import load_many
17
17
 
18
18
 
19
- @legacy_source(__file__)
20
- def execute(context: Any, dates: List[str], url: str, *args: Any, **kwargs: Any) -> ekd.FieldList:
21
- """Execute the data loading process.
22
-
23
- Parameters
24
- ----------
25
- context : Any
26
- The context in which the execution occurs.
27
- dates : List[str]
28
- List of dates for which data is to be loaded.
29
- url : str
30
- The URL from which data is to be loaded.
31
- *args : tuple
32
- Additional positional arguments.
33
- **kwargs : dict
34
- Additional keyword arguments.
35
-
36
- Returns
37
- -------
38
- ekd.FieldList
39
- The loaded data.
40
- """
41
- return load_many("🇿", context, dates, url, *args, **kwargs)
19
+ @source_registry.register("xarray_zarr")
20
+ class XarrayZarrSource(LegacySource):
21
+
22
+ @staticmethod
23
+ def _execute(context: Any, dates: list[str], url: str, *args: Any, **kwargs: Any) -> ekd.FieldList:
24
+ """Execute the data loading process.
25
+
26
+ Parameters
27
+ ----------
28
+ context : Any
29
+ The context in which the execution occurs.
30
+ dates : List[str]
31
+ List of dates for which data is to be loaded.
32
+ url : str
33
+ The URL from which data is to be loaded.
34
+ *args : tuple
35
+ Additional positional arguments.
36
+ **kwargs : dict
37
+ Additional keyword arguments.
38
+
39
+ Returns
40
+ -------
41
+ ekd.FieldList
42
+ The loaded data.
43
+ """
44
+ return load_many("🇿", context, dates, url, *args, **kwargs)
@@ -9,61 +9,63 @@
9
9
 
10
10
 
11
11
  from typing import Any
12
- from typing import Dict
13
- from typing import List
14
12
 
15
13
  import earthkit.data as ekd
16
14
  from earthkit.data.core.fieldlist import MultiFieldList
17
15
  from earthkit.data.sources.url import download_and_cache
18
16
 
19
- from .legacy import legacy_source
17
+ from . import source_registry
18
+ from .legacy import LegacySource
20
19
  from .patterns import iterate_patterns
21
20
  from .xarray import load_one
22
21
 
23
22
 
24
- @legacy_source(__file__)
25
- def execute(context: Any, dates: Any, record_id: str, file_key: str, *args: Any, **kwargs: Any) -> ekd.FieldList:
26
- """Executes the download and processing of files from Zenodo.
23
+ @source_registry.register("zenodo")
24
+ class ZenodoSource(LegacySource):
27
25
 
28
- Parameters
29
- ----------
30
- context : Any
31
- The context in which the function is executed.
32
- dates : Any
33
- The dates for which the data is required.
34
- record_id : str
35
- The Zenodo record ID.
36
- file_key : str
37
- The key to identify the file.
38
- *args : Any
39
- Additional arguments.
40
- **kwargs : Any
41
- Additional keyword arguments.
26
+ @staticmethod
27
+ def _execute(context: Any, dates: Any, record_id: str, file_key: str, *args: Any, **kwargs: Any) -> ekd.FieldList:
28
+ """Executes the download and processing of files from Zenodo.
42
29
 
43
- Returns
44
- -------
45
- MultiFieldList
46
- A list of fields loaded from the downloaded files.
47
- """
48
- import requests
30
+ Parameters
31
+ ----------
32
+ context : Any
33
+ The context in which the function is executed.
34
+ dates : Any
35
+ The dates for which the data is required.
36
+ record_id : str
37
+ The Zenodo record ID.
38
+ file_key : str
39
+ The key to identify the file.
40
+ *args : Any
41
+ Additional arguments.
42
+ **kwargs : Any
43
+ Additional keyword arguments.
49
44
 
50
- result: List[Any] = []
45
+ Returns
46
+ -------
47
+ MultiFieldList
48
+ A list of fields loaded from the downloaded files.
49
+ """
50
+ import requests
51
51
 
52
- URLPATTERN = "https://zenodo.org/api/records/{record_id}"
53
- url = URLPATTERN.format(record_id=record_id)
54
- r = requests.get(url)
55
- r.raise_for_status()
56
- record: Dict[str, Any] = r.json()
52
+ result: list[Any] = []
57
53
 
58
- urls: Dict[str, str] = {}
59
- for file in record["files"]:
60
- urls[file["key"]] = file["links"]["self"]
54
+ URLPATTERN = "https://zenodo.org/api/records/{record_id}"
55
+ url = URLPATTERN.format(record_id=record_id)
56
+ r = requests.get(url)
57
+ r.raise_for_status()
58
+ record: dict[str, Any] = r.json()
61
59
 
62
- for url, dates in iterate_patterns(file_key, dates, **kwargs):
63
- if url not in urls:
64
- continue
60
+ urls: dict[str, str] = {}
61
+ for file in record["files"]:
62
+ urls[file["key"]] = file["links"]["self"]
65
63
 
66
- path = download_and_cache(urls[url])
67
- result.append(load_one("?", context, dates, path, options={}, flavour=None, **kwargs))
64
+ for url, dates in iterate_patterns(file_key, dates, **kwargs):
65
+ if url not in urls:
66
+ continue
68
67
 
69
- return MultiFieldList(result)
68
+ path = download_and_cache(urls[url])
69
+ result.append(load_one("?", context, dates, path, options={}, flavour=None, **kwargs))
70
+
71
+ return MultiFieldList(result)