anemoi-utils 0.4.25__py3-none-any.whl → 0.4.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.

Potentially problematic release.


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

anemoi/utils/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.4.25'
21
- __version_tuple__ = version_tuple = (0, 4, 25)
20
+ __version__ = version = '0.4.27'
21
+ __version_tuple__ = version_tuple = (0, 4, 27)
@@ -308,7 +308,6 @@ def remove_metadata(path: str, *, name: str = DEFAULT_NAME) -> None:
308
308
  name : str, optional
309
309
  The name of the metadata file in the zip archive
310
310
  """
311
- LOG.info("Removing metadata '%s' from %s", name, path)
312
311
 
313
312
  def callback(full):
314
313
  os.remove(full)
@@ -0,0 +1,391 @@
1
+ # (C) Copyright 2024 Anemoi contributors.
2
+ #
3
+ # This software is licensed under the terms of the Apache Licence Version 2.0
4
+ # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
5
+ #
6
+ # In applying this licence, ECMWF does not waive the privileges and immunities
7
+ # granted to it by virtue of its status as an intergovernmental organisation
8
+ # nor does it submit to any jurisdiction.
9
+
10
+
11
+ import json
12
+ import logging
13
+ import os
14
+ import shutil
15
+ import subprocess
16
+ from argparse import ArgumentParser
17
+ from argparse import Namespace
18
+ from tempfile import TemporaryDirectory
19
+ from typing import Any
20
+ from typing import Dict
21
+
22
+ import yaml
23
+
24
+ from . import Command
25
+
26
+ LOG = logging.getLogger(__name__)
27
+
28
+ EDITOR_OPTIONS = {"code": ["--wait"]}
29
+
30
+
31
+ class Metadata(Command):
32
+ """Edit, remove, dump or load metadata from a checkpoint file."""
33
+
34
+ def add_arguments(self, command_parser: ArgumentParser) -> None:
35
+ """Add command line arguments to the parser.
36
+
37
+ Parameters
38
+ ----------
39
+ command_parser : ArgumentParser
40
+ The argument parser to which the arguments will be added.
41
+ """
42
+ from anemoi.utils.checkpoints import DEFAULT_NAME
43
+
44
+ command_parser.add_argument("path", help="Path to the checkpoint.")
45
+
46
+ group = command_parser.add_mutually_exclusive_group(required=True)
47
+
48
+ group.add_argument(
49
+ "--dump",
50
+ action="store_true",
51
+ help=(
52
+ "Extract the metadata from the checkpoint and print it to the standard output"
53
+ " or the file specified by ``--output``, in JSON or YAML format."
54
+ ),
55
+ )
56
+ group.add_argument(
57
+ "--load",
58
+ action="store_true",
59
+ help=(
60
+ "Set the metadata in the checkpoint from the content"
61
+ " of a file specified by the ``--input`` argument."
62
+ ),
63
+ )
64
+
65
+ group.add_argument(
66
+ "--edit",
67
+ action="store_true",
68
+ help=(
69
+ "Edit the metadata in place, using the specified editor."
70
+ " See the ``--editor`` argument for more information."
71
+ ),
72
+ )
73
+
74
+ group.add_argument(
75
+ "--view",
76
+ action="store_true",
77
+ help=(
78
+ "View the metadata in place, using the specified pager."
79
+ " See the ``--pager`` argument for more information."
80
+ ),
81
+ )
82
+
83
+ group.add_argument(
84
+ "--remove",
85
+ action="store_true",
86
+ help="Remove the metadata from the checkpoint.",
87
+ )
88
+
89
+ group.add_argument(
90
+ "--supporting-arrays",
91
+ action="store_true",
92
+ help="Print the supporting arrays.",
93
+ )
94
+
95
+ group.add_argument(
96
+ "--get",
97
+ help="Navigate the metadata via dot-separated path.",
98
+ )
99
+
100
+ group.add_argument(
101
+ "--pytest",
102
+ action="store_true",
103
+ help=("Extract the metadata from the checkpoint so it can be added to the test suite."),
104
+ )
105
+
106
+ command_parser.add_argument(
107
+ "--name",
108
+ default=DEFAULT_NAME,
109
+ help="Name of metadata record to be used with the actions above.",
110
+ )
111
+
112
+ command_parser.add_argument(
113
+ "--input",
114
+ help="The output file name to be used by the ``--load`` option.",
115
+ )
116
+
117
+ command_parser.add_argument(
118
+ "--output",
119
+ help="The output file name to be used by the ``--dump`` option.",
120
+ )
121
+
122
+ command_parser.add_argument(
123
+ "--inplace",
124
+ action="store_true",
125
+ help="If set, update the source file in place instead of writing to a separate target.",
126
+ )
127
+
128
+ command_parser.add_argument(
129
+ "--editor",
130
+ help="Editor to use for the ``--edit`` option. Default to ``$EDITOR`` if defined, else ``vi``.",
131
+ default=os.environ.get("EDITOR", "vi"),
132
+ )
133
+
134
+ command_parser.add_argument(
135
+ "--pager",
136
+ help="Editor to use for the ``--view`` option. Default to ``$PAGER`` if defined, else ``less``.",
137
+ default=os.environ.get("PAGER", "less"),
138
+ )
139
+
140
+ command_parser.add_argument(
141
+ "--json",
142
+ action="store_true",
143
+ help="Use the JSON format with ``--dump``, ``--view`` and ``--edit``.",
144
+ )
145
+
146
+ command_parser.add_argument(
147
+ "--yaml",
148
+ action="store_true",
149
+ help="Use the YAML format with ``--dump``, ``--view`` and ``--edit``.",
150
+ )
151
+
152
+ def run(self, args: Namespace) -> None:
153
+ """Execute the command based on the provided arguments.
154
+
155
+ Parameters
156
+ ----------
157
+ args : Namespace
158
+ The arguments passed to the command.
159
+ """
160
+ if args.edit:
161
+ return self.edit(args)
162
+
163
+ if args.view:
164
+ return self.view(args)
165
+
166
+ if args.get:
167
+ return self.get(args)
168
+
169
+ if args.remove:
170
+ return self.remove(args)
171
+
172
+ if args.dump or args.pytest:
173
+ return self.dump(args)
174
+
175
+ if args.load:
176
+ return self.load(args)
177
+
178
+ if args.supporting_arrays:
179
+ return self.supporting_arrays(args)
180
+
181
+ def edit(self, args: Namespace) -> None:
182
+ """Edit the metadata in place using the specified editor.
183
+
184
+ Parameters
185
+ ----------
186
+ args : Namespace
187
+ The arguments passed to the command.
188
+ """
189
+ return self._edit(args, view=False, cmd=args.editor)
190
+
191
+ def view(self, args: Namespace) -> None:
192
+ """View the metadata in place using the specified pager.
193
+
194
+ Parameters
195
+ ----------
196
+ args : Namespace
197
+ The arguments passed to the command.
198
+ """
199
+ return self._edit(args, view=True, cmd=args.pager)
200
+
201
+ def _edit(self, args: Namespace, view: bool, cmd: str) -> None:
202
+ """Internal method to edit or view the metadata.
203
+
204
+ Parameters
205
+ ----------
206
+ args : Namespace
207
+ The arguments passed to the command.
208
+ view : bool
209
+ If True, view the metadata; otherwise, edit it.
210
+ cmd : str
211
+ The command to use for editing or viewing.
212
+ """
213
+ from anemoi.utils.checkpoints import load_metadata
214
+ from anemoi.utils.checkpoints import replace_metadata
215
+
216
+ kwargs: Dict[str, Any] = {}
217
+
218
+ if args.json:
219
+ ext = "json"
220
+ dump = json.dump
221
+ load = json.load
222
+ if args.test:
223
+ kwargs = {"sort_keys": True}
224
+ else:
225
+ kwargs = {"indent": 4, "sort_keys": True}
226
+ else:
227
+ ext = "yaml"
228
+ dump = yaml.dump
229
+ load = yaml.safe_load
230
+ kwargs = {"default_flow_style": False}
231
+
232
+ with TemporaryDirectory() as temp_dir:
233
+
234
+ path = os.path.join(temp_dir, f"checkpoint.{ext}")
235
+ metadata = load_metadata(args.path)
236
+
237
+ with open(path, "w") as f:
238
+ dump(metadata, f, **kwargs)
239
+
240
+ subprocess.check_call([cmd, *EDITOR_OPTIONS.get(cmd, []), path])
241
+
242
+ if not view:
243
+ with open(path) as f:
244
+ edited = load(f)
245
+
246
+ if edited != metadata:
247
+ replace_metadata(args.path, edited)
248
+ else:
249
+ LOG.info("No changes made.")
250
+
251
+ def remove(self, args: Namespace) -> None:
252
+ """Remove the metadata from the checkpoint.
253
+
254
+ Parameters
255
+ ----------
256
+ args : Namespace
257
+ The arguments passed to the command.
258
+ """
259
+ from anemoi.utils.checkpoints import remove_metadata
260
+
261
+ if args.inplace and args.output:
262
+ raise ValueError("Only choose one of --inplace and --output")
263
+
264
+ LOG.info("Removing metadata from %s", args.path)
265
+
266
+ if args.inplace:
267
+ output = args.path
268
+ else:
269
+ if not args.output:
270
+ raise ValueError("Argument --output is required unless --inplace is set")
271
+
272
+ shutil.copy2(args.path, args.output)
273
+ output = args.output
274
+
275
+ LOG.info("Writing checkpoint at %s", output)
276
+ remove_metadata(output)
277
+
278
+ def dump(self, args: Namespace) -> None:
279
+ """Dump the metadata from the checkpoint to a file or standard output.
280
+
281
+ Parameters
282
+ ----------
283
+ args : Namespace
284
+ The arguments passed to the command.
285
+ """
286
+ from anemoi.utils.checkpoints import load_metadata
287
+
288
+ if args.output:
289
+ file = open(args.output, "w")
290
+ else:
291
+ file = None
292
+
293
+ metadata = load_metadata(args.path)
294
+ if args.pytest:
295
+ from anemoi.inference.testing.mock_checkpoint import minimum_mock_checkpoint
296
+
297
+ # We remove all unessential metadata for testing purposes
298
+ metadata = minimum_mock_checkpoint(metadata)
299
+
300
+ if args.yaml:
301
+ print(yaml.dump(metadata, indent=2, sort_keys=True), file=file)
302
+ return
303
+
304
+ if args.json or True:
305
+ if args.pytest:
306
+ print(json.dumps(metadata, sort_keys=True), file=file)
307
+ else:
308
+ print(json.dumps(metadata, indent=4, sort_keys=True), file=file)
309
+ return
310
+
311
+ def get(self, args: Namespace) -> None:
312
+ """Navigate and print the metadata via a dot-separated path.
313
+
314
+ Parameters
315
+ ----------
316
+ args : Namespace
317
+ The arguments passed to the command.
318
+ """
319
+ from pprint import pprint
320
+
321
+ from anemoi.utils.checkpoints import load_metadata
322
+
323
+ metadata = load_metadata(args.path, name=args.name)
324
+
325
+ if args.get == ".":
326
+ print("Metadata from root: ", list(metadata.keys()))
327
+ return
328
+
329
+ for key in args.get.split("."):
330
+ if key == "":
331
+ keys = list(metadata.keys())
332
+ print(f"Metadata keys from {args.get[:-1]}: ", keys)
333
+ return
334
+ else:
335
+ metadata = metadata[key]
336
+
337
+ print(f"Metadata values for {args.get}: ", end="\n" if isinstance(metadata, (dict, list)) else "")
338
+ if isinstance(metadata, dict):
339
+ pprint(metadata, indent=2, compact=True)
340
+ else:
341
+ print(metadata)
342
+
343
+ def load(self, args: Namespace) -> None:
344
+ """Load metadata into the checkpoint from a specified file.
345
+
346
+ Parameters
347
+ ----------
348
+ args : Namespace
349
+ The arguments passed to the command.
350
+ """
351
+ from anemoi.utils.checkpoints import has_metadata
352
+ from anemoi.utils.checkpoints import replace_metadata
353
+ from anemoi.utils.checkpoints import save_metadata
354
+
355
+ if args.input is None:
356
+ raise ValueError("Please specify a value for --input")
357
+
358
+ _, ext = os.path.splitext(args.input)
359
+ if ext == ".json" or args.json:
360
+ with open(args.input) as f:
361
+ metadata = json.load(f)
362
+
363
+ elif ext in (".yaml", ".yml") or args.yaml:
364
+ with open(args.input) as f:
365
+ metadata = yaml.safe_load(f)
366
+
367
+ else:
368
+ raise ValueError(f"Unknown file extension {ext}. Please specify --json or --yaml")
369
+
370
+ if has_metadata(args.path, name=args.name):
371
+ replace_metadata(args.path, metadata)
372
+ else:
373
+ save_metadata(args.path, metadata, name=args.name)
374
+
375
+ def supporting_arrays(self, args: Namespace) -> None:
376
+ """Print the supporting arrays from the metadata.
377
+
378
+ Parameters
379
+ ----------
380
+ args : Namespace
381
+ The arguments passed to the command.
382
+ """
383
+ from anemoi.utils.checkpoints import load_metadata
384
+
385
+ _, supporting_arrays = load_metadata(args.path, supporting_arrays=True)
386
+
387
+ for name, array in supporting_arrays.items():
388
+ print(f"{name}: shape={array.shape} dtype={array.dtype}")
389
+
390
+
391
+ command = Metadata
@@ -17,7 +17,7 @@ from . import Command
17
17
 
18
18
 
19
19
  class Transfer(Command):
20
- """Handle configuration related commands."""
20
+ """Transfer files or folders from the source to the target location."""
21
21
 
22
22
  def add_arguments(self, command_parser: ArgumentParser) -> None:
23
23
  """Add arguments to the command parser.
