anemoi-utils 0.4.12__py3-none-any.whl → 0.4.14__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.

Potentially problematic release.


This version of anemoi-utils might be problematic. Click here for more details.

Files changed (37) hide show
  1. anemoi/utils/__init__.py +1 -0
  2. anemoi/utils/__main__.py +12 -2
  3. anemoi/utils/_version.py +9 -4
  4. anemoi/utils/caching.py +138 -13
  5. anemoi/utils/checkpoints.py +81 -13
  6. anemoi/utils/cli.py +83 -7
  7. anemoi/utils/commands/__init__.py +4 -0
  8. anemoi/utils/commands/config.py +19 -2
  9. anemoi/utils/commands/requests.py +18 -2
  10. anemoi/utils/compatibility.py +6 -5
  11. anemoi/utils/config.py +254 -23
  12. anemoi/utils/dates.py +204 -50
  13. anemoi/utils/devtools.py +68 -7
  14. anemoi/utils/grib.py +30 -9
  15. anemoi/utils/grids.py +85 -8
  16. anemoi/utils/hindcasts.py +25 -8
  17. anemoi/utils/humanize.py +357 -52
  18. anemoi/utils/logs.py +31 -3
  19. anemoi/utils/mars/__init__.py +46 -12
  20. anemoi/utils/mars/requests.py +15 -1
  21. anemoi/utils/provenance.py +189 -32
  22. anemoi/utils/registry.py +234 -44
  23. anemoi/utils/remote/__init__.py +386 -38
  24. anemoi/utils/remote/s3.py +252 -29
  25. anemoi/utils/remote/ssh.py +140 -8
  26. anemoi/utils/s3.py +77 -4
  27. anemoi/utils/sanitise.py +52 -7
  28. anemoi/utils/testing.py +182 -0
  29. anemoi/utils/text.py +218 -54
  30. anemoi/utils/timer.py +91 -15
  31. {anemoi_utils-0.4.12.dist-info → anemoi_utils-0.4.14.dist-info}/METADATA +8 -4
  32. anemoi_utils-0.4.14.dist-info/RECORD +38 -0
  33. {anemoi_utils-0.4.12.dist-info → anemoi_utils-0.4.14.dist-info}/WHEEL +1 -1
  34. anemoi_utils-0.4.12.dist-info/RECORD +0 -37
  35. {anemoi_utils-0.4.12.dist-info → anemoi_utils-0.4.14.dist-info}/entry_points.txt +0 -0
  36. {anemoi_utils-0.4.12.dist-info → anemoi_utils-0.4.14.dist-info/licenses}/LICENSE +0 -0
  37. {anemoi_utils-0.4.12.dist-info → anemoi_utils-0.4.14.dist-info}/top_level.txt +0 -0
anemoi/utils/text.py CHANGED
@@ -8,16 +8,21 @@
8
8
  # nor does it submit to any jurisdiction.
9
9
 
10
10
 
11
- """Text utilities"""
11
+ """Text utilities."""
12
12
 
13
13
  import re
14
14
  from collections import defaultdict
15
+ from typing import Any
16
+ from typing import List
17
+ from typing import Optional
18
+ from typing import Tuple
19
+ from typing import Union
15
20
 
16
21
  # https://en.wikipedia.org/wiki/Box-drawing_character
17
22
 
18
23
 
19
24
  def dotted_line(width=84) -> str:
20
- """Return a dotted line using '┈'
25
+ """Return a dotted line using '┈'.
21
26
 
22
27
  >>> dotted_line(40)
23
28
  ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈
@@ -40,12 +45,35 @@ def dotted_line(width=84) -> str:
40
45
  _ansi_escape = re.compile(r"\x1b\[([0-9;]*[mGKH])")
41
46
 
42
47
 
43
- def _has_ansi_escape(s):
48
+ def _has_ansi_escape(s: str) -> bool:
49
+ """Check if a string contains ANSI escape codes.
50
+
51
+ Parameters
52
+ ----------
53
+ s : str
54
+ The string to check.
55
+
56
+ Returns
57
+ -------
58
+ bool
59
+ True if the string contains ANSI escape codes, False otherwise.
60
+ """
44
61
  return _ansi_escape.search(s) is not None
45
62
 
46
63
 
