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
@@ -15,10 +15,6 @@ from collections import defaultdict
15
15
  from functools import cached_property
16
16
  from typing import Any
17
17
  from typing import DefaultDict
18
- from typing import Dict
19
- from typing import List
20
- from typing import Optional
21
- from typing import Tuple
22
18
 
23
19
  import numpy as np
24
20
  from anemoi.utils.dates import as_timedelta
@@ -26,14 +22,12 @@ from anemoi.utils.humanize import seconds_to_human
26
22
  from anemoi.utils.humanize import shorten_list
27
23
  from earthkit.data.core.order import build_remapping
28
24
 
29
- from .action import ActionContext
30
- from .trace import trace
31
- from .trace import trace_datasource
25
+ from . import Result
32
26
 
33
27
  LOG = logging.getLogger(__name__)
34
28
 
35
29
 
36
- def _fields_metatata(variables: Tuple[str, ...], cube: Any) -> Dict[str, Any]:
30
+ def _fields_metatata(variables: tuple[str, ...], cube: Any) -> dict[str, Any]:
37
31
  """Retrieve metadata for the given variables and cube.
38
32
 
39
33
  Parameters
@@ -50,7 +44,7 @@ def _fields_metatata(variables: Tuple[str, ...], cube: Any) -> Dict[str, Any]:
50
44
  """
51
45
  assert isinstance(variables, tuple), variables
52
46
 