anemoi/utils/remote/s3.py CHANGED
@@ -47,8 +47,8 @@ SECRETS = ["aws_access_key_id", "aws_secret_access_key"]
47
47
  thread_local = threading.local()
48
48
 
49
49
 
50
- def s3_client(bucket: str, *, region: str = None, service: str = "s3") -> Any:
51
- """Get an S3 client for the specified bucket and region.
50
+ def _s3_config(bucket: str, *, region: str = None) -> Any:
51
+ """Get an S3 client config for the specified bucket and region.
52
52
 
53
53
  Parameters
54
54
  ----------
@@ -56,31 +56,15 @@ def s3_client(bucket: str, *, region: str = None, service: str = "s3") -> Any:
56
56
  The name of the S3 bucket.
57
57
  region : str, optional
58
58
  The AWS region of the S3 bucket.
59
- service : str, optional
60
- The AWS service to use, default is "s3".
61
59
 
62
60
  Returns
63
61
  -------
64
62
  Any
65
63
  The S3 client.
66
64
  """
67
- import boto3
68
65
  from botocore import UNSIGNED
69
- from botocore.client import Config
70
66
 
71
- if not hasattr(thread_local, "s3_clients"):
72
- thread_local.s3_clients = {}
73
-
74
- key = f"{bucket}-{region}-{service}"
75
-
76
- if key in thread_local.s3_clients:
77
- return thread_local.s3_clients[key]
78
-
79
- boto3_config = dict(
80
- max_pool_connections=25,
81
- request_checksum_calculation="when_required",
82
- response_checksum_validation="when_required",
83
- )
67
+ boto3_config = {}
84
68
 
85
69
  if region:
86
70
  # This is using AWS
@@ -129,6 +113,85 @@ def s3_client(bucket: str, *, region: str = None, service: str = "s3") -> Any:
129
113
  boto3_config.update(options["config"])
130
114
  del options["config"]
131
115
 
116
+ def _(options):
117
+
118
+ def __(k, v):
119
+ if k in SECRETS:
120
+ return "***"
121
+ return v
122
+
123
+ if isinstance(options, dict):
124
+ return {k: __(k, v) for k, v in options.items()}
125
+
126
+ if isinstance(options, list):
127
+ return [_(o) for o in options]
128
+
129
+ return options
130
+
131
+ LOG.debug(f"Using S3 options: {_(options)}")
132
+
133
+ return boto3_config, options
134
+
135
+
136
+ def s3_options(bucket: str, *, region: str = None, service: str = "s3") -> dict:
137
+ """Get the S3 configuration for the specified bucket and region.
138
+
139
+ Parameters
140
+ ----------
141
+ bucket : str
142
+ The name of the S3 bucket.
143
+ region : str, optional
144
+ The AWS region of the S3 bucket.
145
+ service : str, optional
146
+ The AWS service to use, default is "s3".
147
+
148
+ Returns
149
+ -------
150
+ dict
151
+ The S3 configuration.
152
+ """
153
+ _, options = _s3_config(bucket, region=region)
154
+ return options
155
+
156
+
157
+ def s3_client(bucket: str, *, region: str = None, service: str = "s3") -> Any:
158
+ """Get an S3 client for the specified bucket and region.
159
+
160
+ Parameters
161
+ ----------
162
+ bucket : str
163
+ The name of the S3 bucket.
164
+ region : str, optional
165
+ The AWS region of the S3 bucket.
166
+ service : str, optional
167
+ The AWS service to use, default is "s3".
168
+
169
+ Returns
170
+ -------
171
+ Any
172
+ The S3 client.
173
+ """
174
+ import boto3
175
+ from botocore.client import Config
176
+
177
+ if not hasattr(thread_local, "s3_clients"):
178
+ thread_local.s3_clients = {}
179
+
180
+ key = f"{bucket}-{region}-{service}"
181
+
182
+ if key in thread_local.s3_clients:
183
+ return thread_local.s3_clients[key]
184
+
185
+ boto3_config, options = _s3_config(bucket, region=region)
186
+
187
+ boto3_config.update(
188
+ dict(
189
+ max_pool_connections=25,
190
+ request_checksum_calculation="when_required",
191
+ response_checksum_validation="when_required",
192
+ )
193
+ )
194
+
132
195
  options["config"] = Config(**boto3_config)
133
196
 
134
197
  def _(options):
anemoi/utils/testing.py CHANGED
@@ -7,14 +7,12 @@
7
7
  # granted to it by virtue of its status as an intergovernmental organisation
8
8
  # nor does it submit to any jurisdiction.
9
9
 
10
- import atexit
11
10
  import logging
12
11
  import os
13
12
  import shutil
14
- import tempfile
15
- import threading
16
13
  import warnings
17
14
  from functools import lru_cache
15
+ from pathlib import Path
18
16
 
19
17
  import pytest
20
18
  from multiurl import download
@@ -25,30 +23,6 @@ LOG = logging.getLogger(__name__)
25
23
 
26
24
  TEST_DATA_URL = "https://object-store.os-api.cci1.ecmwf.int/ml-tests/test-data/samples/"
27
25
 
28
- lock = threading.RLock()
29
- TEMPORARY_DIRECTORY = None
30
-
31
-
32
- def _temporary_directory() -> str:
33
- """Return a temporary directory in which to download test data.
34
-
35
- Returns
36
- -------
37
- str
38
- The path to the temporary directory.
39
- """
40
- global TEMPORARY_DIRECTORY
41
- with lock:
42
- if TEMPORARY_DIRECTORY is not None:
43
- return TEMPORARY_DIRECTORY
44
-
45
- TEMPORARY_DIRECTORY = tempfile.mkdtemp()
46
-
47
- # Register a cleanup function to remove the directory at exit
48
- atexit.register(shutil.rmtree, TEMPORARY_DIRECTORY)
49
-
50
- return TEMPORARY_DIRECTORY
51
-
52
26
 
53
27
  def _check_path(path: str) -> None:
54
28
  """Check if the given path is normalized, not absolute, and does not start with a dot.