47
- def _split_tokens(s):
48
- """Split a string into a list of visual characters with their lenghts."""
64
+ def _split_tokens(s: str) -> List[Tuple[str, int]]:
65
+ """Split a string into a list of visual characters with their lengths.
66
+
67
+ Parameters
68
+ ----------
69
+ s : str
70
+ The string to split.
71
+
72
+ Returns
73
+ -------
74
+ list of tuple
75
+ A list of tuples where each tuple contains a visual character and its length.
76
+ """
49
77
  from wcwidth import wcswidth
50
78
 
51
79
  initial = s
@@ -76,8 +104,19 @@ def _split_tokens(s):
76
104
  return out
77
105
 
78
106
 
79
- def visual_len(s):
80
- """Compute the length of a string as it appears on the terminal."""
107
+ def visual_len(s: Union[str, List[Tuple[str, int]]]) -> int:
108
+ """Compute the length of a string as it appears on the terminal.
109
+
110
+ Parameters
111
+ ----------
112
+ s : str or list of tuple
113
+ The string or list of visual characters with their lengths.
114
+
115
+ Returns
116
+ -------
117
+ int
118
+ The visual length of the string.
119
+ """
81
120
  if isinstance(s, str):
82
121
  s = _split_tokens(s)
83
122
  assert isinstance(s, (tuple, list)), (type(s), s)
@@ -92,8 +131,8 @@ def visual_len(s):
92
131
  return n
93
132
 
94
133
 
95
- def boxed(text, min_width=80, max_width=80) -> str:
96
- """Put a box around a text
134
+ def boxed(text: str, min_width: int = 80, max_width: int = 80) -> str:
135
+ """Put a box around a text.
97
136
 
98
137
  >>> boxed("Hello,\\nWorld!", max_width=40)
99
138
  ┌──────────────────────────────────────────┐
@@ -114,12 +153,11 @@ def boxed(text, min_width=80, max_width=80) -> str:
114
153
  -------
115
154
  str
116
155
  A boxed version of the input text
117
-
118
156
  """
119
157
 
120
158
  lines = []
121
159
  for line in text.split("\n"):
122
- line = line.strip()
160
+ line = line.rstrip()
123
161
  line = _split_tokens(line)
124
162
  lines.append(line)
125
163
 
@@ -158,68 +196,157 @@ def boxed(text, min_width=80, max_width=80) -> str:
158
196
  return "\n".join(box)
159
197
 
160
198
 
161
- def bold(text):
199
+ def bold(text: str) -> str:
200
+ """Make the text bold.
201
+
202
+ Parameters
203
+ ----------
204
+ text : str
205
+ The text to make bold.
206
+
207
+ Returns
208
+ -------
209
+ str
210
+ The bold text.
211
+ """
162
212
  from termcolor import colored
163
213
 
164
214
  return colored(text, attrs=["bold"])
165
215
 
166
216
 
167
- def red(text):
217
+ def red(text: str) -> str:
218
+ """Make the text red.
219
+
220
+ Parameters
221
+ ----------
222
+ text : str
223
+ The text to make red.
224
+
225
+ Returns
226
+ -------
227
+ str
228
+ The red text.
229
+ """
168
230
  from termcolor import colored
169
231
 
170
232
  return colored(text, "red")
171
233
 
172
234
 
173
- def green(text):
235
+ def green(text: str) -> str:
236
+ """Make the text green.
237
+
238
+ Parameters
239
+ ----------
240
+ text : str
241
+ The text to make green.
242
+
243
+ Returns
244
+ -------
245
+ str
246
+ The green text.
247
+ """
174
248
  from termcolor import colored
175
249
 
176
250
  return colored(text, "green")
177
251
 
178
252
 
179
- def blue(text):
253
+ def blue(text: str) -> str:
254
+ """Make the text blue.
255
+
256
+ Parameters
257
+ ----------
258
+ text : str
259
+ The text to make blue.
260
+
261
+ Returns
262
+ -------
263
+ str
264
+ The blue text.
265
+ """
180
266
  from termcolor import colored
181
267
 
182
268
  return colored(text, "blue")
183
269
 
184
270
 
185
271
  class Tree:
186
- """Tree data structure."""
272
+ """Tree data structure.
187
273
 