53
- KNOWN: Dict[str, Dict[str, bool]] = {
47
+ KNOWN: dict[str, dict[str, bool]] = {
54
48
  "cos_julian_day": dict(computed_forcing=True, constant_in_time=False),
55
49
  "cos_latitude": dict(computed_forcing=True, constant_in_time=True),
56
50
  "cos_local_time": dict(computed_forcing=True, constant_in_time=False),
@@ -65,9 +59,9 @@ def _fields_metatata(variables: Tuple[str, ...], cube: Any) -> Dict[str, Any]:
65
59
  "sin_longitude": dict(computed_forcing=True, constant_in_time=True),
66
60
  }
67
61
 
68
- def _merge(md1: Dict[str, Any], md2: Dict[str, Any]) -> Dict[str, Any]:
62
+ def _merge(md1: dict[str, Any], md2: dict[str, Any]) -> dict[str, Any]:
69
63
  assert set(md1.keys()) == set(md2.keys()), (set(md1.keys()), set(md2.keys()))
70
- result: Dict[str, Any] = {}
64
+ result: dict[str, Any] = {}
71
65
  for k in md1.keys():
72
66
  v1 = md1[k]
73
67
  v2 = md2[k]
@@ -90,10 +84,10 @@ def _fields_metatata(variables: Tuple[str, ...], cube: Any) -> Dict[str, Any]:
90
84
 
91
85
  return result
92
86
 
93
- mars: Dict[str, Any] = {}
94
- other: DefaultDict[str, Dict[str, Any]] = defaultdict(dict)
87
+ mars: dict[str, Any] = {}
88
+ other: DefaultDict[str, dict[str, Any]] = defaultdict(dict)
95
89
  i: int = -1
96
- date: Optional[str] = None
90
+ date: str | None = None
97
91
  for c in cube.iterate_cubelets():
98
92
 
99
93
  if date is None:
@@ -137,7 +131,7 @@ def _fields_metatata(variables: Tuple[str, ...], cube: Any) -> Dict[str, Any]:
137
131
 
138
132
  if startStep != endStep:
139
133
  # https://codes.ecmwf.int/grib/format/grib2/ctables/4/10/
140
- TYPE_OF_STATISTICAL_PROCESSING: Dict[Optional[int], Optional[str]] = {
134
+ TYPE_OF_STATISTICAL_PROCESSING: dict[int | None, str | None] = {
141
135
  None: None,
142
136
  0: "average",
143
137
  1: "accumulation",
@@ -157,12 +151,12 @@ def _fields_metatata(variables: Tuple[str, ...], cube: Any) -> Dict[str, Any]:
157
151
 
158
152
  # https://codes.ecmwf.int/grib/format/grib1/ctable/5/
159
153
 
160
- TIME_RANGE_INDICATOR: Dict[int, str] = {
154
+ TIME_RANGE_INDICATOR: dict[int, str] = {
161
155
  4: "accumulation",
162
156
  3: "average",
163
157
  }
164
158
 
165
- STEP_TYPE_FOR_CONVERSION: Dict[str, str] = {
159
+ STEP_TYPE_FOR_CONVERSION: dict[str, str] = {
166
160
  "min": "minimum",
167
161
  "max": "maximum",
168
162
  "accum": "accumulation",
@@ -172,7 +166,7 @@ def _fields_metatata(variables: Tuple[str, ...], cube: Any) -> Dict[str, Any]:
172
166
  # A few patches
173
167
  #
174
168
 
175
- PATCHES: Dict[str, str] = {
169
+ PATCHES: dict[str, str] = {
176
170
  "10fg6": "maximum",
177
171
  "mntpr3": "minimum", # Not in param db
178
172
  "mntpr6": "minimum", # Not in param db
@@ -211,7 +205,7 @@ def _fields_metatata(variables: Tuple[str, ...], cube: Any) -> Dict[str, Any]:
211
205
  else:
212
206
  mars[variables[i]] = md
213
207
 
214
- result: Dict[str, Dict[str, Any]] = {}
208
+ result: dict[str, dict[str, Any]] = {}
215
209
  for k, v in mars.items():
216
210
  result[k] = dict(mars=v) if v else {}
217
211
  result[k].update(other[k])
@@ -222,7 +216,7 @@ def _fields_metatata(variables: Tuple[str, ...], cube: Any) -> Dict[str, Any]:
222
216
  return result
223
217
 
224
218
 
225
- def _data_request(data: Any) -> Dict[str, Any]:
219
+ def _data_request(data: Any) -> dict[str, Any]:
226
220
  """Build a data request dictionary from the given data.
227
221
 
228
222
  Parameters
@@ -235,12 +229,12 @@ def _data_request(data: Any) -> Dict[str, Any]:
235
229
  dict
236
230
  The data request dictionary.
237
231
  """
238
- date: Optional[Any] = None
232
+ date: Any | None = None
239
233
  params_levels: DefaultDict[str, set] = defaultdict(set)
240
234
  params_steps: DefaultDict[str, set] = defaultdict(set)
241
235
 
242
- area: Optional[Any] = None
243
- grid: Optional[Any] = None
236
+ area: Any | None = None
237
+ grid: Any | None = None
244
238
 
245
239
  for field in data:
246
240
  try:
@@ -270,8 +264,8 @@ def _data_request(data: Any) -> Dict[str, Any]:
270
264
  except Exception:
271
265
  LOG.error(f"Error in retrieving metadata (cannot build data request info) for {field}", exc_info=True)
272
266
 
273
- def sort(old_dic: DefaultDict[str, set]) -> Dict[str, List[Any]]:
274
- new_dic: Dict[str, List[Any]] = {}
267
+ def sort(old_dic: DefaultDict[str, set]) -> dict[str, list[Any]]:
268
+ new_dic: dict[str, list[Any]] = {}
275
269
  for k, v in old_dic.items():
276
270
  new_dic[k] = sorted(list(v))
277
271
  return new_dic
@@ -282,43 +276,25 @@ def _data_request(data: Any) -> Dict[str, Any]:
282
276
  return dict(param_level=params_levels, param_step=params_steps, area=area, grid=grid)
283
277
 
284
278
 
285
- class Result:
279
+ class FieldResult(Result):
286
280
  """Class to represent the result of an action in the dataset creation process."""
287
281
 
288
282
  empty: bool = False
289
283
  _coords_already_built: bool = False
290
284
 
291
- def __init__(self, context: ActionContext, action_path: List[str], dates: Any) -> None:
292
- """Initialize a Result instance.
285
+ def __init__(self, context: Any, datasource: Any) -> None:
293
286
 
294
- Parameters
295
- ----------
296
- context : ActionContext
297
- The context in which the result exists.
298
- action_path : list of str
299
- The action path.
300
- dates : Any
301
- The dates associated with the result.
302
- """
303
287
  from anemoi.datasets.dates.groups import GroupOfDates
304
288
 
305
- assert isinstance(dates, GroupOfDates), dates
306
-
307
- assert isinstance(context, ActionContext), type(context)
308
- assert isinstance(action_path, list), action_path
309
-
310
289
  self.context: Any = context
311
- self.group_of_dates: Any = dates
312
- self.action_path: List[str] = action_path
313
-
314
- @property
315
- @trace_datasource
316
- def datasource(self) -> Any:
317
- """Retrieve the data source for the result."""
318
- self._raise_not_implemented()
290
+ self.datasource = datasource
291
+ self.group_of_dates = context.argument
292
+ assert isinstance(
293
+ self.group_of_dates, GroupOfDates
294
+ ), f"Expected group_of_dates to be a GroupOfDates, got {type(self.group_of_dates)}: {self.group_of_dates}"
319
295
 
320
296
  @property
321
- def data_request(self) -> Dict[str, Any]:
297
+ def data_request(self) -> dict[str, Any]:
322
298
  """Returns a dictionary with the parameters needed to retrieve the data."""
323
299
  return _data_request(self.datasource)
324
300
 
@@ -330,7 +306,7 @@ class Result:
330
306
  Any
331
307
  The data cube.
332
308
  """
333
- trace("🧊", f"getting cube from {self.__class__.__name__}")
309
+
334
310
  ds: Any = self.datasource
335
311
 
336
312
  remapping: Any = self.context.remapping
@@ -340,7 +316,7 @@ class Result:
340
316
  LOG.debug("Sorting dataset %s %s", dict(order_by), remapping)
341
317
  assert order_by, order_by
342
318
 
343
- patches: Dict[str, Dict[Optional[Any], int]] = {"number": {None: 0}}
319
+ patches: dict[str, dict[Any | None, int]] = {"number": {None: 0}}
344
320
 
345
321
  try:
346
322
  cube: Any = ds.cube(
@@ -377,7 +353,7 @@ class Result:
377
353
  patches : Any
378
354
  The patches configuration.
379
355
  """
380
- METADATA: Tuple[str, ...] = (
356
+ METADATA: tuple[str, ...] = (
381
357
  "date",
382
358
  "time",
383
359
  "step",
@@ -402,7 +378,7 @@ class Result:
402
378
  # print("Executing", self.action_path)
403
379
  # print("Dates:", compress_dates(self.dates))
404
380
 
405
- names: List[str] = []
381
+ names: list[str] = []
406
382
  for a in args:
407
383
  if isinstance(a, str):
408
384
  names.append(a)
@@ -421,7 +397,7 @@ class Result:
421
397
  print(" ", n)
422
398
 
423
399
  print()
424
- user_shape: Tuple[int, ...] = tuple(len(v) for k, v in user_coords.items())
400
+ user_shape: tuple[int, ...] = tuple(len(v) for k, v in user_coords.items())
425
401
  print("Shape of the hypercube :", user_shape)
426
402
  print(
427
403
  "Number of expected fields :", math.prod(user_shape), "=", " x ".join([str(i) for i in user_shape])
@@ -523,66 +499,6 @@ class Result:
523
499
  print()
524
500
  exit(1)
525
501
 
526
- def _repr(self, *args: Any, _indent_: str = "\n", **kwargs: Any) -> str:
527
- """Return the string representation of the Result instance.
528
-
529
- Parameters
530
- ----------
531
- args : Any
532
- Additional positional arguments.
533
- _indent_ : str
534
- Indentation string.
535
- kwargs : Any
536
- Additional keyword arguments.
537
-
538
- Returns
539
- -------
540
- str
541
- The string representation.
542
- """
543
- more: str = ",".join([str(a)[:5000] for a in args])
544
- more += ",".join([f"{k}={v}"[:5000] for k, v in kwargs.items()])
545
-
546
- dates: str = " no-dates"
547
- if self.group_of_dates is not None:
548
- dates = f" {len(self.group_of_dates)} dates"
549
- dates += " ("
550
- dates += "/".join(d.strftime("%Y-%m-%dT%H:%M") for d in self.group_of_dates)
551
- if len(dates) > 100:
552
- dates = dates[:100] + "..."
553
- dates += ")"
554
-
555
- more = more[:5000]
556
- txt: str = f"{self.__class__.__name__}:{dates}{_indent_}{more}"
557
- if _indent_:
558
- txt = txt.replace("\n", "\n ")
559
- return txt
560
-
561
- def __repr__(self) -> str:
562
- """Return the string representation of the Result instance."""
563
- return self._repr()
564
-
565
- def _raise_not_implemented(self) -> None:
566
- """Raise a NotImplementedError indicating the method is not implemented."""
567
- raise NotImplementedError(f"Not implemented in {self.__class__.__name__}")
568
-
569
- def _trace_datasource(self, *args: Any, **kwargs: Any) -> str:
570
- """Trace the data source for the result.
571
-
572
- Parameters
573
- ----------
574
- args : Any
575
- Additional positional arguments.
576
- kwargs : Any
577
- Additional keyword arguments.
578
-
579
- Returns
580
- -------
581
- str
582
- The trace string.
583
- """
584
- return f"{self.__class__.__name__}({self.group_of_dates})"
585
-
586
502
  def build_coords(self) -> None:
587
503
  """Build the coordinates for the result."""
588
504
  if self._coords_already_built:
@@ -643,13 +559,13 @@ class Result:
643
559
  self._coords_already_built: bool = True
644
560
 
645
561
  @property
646
- def variables(self) -> List[str]:
562
+ def variables(self) -> list[str]:
647
563
  """Retrieve the variables for the result."""
648
564
  self.build_coords()
649
565
  return self._variables
650
566
 
651
567
  @property
652
- def variables_metadata(self) -> Dict[str, Any]:
568
+ def variables_metadata(self) -> dict[str, Any]:
653
569
  """Retrieve the metadata for the variables."""
654
570
  return _fields_metatata(self.variables, self._cube)
655
571
 
@@ -690,7 +606,7 @@ class Result:
690
606
  return self._proj_string
691
607
 
692
608
  @cached_property
693
- def shape(self) -> List[int]:
609
+ def shape(self) -> list[int]:
694
610
  """Retrieve the shape of the result."""
695
611
  return [
696
612
  len(self.group_of_dates),
@@ -700,7 +616,7 @@ class Result:
700
616
  ]
701
617
 
702
618
  @cached_property
703
- def coords(self) -> Dict[str, Any]:
619
+ def coords(self) -> dict[str, Any]:
704
620
  """Retrieve the coordinates of the result."""
705
621
  return {
706
622
  "dates": list(self.group_of_dates),
@@ -11,9 +11,9 @@
11
11
  import logging
12
12
  import textwrap
13
13
  import threading
14
+ from collections.abc import Callable
14
15
  from functools import wraps
15
16
  from typing import Any
16
- from typing import Callable
17
17
 
18
18
  LOG = logging.getLogger(__name__)
19
19
 
@@ -10,14 +10,13 @@
10
10
  import json
11
11
  import logging
12
12
  import os
13
- from typing import Union
14
13
 
15
14
  import zarr
16
15
 
17
16
  LOG = logging.getLogger(__name__)
18
17
 
19
18
 
20
- def fix_order_by(order_by: Union[dict, list]) -> list[dict]:
19
+ def fix_order_by(order_by: dict | list) -> list[dict]:
21
20
  """Fix the order_by attribute to ensure it is a list of dictionaries.
22
21
 
23
22
  Parameters
@@ -16,9 +16,8 @@ import os
16
16
  import pickle
17
17
  import shutil
18
18
  import socket
19
+ from collections.abc import Iterator
19
20
  from typing import Any
20
- from typing import Iterator
21
- from typing import Tuple
22
21
 
23
22
  import numpy as np
24
23
  from anemoi.utils.provenance import gather_provenance_info
@@ -198,7 +197,7 @@ class BufferedPersistentDict(PersistentDict):
198
197
  self.elements = []
199
198
  self.keys = []
200
199
 
201
- def items(self) -> Iterator[Tuple[Any, Any]]:
200
+ def items(self) -> Iterator[tuple[Any, Any]]:
202
201
  """Yield items stored in the BufferedPersistentDict.
203
202
 
204
203
  Yields
@@ -207,8 +206,7 @@ class BufferedPersistentDict(PersistentDict):
207
206
  An iterator over the items.
208
207
  """
209
208
  for keys, elements in self.storage.items():
210
- for key, elt in zip(keys, elements):
211
- yield key, elt
209
+ yield from zip(keys, elements)
212
210
 
213
211
  def delete(self) -> None:
214
212
  """Delete the storage directory and its contents."""
@@ -10,8 +10,6 @@
10
10
 
11
11
  import logging
12
12
  import os
13
- from typing import Dict
14
- from typing import Optional
15
13
 
16
14
  import tqdm
17
15
  from anemoi.utils.humanize import bytes_to_human
@@ -19,7 +17,7 @@ from anemoi.utils.humanize import bytes_to_human
19
17
  LOG = logging.getLogger(__name__)
20
18
 
21
19
 
22
- def compute_directory_sizes(path: str) -> Optional[Dict[str, int]]:
20
+ def compute_directory_sizes(path: str) -> dict[str, int] | None:
23
21
  """Computes the total size and number of files in a directory.
24
22
 
25
23
  Parameters