@@ -68,21 +42,17 @@ def _check_path(path: str) -> None:
68
42
  assert not path.startswith("."), f"Path '{path}' should not start with '.'"
69
43
 
70
44
 
71
- def _temporary_directory_for_test_data(path: str) -> str:
72
- """Get the temporary directory for a test dataset.
45
+ @pytest.fixture(scope="session")
46
+ def temporary_directory_for_test_data(tmp_path_factory) -> callable:
47
+ base_dir = tmp_path_factory.mktemp("test_data_base")
73
48
 
74
- Parameters
75
- ----------
76
- path : str
77
- The relative path to the test data in the object store.
49
+ def _temporary_directory_for_test_data(path: str = "", archive: bool = False) -> str:
50
+ if path == "":
51
+ return str(base_dir)
52
+ _check_path(path)
53
+ return str(base_dir.joinpath(*Path(path).parts)) + (".extracted" if archive else "")
78
54
 
79
- Returns
80
- -------
81
- str
82
- The path to the temporary directory.
83
- """
84
- _check_path(path)
85
- return os.path.normpath(os.path.join(_temporary_directory(), path))
55
+ return _temporary_directory_for_test_data
86
56
 
87
57
 
88
58
  def url_for_test_data(path: str) -> str:
@@ -103,27 +73,29 @@ def url_for_test_data(path: str) -> str:
103
73
  return f"{TEST_DATA_URL}{path}"