188
- def __init__(self, actor, parent=None):
274
+ Parameters
275
+ ----------
276
+ actor : Any
277
+ The actor associated with the tree node.
278
+ parent : Tree, optional
279
+ The parent tree node, by default None.
280
+ """
281
+
282
+ def __init__(self, actor: Any, parent: Optional["Tree"] = None):
189
283
  self._actor = actor
190
284
  self._kids = []
191
285
  self._parent = parent
192
286
 
193
- def adopt(self, kid):
287
+ def adopt(self, kid: "Tree") -> None:
288
+ """Adopt a child tree node.
289
+
290
+ Parameters
291
+ ----------
292
+ kid : Tree
293
+ The child tree node to adopt.
294
+ """
194
295
  kid._parent._kids.remove(kid)
195
296
  self._kids.append(kid)
196
297
  kid._parent = self
197
298
  # assert False
198
299
 
199
- def forget(self):
300
+ def forget(self) -> None:
301
+ """Forget the current tree node."""
200
302
  self._parent._kids.remove(self)
201
303
  self._parent = None
202
304
 
203
305
  @property
204
- def is_leaf(self):
306
+ def is_leaf(self) -> bool:
307
+ """Bool: True if the tree node is a leaf, False otherwise."""
205
308
  return len(self._kids) == 0
206
309
 
207
310
  @property
208
- def key(self):
311
+ def key(self) -> Tuple:
312
+ """Tuple: The key of the tree node."""
209
313
  return tuple(sorted(self._actor.as_dict().items()))
210
314
 
211
315
  @property
212
- def _text(self):
316
+ def _text(self) -> str:
317
+ """Str: The text representation of the tree node."""
213
318
  return self._actor.summary
214
319
 
215
320
  @property
216
- def summary(self):
321
+ def summary(self) -> str:
322
+ """Str: The summary of the tree node."""
217
323
  return self._actor.summary
218
324
 
219
- def as_dict(self):
325
+ def as_dict(self) -> dict:
326
+ """Convert the tree node to a dictionary.
327
+
328
+ Returns
329
+ -------
330
+ dict
331
+ The dictionary representation of the tree node.
332
+ """
220
333
  return self._actor.as_dict()
221
334
 
222
- def node(self, actor, insert=False):
335
+ def node(self, actor: Any, insert: bool = False) -> "Tree":
336
+ """Create a new tree node.
337
+
338
+ Parameters
339
+ ----------
340
+ actor : Any
341
+ The actor associated with the new tree node.
342
+ insert : bool, optional
343
+ Whether to insert the new tree node at the beginning, by default False.
344
+
345
+ Returns
346
+ -------
347
+ Tree
348
+ The new tree node.
349
+ """
223
350
  node = Tree(actor, self)
224
351
  if insert:
225
352
  self._kids.insert(0, node)
@@ -227,7 +354,8 @@ class Tree:
227
354
  self._kids.append(node)
228
355
  return node
229
356
 
230
- def print(self):
357
+ def print(self) -> None:
358
+ """Print the tree."""
231
359
  padding = []
232
360
 
233
361
  while self._factorise():
@@ -235,14 +363,28 @@ class Tree:
235
363
 
236
364
  self._print(padding)
237
365
 
238
- def _leaves(self, result):
366
+ def _leaves(self, result: List["Tree"]) -> None:
367
+ """Collect all leaf nodes.
368
+
369
+ Parameters
370
+ ----------
371
+ result : list of Tree
372
+ The list to collect the leaf nodes.
373
+ """
239
374
  if self.is_leaf:
240
375
  result.append(self)
241
376
  else:
242
377
  for kid in self._kids:
243
378
  kid._leaves(result)
244
379
 
245
- def _factorise(self):
380
+ def _factorise(self) -> bool:
381
+ """Factorise the tree.
382
+
383
+ Returns
384
+ -------
385
+ bool
386
+ True if the tree was factorised, False otherwise.
387
+ """
246
388
  if len(self._kids) == 0:
247
389
  return False
248
390
 
@@ -290,7 +432,14 @@ class Tree:
290
432
 
291
433
  return result
292
434
 
293
- def _print(self, padding):
435
+ def _print(self, padding: List[str]) -> None:
436
+ """Print the tree with padding.
437
+
438
+ Parameters
439
+ ----------
440
+ padding : list of str
441
+ The padding for each level of the tree.
442
+ """
294
443
  for i, p in enumerate(padding[:-1]):
295
444
  if p == " └":
296
445
  padding[i] = " "
@@ -308,7 +457,19 @@ class Tree:
308
457
 
309
458
  padding.pop()
310
459
 
311
- def to_json(self, depth=0):
460
+ def to_json(self, depth: int = 0) -> dict:
461
+ """Convert the tree to a JSON serializable dictionary.
462
+
463
+ Parameters
464
+ ----------
465
+ depth : int, optional
466
+ The depth of the tree, by default 0.
467
+
468
+ Returns
469
+ -------
470
+ dict
471
+ The JSON serializable dictionary representation of the tree.
472
+ """
312
473
  while self._factorise():
313
474
  pass
314
475
 
@@ -319,20 +480,20 @@ class Tree:
319
480
  }
320
481
 
321
482
 
322
- def table(rows, header, align, margin=0) -> str:
323
- """Format a table
483
+ def table(rows: List[List[Any]], header: List[str], align: List[str], margin: int = 0) -> str:
484
+ """Format a table.
324
485
 
325
- >>> table([['Aa', 12, 5],
326
- ['B', 120, 1],
327
- ['C', 9, 123]],
328
- ['C1', 'C2', 'C3'],
329
- ['<', '>', '>'])
330
- C1 │ C2 │ C3
331
- ───┼─────┼────
332
- Aa │ 12 │ 5
333
- B │ 120 │ 1
334
- C │ 9 │ 123
335
- ───┴─────┴────
486
+ >>> table([['Aa', 12, 5],
487
+ ['B', 120, 1],
488
+ ['C', 9, 123]],
489
+ ['C1', 'C2', 'C3'],
490
+ ['<', '>', '>'])
491
+ C1 │ C2 │ C3
492
+ ───┼─────┼────
493
+ Aa │ 12 │ 5
494
+ B │ 120 │ 1
495
+ C │ 9 │ 123
496
+ ───┴─────┴────
336
497
 
337
498
  Parameters
338
499
  ----------
@@ -397,26 +558,29 @@ def table(rows, header, align, margin=0) -> str:
397
558
  return "\n".join(result)
398
559
 
399
560
 
400
- def progress(done, todo, width=80) -> str:
401
- """_summary_
402
-
403
- >>> print(progress(10, 100,width=50))
404
- █████▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
561
+ def progress(done: int, todo: int, width: int = 80) -> str:
562
+ """Generates a progress bar string.
405
563
 
406
564
  Parameters
407
565
  ----------
408
- done : function
409
- _description_
410
- todo : _type_
411
- _description_
566
+
567
+ done : int
568
+ The number of tasks completed.
569
+ todo : int
570
+ The total number of tasks.
412
571
  width : int, optional
413
- _description_, by default 80
572
+ The width of the progress bar, by default 80.
414
573
 
415
574
  Returns
416
575
  -------
417
576
  str
418
- _description_
577
+ A string representing the progress bar.
419
578
 
579
+ Example
580
+ -------
581
+
582
+ >>> print(progress(10, 100,width=50))
583
+ █████▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
420
584
  """