104
74
 
105
75
 
106
- def get_test_data(path: str, gzipped=False) -> str:
107
- """Download the test data to a temporary directory and return the local path.
76
+ @pytest.fixture()
77
+ def get_test_data(temporary_directory_for_test_data):
78
+ def _get_test_data(path: str, gzipped=False) -> callable:
79
+ """Download the test data to a temporary directory and return the local path.
108
80
 
109
- Parameters
110
- ----------
111
- path : str
112
- The relative path to the test data.
113
- gzipped : bool, optional
114
- Flag indicating if the remote file is gzipped, by default False. The local file will be gunzipped.
81
+ Parameters
82
+ ----------
83
+ path : str
84
+ The relative path to the test data.
85
+ gzipped : bool, optional
86
+ Flag indicating if the remote file is gzipped, by default False. The local file will be gunzipped.
115
87
 
116
- Returns
117
- -------
118
- str
119
- The local path to the downloaded test data.
120
- """
88
+ Returns
89
+ -------
90
+ str
91
+ The local path to the downloaded test data.
92
+ """
93
+
94
+ if _offline():
95
+ raise RuntimeError("Offline mode: cannot download test data, add @pytest.mark.skipif(not offline(),...)")
121
96
 
122
- if _offline():
123
- raise RuntimeError("Offline mode: cannot download test data, add @pytest.mark.skipif(not offline(),...)")
97
+ target = temporary_directory_for_test_data(path)
124
98
 
125
- target = _temporary_directory_for_test_data(path)
126
- with lock:
127
99
  if os.path.exists(target):
128
100
  return target
129
101
 
@@ -149,27 +121,29 @@ def get_test_data(path: str, gzipped=False) -> str:
149
121
 
150
122
  return target
151
123
 
124
+ return _get_test_data
152
125
 
153
- def get_test_archive(path: str, extension=".extracted") -> str:
154
- """Download an archive file (.zip, .tar, .tar.gz, .tar.bz2, .tar.xz) to a temporary directory
155
- unpack it, and return the local path to the directory containing the extracted files.
156
126
 
157
- Parameters
158
- ----------
159
- path : str
160
- The relative path to the test data.
161
- extension : str, optional
162
- The extension to add to the extracted directory, by default '.extracted'
127
+ @pytest.fixture()
128
+ def get_test_archive(temporary_directory_for_test_data, get_test_data) -> callable:
129
+ def _get_test_archive(path: str) -> str:
130
+ """Download an archive file (.zip, .tar, .tar.gz, .tar.bz2, .tar.xz) to a temporary directory
131
+ unpack it, and return the local path to the directory containing the extracted files.
163
132
 
164
- Returns
165
- -------
166
- str
167
- The local path to the downloaded test data.
168
- """
133
+ Parameters
134
+ ----------
135
+ path : str
136
+ The relative path to the test data.
137
+ extension : str, optional
138
+ The extension to add to the extracted directory, by default '.extracted'
169
139
 
170
- with lock:
140
+ Returns
141
+ -------
142
+ str
143
+ The local path to the downloaded test data.
144
+ """
171
145
 