421
585
  done = min(int(done / todo * width + 0.5), width)
422
586
  return green("█" * done) + red("█" * (width - done))
anemoi/utils/timer.py CHANGED
@@ -13,6 +13,7 @@
13
13
  import logging
14
14
  import time
15
15
  from collections import defaultdict
16
+ from typing import Any
16
17
 
17
18
  from .humanize import seconds_to_human
18
19
 
@@ -20,21 +21,53 @@ LOGGER = logging.getLogger(__name__)
20
21
 
21
22
 
22
23
  class Timer:
23
- """Context manager to measure elapsed time."""
24
-
25
- def __init__(self, title, logger=LOGGER):
24
+ """Context manager to measure elapsed time.
25
+
26
+ Parameters
27
+ ----------
28
+ title : str
29
+ The title of the timer.
30
+ logger : logging.Logger, optional
31
+ The logger to use for logging the elapsed time, by default LOGGER.
32
+ """
33
+
34
+ def __init__(self, title: str, logger: logging.Logger = LOGGER):
35
+ """Initialize the Timer.
36
+
37
+ Parameters
38
+ ----------
39
+ title : str
40
+ The title of the timer.
41
+ logger : logging.Logger, optional
42
+ The logger to use for logging the elapsed time, by default LOGGER.
43
+ """
26
44
  self.title = title