172
- target = _temporary_directory_for_test_data(path) + extension
146
+ target = Path(temporary_directory_for_test_data(path, archive=True))
173
147
 
174
148
  if os.path.exists(target):
175
149
  return target
@@ -183,6 +157,8 @@ def get_test_archive(path: str, extension=".extracted") -> str:
183
157
 
184
158
  return target
185
159
 
160
+ return _get_test_archive
161
+
186
162
 
187
163
  def packages_installed(*names: str) -> bool:
188
164
  """Check if all the given packages are installed.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anemoi-utils
3
- Version: 0.4.25
3
+ Version: 0.4.27
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
@@ -1,8 +1,8 @@
1
1
  anemoi/utils/__init__.py,sha256=uVhpF-VjIl_4mMywOVtgTutgsdIsqz-xdkwxeMhzuag,730
2
2
  anemoi/utils/__main__.py,sha256=6LlE4MYrPvqqrykxXh7XMi50UZteUY59NeM8P9Zs2dU,910
3
- anemoi/utils/_version.py,sha256=3hQuMRH6SNq4Gy0jS57VxD61lFAwagLT8wP_tfZa6fA,513
3
+ anemoi/utils/_version.py,sha256=l2RpdEWLXHjIi56KlS-u-66k8wBi9yvFH_NPLD7QkmQ,513
4
4
  anemoi/utils/caching.py,sha256=rXbeAmpBcMbbfN4EVblaHWKicsrtx1otER84FEBtz98,6183
5
- anemoi/utils/checkpoints.py,sha256=N4WpAZXa4etrpSEKhHqUUtG2-x9w3FJMHcLO-dDAXPY,9600
5
+ anemoi/utils/checkpoints.py,sha256=PydBqA-wI8_05zU-3yT-ZP53GnvuqB7vPXl-w6-9XX8,9541
6
6
  anemoi/utils/cli.py,sha256=Cd3ESQkNWecbGnJjkR_SKHdFPETJWFrHqg5ovtANDKs,6522
7
7
  anemoi/utils/compatibility.py,sha256=wRBRMmxQP88rNcWiP5gqXliwYQbBv1iCAsDjcCRi5UY,2234
8
8
  anemoi/utils/config.py,sha256=EEfcSxW2CD6fFOzDtqz_uYlMKuYq4X5QinJW_8GBYj4,17325
@@ -19,24 +19,25 @@ anemoi/utils/rules.py,sha256=VspUoPmw7tijrs6l_wl4vDjr_zVQsFjx9ITiBSvxgc8,6972
19
19
  anemoi/utils/s3.py,sha256=xMT48kbcelcjjqsaU567WI3oZ5eqo88Rlgyx5ECszAU,4074
20
20
  anemoi/utils/sanitise.py,sha256=ZYGdSX6qihQANr3pHZjbKnoapnzP1KcrWdW1Ul1mOGk,3668
21
21
  anemoi/utils/sanitize.py,sha256=43ZKDcfVpeXSsJ9TFEc9aZnD6oe2cUh151XnDspM98M,462
22
- anemoi/utils/testing.py,sha256=1qisyMA71HMT2mQUpLOsh01F9KR6vIOLid5WVyniaiw,10605
22
+ anemoi/utils/testing.py,sha256=DeTAkmg-RCMPXBBRUy_Gti5UJriUPRKT6ruE3JL2GVc,10372
23
23
  anemoi/utils/text.py,sha256=HkzIvi24obDceFLpJEwBJ9PmPrJUkQN2TrElJ-A87gU,14441
24
24
  anemoi/utils/timer.py,sha256=_leKMYza2faM7JKlGE7LCNy13rbdPnwaCF7PSrI_NmI,3895
25
25
  anemoi/utils/commands/__init__.py,sha256=5u_6EwdqYczIAgJfCwRSyQAYFEqh2ZuHHT57g9g7sdI,808
26
26
  anemoi/utils/commands/config.py,sha256=nYvYbjcum9uumRa3gzPfmQCjNZKURXMA2XOUwz9b7ls,1369
27
+ anemoi/utils/commands/metadata.py,sha256=_j1ohUy9tty9KhD0N2zqc7vaOJ3pJFRZUGMGRE0z7Mw,11973
27
28
  anemoi/utils/commands/requests.py,sha256=AEbssF1OlpbmSwrV5Lj6amCCn0w_-nbajBWTwYV15vA,2059
28
- anemoi/utils/commands/transfer.py,sha256=05GT5BAY9xt6vx86--xfJ8bkPCUkIRz0d265hDcJLCE,2600
29
+ anemoi/utils/commands/transfer.py,sha256=29o1RQ46dCJ6kTD1sbxp8XFgB-Qx3RceMccypBMRxeQ,2627
29
30
  anemoi/utils/mars/__init__.py,sha256=b-Lc3L1TAQd9ODs0Z1YSJzgZCO1K_M3DSgx_yd2qXvM,2724
30
31
  anemoi/utils/mars/mars.yaml,sha256=R0dujp75lLA4wCWhPeOQnzJ45WZAYLT8gpx509cBFlc,66
31
32
  anemoi/utils/mars/requests.py,sha256=VFMHBVAAl0_2lOcMBa1lvaKHctN0lDJsI6_U4BucGew,1142
32
33
  anemoi/utils/remote/__init__.py,sha256=7nHu-LRspYW2Fx9GNLjsxpytAUIvhIbOjb0Xmxb-33s,20988
33
- anemoi/utils/remote/s3.py,sha256=VgxNuJpJBnfxwkkV9TyrSkf7fjIuHqy1YG9vRdj9QRc,19978
34
+ anemoi/utils/remote/s3.py,sha256=DxO_TjmetX_r3ZvGaHjpz40oqvcQYP3Vd_A4ojMGlSA,21379
34
35
  anemoi/utils/remote/ssh.py,sha256=xNtsawh8okytCKRehkRCVExbHZj-CRUQNormEHglfuw,8088
35
36
  anemoi/utils/schemas/__init__.py,sha256=nkinKlsPLPXEjfTYQT1mpKC4cvs-14w_zBkDRxakwxw,698
36
37
  anemoi/utils/schemas/errors.py,sha256=lgOXzVTYzAE0qWQf3OZ42vCWixv8lilSqLLhzARBmvI,1831
37
- anemoi_utils-0.4.25.dist-info/licenses/LICENSE,sha256=8HznKF1Vi2IvfLsKNE5A2iVyiri3pRjRPvPC9kxs6qk,11354
38
- anemoi_utils-0.4.25.dist-info/METADATA,sha256=PILyf6_K2AZs161wasDnRvfhMdQjm4Cub1YzXkdKFmM,15486
39
- anemoi_utils-0.4.25.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
40
- anemoi_utils-0.4.25.dist-info/entry_points.txt,sha256=LENOkn88xzFQo-V59AKoA_F_cfYQTJYtrNTtf37YgHY,60
41
- anemoi_utils-0.4.25.dist-info/top_level.txt,sha256=DYn8VPs-fNwr7fNH9XIBqeXIwiYYd2E2k5-dUFFqUz0,7
42
- anemoi_utils-0.4.25.dist-info/RECORD,,
38
+ anemoi_utils-0.4.27.dist-info/licenses/LICENSE,sha256=8HznKF1Vi2IvfLsKNE5A2iVyiri3pRjRPvPC9kxs6qk,11354
39
+ anemoi_utils-0.4.27.dist-info/METADATA,sha256=zqysYlsP5gsTcGN93z7KW5rSIehOpVjg85arxug6MZE,15486
40
+ anemoi_utils-0.4.27.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
41
+ anemoi_utils-0.4.27.dist-info/entry_points.txt,sha256=LENOkn88xzFQo-V59AKoA_F_cfYQTJYtrNTtf37YgHY,60
42
+ anemoi_utils-0.4.27.dist-info/top_level.txt,sha256=DYn8VPs-fNwr7fNH9XIBqeXIwiYYd2E2k5-dUFFqUz0,7
43
+ anemoi_utils-0.4.27.dist-info/RECORD,,