27
45
  self.start = time.time()
28
46
  self.logger = logger
29
47
 
30
- def __enter__(self):
48
+ def __enter__(self) -> "Timer":
49
+ """Enter the runtime context related to this object.
50
+
51
+ Returns
52
+ -------
53
+ Timer
54
+ The Timer object itself.
55
+ """
31
56
  return self
32
57
 
33
58
  @property
34
- def elapsed(self):
59
+ def elapsed(self) -> float:
60
+ """Float: The elapsed time in seconds."""
35
61
  return time.time() - self.start
36
62
 
37
- def __exit__(self, *args):
63
+ def __exit__(self, *args: Any) -> None:
64
+ """Exit the runtime context related to this object.
65
+
66
+ Parameters
67
+ ----------
68
+ *args : Any
69
+ Exception information.
70
+ """
38
71
  self.logger.info("%s: %s.", self.title, seconds_to_human(self.elapsed))
39
72
 
40
73
 
@@ -42,33 +75,76 @@ class _Timer:
42
75
  """Internal timer class."""
43
76
 
44
77
  def __init__(self):
78
+ """Initialize the _Timer."""
45
79
  self.elapsed = 0.0
46
80
 
47
- def __enter__(self):
81
+ def __enter__(self) -> "_Timer":
82
+ """Enter the runtime context related to this object.
83
+
84
+ Returns
85
+ -------
86
+ _Timer
87
+ The _Timer object itself.
88
+ """
48
89
  self.start()
49
90
  return self
50
91
 
51
- def __exit__(self, *args):
92
+ def __exit__(self, *args: Any) -> None:
93
+ """Exit the runtime context related to this object.
94
+
95
+ Parameters
96
+ ----------
97
+ *args : Any
98
+ Exception information.
99
+ """
52
100
  self.stop()
53
101
 
54
- def start(self):
102
+ def start(self) -> None:
103
+ """Start the timer."""
55
104
  self._start = time.time()
56
105
 
57
- def stop(self):
106
+ def stop(self) -> None:
107
+ """Stop the timer and accumulate the elapsed time."""
58
108
  self.elapsed += time.time() - self._start
59
109
 
60
110
 
61
111
  class Timers:
62
- """A collection of timers."""
63
-
64
- def __init__(self, logger=LOGGER):
112
+ """A collection of timers.
113
+
114
+ Parameters
115
+ ----------
116
+ logger : logging.Logger, optional
117
+ The logger to use for logging the elapsed time, by default LOGGER.
118
+ """
119
+
120
+ def __init__(self, logger: logging.Logger = LOGGER):
121
+ """Initialize the Timers collection.
122
+
123
+ Parameters
124
+ ----------
125
+ logger : logging.Logger, optional
126
+ The logger to use for logging the elapsed time, by default LOGGER.
127
+ """
65
128
  self.logger = logger
66
129
  self.timers = defaultdict(_Timer)
67
130
 
68
- def __getitem__(self, name):
131
+ def __getitem__(self, name: str) -> _Timer:
132
+ """Get a timer by name.
133
+
134
+ Parameters
135
+ ----------
136
+ name : str
137
+ The name of the timer.
138
+
139
+ Returns
140
+ -------
141
+ _Timer
142
+ The timer with the given name.
143
+ """
69
144
  return self.timers[name]
70
145
 
71
- def report(self):
146
+ def report(self) -> None:
147
+ """Log the elapsed time for all timers."""
72
148
  length = max(len(name) for name in self.timers)
73
149
  for name, timer in sorted(self.timers.items()):
74
150
  self.logger.info("%s: %s.", f"{name:<{length}}", seconds_to_human(timer.elapsed))
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: anemoi-utils
3
- Version: 0.4.12
3
+ Version: 0.4.14
4
4
  Summary: A package to hold various functions to support training of ML models on ECMWF data.
5
5
  Author-email: "European Centre for Medium-Range Weather Forecasts (ECMWF)" <software.support@ecmwf.int>
6
6
  License: Apache License
@@ -226,13 +226,14 @@ Requires-Python: >=3.9
226
226
  License-File: LICENSE
227
227
  Requires-Dist: aniso8601
228
228
  Requires-Dist: importlib-metadata; python_version < "3.10"
229
+ Requires-Dist: multiurl
229
230
  Requires-Dist: numpy
230
231
  Requires-Dist: python-dateutil
231
232
  Requires-Dist: pyyaml
232
233
  Requires-Dist: tomli; python_version < "3.11"
233
234
  Requires-Dist: tqdm
234
235
  Provides-Extra: all
235
- Requires-Dist: anemoi-utils[grib,provenance,text]; extra == "all"
236
+ Requires-Dist: anemoi-utils[grib,provenance,s3,text]; extra == "all"
236
237
  Provides-Extra: dev
237
238
  Requires-Dist: anemoi-utils[all,docs,tests]; extra == "dev"
238
239
  Provides-Extra: docs
@@ -240,7 +241,7 @@ Requires-Dist: nbsphinx; extra == "docs"
240
241
  Requires-Dist: pandoc; extra == "docs"
241
242
  Requires-Dist: requests; extra == "docs"
242
243
  Requires-Dist: sphinx; extra == "docs"
243
- Requires-Dist: sphinx-argparse<0.5; extra == "docs"
244
+ Requires-Dist: sphinx-argparse; extra == "docs"
244
245
  Requires-Dist: sphinx-rtd-theme; extra == "docs"
245
246
  Requires-Dist: termcolor; extra == "docs"
246
247
  Provides-Extra: grib
@@ -248,8 +249,11 @@ Requires-Dist: requests; extra == "grib"
248
249
  Provides-Extra: provenance
249
250
  Requires-Dist: gitpython; extra == "provenance"
250
251
  Requires-Dist: nvsmi; extra == "provenance"
252
+ Provides-Extra: s3
253
+ Requires-Dist: boto3<1.36; extra == "s3"
251
254
  Provides-Extra: tests
252
255
  Requires-Dist: pytest; extra == "tests"
253
256
  Provides-Extra: text
254
257
  Requires-Dist: termcolor; extra == "text"
255
258
  Requires-Dist: wcwidth; extra == "text"
259
+ Dynamic: license-file
@@ -0,0 +1,38 @@
1
+ anemoi/utils/__init__.py,sha256=uVhpF-VjIl_4mMywOVtgTutgsdIsqz-xdkwxeMhzuag,730
2
+ anemoi/utils/__main__.py,sha256=6LlE4MYrPvqqrykxXh7XMi50UZteUY59NeM8P9Zs2dU,910
3
+ anemoi/utils/_version.py,sha256=ggKLQlA5gegh3l4LAMWuwHPmL8zgZ4b0pYtFx7quk78,513
4
+ anemoi/utils/caching.py,sha256=rXbeAmpBcMbbfN4EVblaHWKicsrtx1otER84FEBtz98,6183
5
+ anemoi/utils/checkpoints.py,sha256=N4WpAZXa4etrpSEKhHqUUtG2-x9w3FJMHcLO-dDAXPY,9600
6
+ anemoi/utils/cli.py,sha256=IyZfnSw0u0yYnrjOrzvm2RuuKvDk4cVb8pf8BkaChgA,6209
7
+ anemoi/utils/compatibility.py,sha256=wRBRMmxQP88rNcWiP5gqXliwYQbBv1iCAsDjcCRi5UY,2234
8
+ anemoi/utils/config.py,sha256=4K4_ePeuUPyIMsE7-r77OC87ntFGdVLskB0ow9XNzXI,16353
9
+ anemoi/utils/dates.py,sha256=CnY6JOdpk0T-waPEowMRTkcMzxcN0GcjPVtLkwH_byw,17196
10
+ anemoi/utils/devtools.py,sha256=vivYRQ1l2aMAkggqhpMJcJedfCrRfpy5XMQjZIsMJS0,3596
11
+ anemoi/utils/grib.py,sha256=201WcxjjAl92Y2HX2kZ2S8Qr5dN-oG7nV-vQLaybzP4,3610
12
+ anemoi/utils/grids.py,sha256=edTrMK8hpE9ZBzSfwcRftgk0jljNAK3i8CraadILQoM,4427
13
+ anemoi/utils/hindcasts.py,sha256=iYVIxSNFL2HJcc_k1abCFLkpJFGHT8WKRIR4wcAwA3s,2144
14
+ anemoi/utils/humanize.py,sha256=hCrHr5ppREuJR-tBqRqynqe58BHR6Ga_gCQqgEmmrfU,25301
15
+ anemoi/utils/logs.py,sha256=naTgrmPwWHD4eekFttXftS4gtcAGYHpCqG4iwYprNDA,1804
16
+ anemoi/utils/provenance.py,sha256=tIIgweS0EJyaYzgKwuv3iWny-Gz7N5e5CNWH5MeSYWU,14615
17
+ anemoi/utils/registry.py,sha256=zTnVSCecmrI6SkZhF4ipV7WKiZYHEBJ_5ZwqhRGM4T0,9287
18
+ anemoi/utils/s3.py,sha256=xMT48kbcelcjjqsaU567WI3oZ5eqo88Rlgyx5ECszAU,4074
19
+ anemoi/utils/sanitise.py,sha256=ZYGdSX6qihQANr3pHZjbKnoapnzP1KcrWdW1Ul1mOGk,3668
20
+ anemoi/utils/sanitize.py,sha256=43ZKDcfVpeXSsJ9TFEc9aZnD6oe2cUh151XnDspM98M,462
21
+ anemoi/utils/testing.py,sha256=N1y4dfZLE9zqOhIR3o-933fdAdd9BxDvjcJx7SwFC9A,4803
22
+ anemoi/utils/text.py,sha256=HkzIvi24obDceFLpJEwBJ9PmPrJUkQN2TrElJ-A87gU,14441
23
+ anemoi/utils/timer.py,sha256=_leKMYza2faM7JKlGE7LCNy13rbdPnwaCF7PSrI_NmI,3895
24
+ anemoi/utils/commands/__init__.py,sha256=5u_6EwdqYczIAgJfCwRSyQAYFEqh2ZuHHT57g9g7sdI,808
25
+ anemoi/utils/commands/config.py,sha256=nYvYbjcum9uumRa3gzPfmQCjNZKURXMA2XOUwz9b7ls,1369
26
+ anemoi/utils/commands/requests.py,sha256=AEbssF1OlpbmSwrV5Lj6amCCn0w_-nbajBWTwYV15vA,2059
27
+ anemoi/utils/mars/__init__.py,sha256=b-Lc3L1TAQd9ODs0Z1YSJzgZCO1K_M3DSgx_yd2qXvM,2724
28
+ anemoi/utils/mars/mars.yaml,sha256=R0dujp75lLA4wCWhPeOQnzJ45WZAYLT8gpx509cBFlc,66
29
+ anemoi/utils/mars/requests.py,sha256=VFMHBVAAl0_2lOcMBa1lvaKHctN0lDJsI6_U4BucGew,1142
30
+ anemoi/utils/remote/__init__.py,sha256=-uaYFi4yRYFRf46ubQbJo86GCn6HE5VQrcaoyrmyW28,20704
31
+ anemoi/utils/remote/s3.py,sha256=spQ8l0rwQjLZh9dZu5cOsYIvNwKihQfCJ6YsFYegeqI,17339
32
+ anemoi/utils/remote/ssh.py,sha256=xNtsawh8okytCKRehkRCVExbHZj-CRUQNormEHglfuw,8088
33
+ anemoi_utils-0.4.14.dist-info/licenses/LICENSE,sha256=8HznKF1Vi2IvfLsKNE5A2iVyiri3pRjRPvPC9kxs6qk,11354
34
+ anemoi_utils-0.4.14.dist-info/METADATA,sha256=VLuh3lxCAuACOrJRnE_Dm0zX_pVfIHoppmRrP505pkY,15360
35
+ anemoi_utils-0.4.14.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
36
+ anemoi_utils-0.4.14.dist-info/entry_points.txt,sha256=LENOkn88xzFQo-V59AKoA_F_cfYQTJYtrNTtf37YgHY,60
37
+ anemoi_utils-0.4.14.dist-info/top_level.txt,sha256=DYn8VPs-fNwr7fNH9XIBqeXIwiYYd2E2k5-dUFFqUz0,7
38
+ anemoi_utils-0.4.14.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (77.0.3)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5