anemoi-utils 0.4.11__py3-none-any.whl → 0.4.13__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/__init__.py +1 -0
- anemoi/utils/__main__.py +12 -2
- anemoi/utils/_version.py +9 -4
- anemoi/utils/caching.py +138 -13
- anemoi/utils/checkpoints.py +81 -13
- anemoi/utils/cli.py +83 -7
- anemoi/utils/commands/__init__.py +4 -0
- anemoi/utils/commands/config.py +19 -2
- anemoi/utils/commands/requests.py +24 -4
- anemoi/utils/compatibility.py +6 -5
- anemoi/utils/config.py +254 -23
- anemoi/utils/dates.py +216 -55
- anemoi/utils/devtools.py +68 -7
- anemoi/utils/grib.py +30 -9
- anemoi/utils/grids.py +85 -8
- anemoi/utils/hindcasts.py +25 -8
- anemoi/utils/humanize.py +357 -52
- anemoi/utils/logs.py +31 -3
- anemoi/utils/mars/__init__.py +46 -12
- anemoi/utils/mars/requests.py +15 -1
- anemoi/utils/provenance.py +185 -28
- anemoi/utils/registry.py +122 -13
- anemoi/utils/remote/__init__.py +386 -38
- anemoi/utils/remote/s3.py +252 -29
- anemoi/utils/remote/ssh.py +140 -8
- anemoi/utils/s3.py +77 -4
- anemoi/utils/sanitise.py +52 -7
- anemoi/utils/text.py +218 -54
- anemoi/utils/timer.py +91 -15
- {anemoi_utils-0.4.11.dist-info → anemoi_utils-0.4.13.dist-info}/LICENSE +1 -1
- {anemoi_utils-0.4.11.dist-info → anemoi_utils-0.4.13.dist-info}/METADATA +7 -4
- anemoi_utils-0.4.13.dist-info/RECORD +37 -0
- {anemoi_utils-0.4.11.dist-info → anemoi_utils-0.4.13.dist-info}/WHEEL +1 -1
- anemoi_utils-0.4.11.dist-info/RECORD +0 -37
- {anemoi_utils-0.4.11.dist-info → anemoi_utils-0.4.13.dist-info}/entry_points.txt +0 -0
- {anemoi_utils-0.4.11.dist-info → anemoi_utils-0.4.13.dist-info}/top_level.txt +0 -0
anemoi/utils/__init__.py
CHANGED
anemoi/utils/__main__.py
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
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
|
+
from typing import Any
|
|
11
|
+
|
|
10
12
|
from anemoi.utils.cli import cli_main
|
|
11
13
|
from anemoi.utils.cli import make_parser
|
|
12
14
|
|
|
@@ -15,11 +17,19 @@ from .commands import COMMANDS
|
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
# For read-the-docs
|
|
18
|
-
def create_parser():
|
|
20
|
+
def create_parser() -> Any:
|
|
21
|
+
"""Create the argument parser for the CLI.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
Any
|
|
26
|
+
The argument parser
|
|
27
|
+
"""
|
|
19
28
|
return make_parser(__doc__, COMMANDS)
|
|
20
29
|
|
|
21
30
|
|
|
22
|
-
def main():
|
|
31
|
+
def main() -> None:
|
|
32
|
+
"""Main entry point for the CLI."""
|
|
23
33
|
cli_main(__version__, __doc__, COMMANDS)
|
|
24
34
|
|
|
25
35
|
|
anemoi/utils/_version.py
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
# file generated by
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
3
6
|
TYPE_CHECKING = False
|
|
4
7
|
if TYPE_CHECKING:
|
|
5
|
-
from typing import Tuple
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
6
11
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
12
|
else:
|
|
8
13
|
VERSION_TUPLE = object
|
|
@@ -12,5 +17,5 @@ __version__: str
|
|
|
12
17
|
__version_tuple__: VERSION_TUPLE
|
|
13
18
|
version_tuple: VERSION_TUPLE
|
|
14
19
|
|
|
15
|
-
__version__ = version = '0.4.
|
|
16
|
-
__version_tuple__ = version_tuple = (0, 4,
|
|
20
|
+
__version__ = version = '0.4.13'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 4, 13)
|
anemoi/utils/caching.py
CHANGED
|
@@ -13,6 +13,9 @@ import json
|
|
|
13
13
|
import os
|
|
14
14
|
import time
|
|
15
15
|
from threading import Lock
|
|
16
|
+
from typing import Any
|
|
17
|
+
from typing import Callable
|
|
18
|
+
from typing import Optional
|
|
16
19
|
|
|
17
20
|
import numpy as np
|
|
18
21
|
|
|
@@ -20,11 +23,30 @@ LOCK = Lock()
|
|
|
20
23
|
CACHE = {}
|
|
21
24
|
|
|
22
25
|
|
|
23
|
-
def _get_cache_path(collection):
|
|
26
|
+
def _get_cache_path(collection: str) -> str:
|
|
27
|
+
"""Get the cache path for a collection.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
collection : str
|
|
32
|
+
The name of the collection
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
str
|
|
37
|
+
The cache path
|
|
38
|
+
"""
|
|
24
39
|
return os.path.join(os.path.expanduser("~"), ".cache", "anemoi", collection)
|
|
25
40
|
|
|
26
41
|
|
|
27
|
-
def clean_cache(collection="default"):
|
|
42
|
+
def clean_cache(collection: str = "default") -> None:
|
|
43
|
+
"""Clean the cache for a collection.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
collection : str, optional
|
|
48
|
+
The name of the collection, by default "default"
|
|
49
|
+
"""
|
|
28
50
|
global CACHE
|
|
29
51
|
CACHE = {}
|
|
30
52
|
path = _get_cache_path(collection)
|
|
@@ -36,14 +58,35 @@ def clean_cache(collection="default"):
|
|
|
36
58
|
|
|
37
59
|
class Cacher:
|
|
38
60
|
"""This class implements a simple caching mechanism.
|
|
39
|
-
Private class, do not use directly
|
|
61
|
+
Private class, do not use directly.
|
|
62
|
+
"""
|
|
40
63
|
|
|
41
|
-
def __init__(self, collection, expires):
|
|
64
|
+
def __init__(self, collection: str, expires: Optional[int]):
|
|
65
|
+
"""Initialize the Cacher.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
collection : str
|
|
70
|
+
The name of the collection
|
|
71
|
+
expires : int, optional
|
|
72
|
+
The expiration time in seconds, or None for no expiration
|
|
73
|
+
"""
|
|
42
74
|
self.collection = collection
|
|
43
75
|
self.expires = expires
|
|
44
76
|
|
|
45
|
-
def __call__(self, func):
|
|
77
|
+
def __call__(self, func: Callable) -> Callable:
|
|
78
|
+
"""Wrap a function with caching.
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
func : Callable
|
|
83
|
+
The function to wrap
|
|
46
84
|
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
Callable
|
|
88
|
+
The wrapped function
|
|
89
|
+
"""
|
|
47
90
|
full = f"{func.__module__}.{func.__name__}"
|
|
48
91
|
|
|
49
92
|
def wrapped(*args, **kwargs):
|
|
@@ -55,8 +98,21 @@ class Cacher:
|
|
|
55
98
|
|
|
56
99
|
return wrapped
|
|
57
100
|
|
|
58
|
-
def cache(self, key, proc):
|
|
59
|
-
|
|
101
|
+
def cache(self, key: tuple, proc: Callable) -> Any:
|
|
102
|
+
"""Cache the result of a function.
|
|
103
|
+
|
|
104
|
+
Parameters
|
|
105
|
+
----------
|
|
106
|
+
key : tuple
|
|
107
|
+
The cache key
|
|
108
|
+
proc : Callable
|
|
109
|
+
The function to call if the result is not cached
|
|
110
|
+
|
|
111
|
+
Returns
|
|
112
|
+
-------
|
|
113
|
+
Any
|
|
114
|
+
The cached result
|
|
115
|
+
"""
|
|
60
116
|
key = json.dumps(key, sort_keys=True)
|
|
61
117
|
m = hashlib.md5()
|
|
62
118
|
m.update(key.encode("utf-8"))
|
|
@@ -88,37 +144,106 @@ class Cacher:
|
|
|
88
144
|
|
|
89
145
|
|
|
90
146
|
class JsonCacher(Cacher):
|
|
147
|
+
"""Cacher that uses JSON files."""
|
|
148
|
+
|
|
91
149
|
ext = ""
|
|
92
150
|
|
|
93
|
-
def save(self, path, data):
|
|
151
|
+
def save(self, path: str, data: dict) -> str:
|
|
152
|
+
"""Save data to a JSON file.
|
|
153
|
+
|
|
154
|
+
Parameters
|
|
155
|
+
----------
|
|
156
|
+
path : str
|
|
157
|
+
The path to the JSON file
|
|
158
|
+
data : dict
|
|
159
|
+
The data to save
|
|
160
|
+
|
|
161
|
+
Returns
|
|
162
|
+
-------
|
|
163
|
+
str
|
|
164
|
+
The temporary file path
|
|
165
|
+
"""
|
|
94
166
|
temp_path = path + ".tmp"
|
|
95
167
|
with open(temp_path, "w") as f:
|
|
96
168
|
json.dump(data, f)
|
|
97
169
|
return temp_path
|
|
98
170
|
|
|
99
|
-
def load(self, path):
|
|
171
|
+
def load(self, path: str) -> dict:
|
|
172
|
+
"""Load data from a JSON file.
|
|
173
|
+
|
|
174
|
+
Parameters
|
|
175
|
+
----------
|
|
176
|
+
path : str
|
|
177
|
+
The path to the JSON file
|
|
178
|
+
|
|
179
|
+
Returns
|
|
180
|
+
-------
|
|
181
|
+
dict
|
|
182
|
+
The loaded data
|
|
183
|
+
"""
|
|
100
184
|
with open(path, "r") as f:
|
|
101
185
|
return json.load(f)
|
|
102
186
|
|
|
103
187
|
|
|
104
188
|
class NpzCacher(Cacher):
|
|
189
|
+
"""Cacher that uses NPZ files."""
|
|
190
|
+
|
|
105
191
|
ext = ".npz"
|
|
106
192
|
|
|
107
|
-
def save(self, path, data):
|
|
193
|
+
def save(self, path: str, data: dict) -> str:
|
|
194
|
+
"""Save data to an NPZ file.
|
|
195
|
+
|
|
196
|
+
Parameters
|
|
197
|
+
----------
|
|
198
|
+
path : str
|
|
199
|
+
The path to the NPZ file
|
|
200
|
+
data : dict
|
|
201
|
+
The data to save
|
|
202
|
+
|
|
203
|
+
Returns
|
|
204
|
+
-------
|
|
205
|
+
str
|
|
206
|
+
The temporary file path
|
|
207
|
+
"""
|
|
108
208
|
temp_path = path + ".tmp.npz"
|
|
109
209
|
np.savez(temp_path, **data)
|
|
110
210
|
return temp_path
|
|
111
211
|
|
|
112
|
-
def load(self, path):
|
|
212
|
+
def load(self, path: str) -> dict:
|
|
213
|
+
"""Load data from an NPZ file.
|
|
214
|
+
|
|
215
|
+
Parameters
|
|
216
|
+
----------
|
|
217
|
+
path : str
|
|
218
|
+
The path to the NPZ file
|
|
219
|
+
|
|
220
|
+
Returns
|
|
221
|
+
-------
|
|
222
|
+
dict
|
|
223
|
+
The loaded data
|
|
224
|
+
"""
|
|
113
225
|
return np.load(path, allow_pickle=True)
|
|
114
226
|
|
|
115
227
|
|
|
116
|
-
#
|
|
117
|
-
def cached(collection="default", expires=None, encoding="json"):
|
|
228
|
+
# This function is the main entry point for the caching mechanism for the other anemoi packages
|
|
229
|
+
def cached(collection: str = "default", expires: Optional[int] = None, encoding: str = "json") -> Callable:
|
|
118
230
|
"""Decorator to cache the result of a function.
|
|
119
231
|
|
|
120
232
|
Default is to use a json file to store the cache, but you can also use npz files
|
|
121
233
|
to cache dict of numpy arrays.
|
|
122
234
|
|
|
235
|
+
Parameters
|
|
236
|
+
----------
|
|
237
|
+
collection : str, optional
|
|
238
|
+
The name of the collection, by default "default"
|
|
239
|
+
expires : int, optional
|
|
240
|
+
The expiration time in seconds, or None for no expiration, by default None
|
|
241
|
+
encoding : str, optional
|
|
242
|
+
The encoding type, either "json" or "npz", by default "json"
|
|
243
|
+
|
|
244
|
+
Returns
|
|
245
|
+
-------
|
|
246
|
+
Callable
|
|
247
|
+
The decorated function
|
|
123
248
|
"""
|
|
124
249
|
return dict(json=JsonCacher, npz=NpzCacher)[encoding](collection, expires)
|
anemoi/utils/checkpoints.py
CHANGED
|
@@ -18,6 +18,7 @@ import os
|
|
|
18
18
|
import time
|
|
19
19
|
import zipfile
|
|
20
20
|
from tempfile import TemporaryDirectory
|
|
21
|
+
from typing import Callable
|
|
21
22
|
|
|
22
23
|
import tqdm
|
|
23
24
|
|
|
@@ -28,7 +29,7 @@ DEFAULT_FOLDER = "anemoi-metadata"
|
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
def has_metadata(path: str, *, name: str = DEFAULT_NAME) -> bool:
|
|
31
|
-
"""Check if a checkpoint file has a metadata file
|
|
32
|
+
"""Check if a checkpoint file has a metadata file.
|
|
32
33
|
|
|
33
34
|
Parameters
|
|
34
35
|
----------
|
|
@@ -49,8 +50,26 @@ def has_metadata(path: str, *, name: str = DEFAULT_NAME) -> bool:
|
|
|
49
50
|
return False
|
|
50
51
|
|
|
51
52
|
|
|
52
|
-
def metadata_root(path: str, *, name: str = DEFAULT_NAME) ->
|
|
53
|
+
def metadata_root(path: str, *, name: str = DEFAULT_NAME) -> str:
|
|
54
|
+
"""Get the root directory of the metadata file.
|
|
53
55
|
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
path : str
|
|
59
|
+
The path to the checkpoint file
|
|
60
|
+
name : str, optional
|
|
61
|
+
The name of the metadata file in the zip archive
|
|
62
|
+
|
|
63
|
+
Returns
|
|
64
|
+
-------
|
|
65
|
+
str
|
|
66
|
+
The root directory of the metadata file
|
|
67
|
+
|
|
68
|
+
Raises
|
|
69
|
+
------
|
|
70
|
+
ValueError
|
|
71
|
+
If the metadata file is not found
|
|
72
|
+
"""
|
|
54
73
|
with zipfile.ZipFile(path, "r") as f:
|
|
55
74
|
for b in f.namelist():
|
|
56
75
|
if os.path.basename(b) == name:
|
|
@@ -58,15 +77,15 @@ def metadata_root(path: str, *, name: str = DEFAULT_NAME) -> bool:
|
|
|
58
77
|
raise ValueError(f"Could not find '{name}' in {path}.")
|
|
59
78
|
|
|
60
79
|
|
|
61
|
-
def load_metadata(path: str, *, supporting_arrays=False, name: str = DEFAULT_NAME) -> dict:
|
|
62
|
-
"""Load metadata from a checkpoint file
|
|
80
|
+
def load_metadata(path: str, *, supporting_arrays: bool = False, name: str = DEFAULT_NAME) -> dict:
|
|
81
|
+
"""Load metadata from a checkpoint file.
|
|
63
82
|
|
|
64
83
|
Parameters
|
|
65
84
|
----------
|
|
66
85
|
path : str
|
|
67
86
|
The path to the checkpoint file
|
|
68
87
|
|
|
69
|
-
supporting_arrays: bool, optional
|
|
88
|
+
supporting_arrays : bool, optional
|
|
70
89
|
If True, the function will return a dictionary with the supporting arrays
|
|
71
90
|
|
|
72
91
|
name : str, optional
|
|
@@ -102,7 +121,21 @@ def load_metadata(path: str, *, supporting_arrays=False, name: str = DEFAULT_NAM
|
|
|
102
121
|
raise ValueError(f"Could not find '{name}' in {path}.")
|
|
103
122
|
|
|
104
123
|
|
|
105
|
-
def load_supporting_arrays(zipf, entries) -> dict:
|
|
124
|
+
def load_supporting_arrays(zipf: zipfile.ZipFile, entries: dict) -> dict:
|
|
125
|
+
"""Load supporting arrays from a zip file.
|
|
126
|
+
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
zipf : zipfile.ZipFile
|
|
130
|
+
The zip file
|
|
131
|
+
entries : dict
|
|
132
|
+
A dictionary of entries with paths, shapes, and dtypes
|
|
133
|
+
|
|
134
|
+
Returns
|
|
135
|
+
-------
|
|
136
|
+
dict
|
|
137
|
+
A dictionary of supporting arrays
|
|
138
|
+
"""
|
|
106
139
|
import numpy as np
|
|
107
140
|
|
|
108
141
|
supporting_arrays = {}
|
|
@@ -114,16 +147,18 @@ def load_supporting_arrays(zipf, entries) -> dict:
|
|
|
114
147
|
return supporting_arrays
|
|
115
148
|
|
|
116
149
|
|
|
117
|
-
def save_metadata(
|
|
118
|
-
|
|
150
|
+
def save_metadata(
|
|
151
|
+
path: str, metadata: dict, *, supporting_arrays: dict = None, name: str = DEFAULT_NAME, folder: str = DEFAULT_FOLDER
|
|
152
|
+
) -> None:
|
|
153
|
+
"""Save metadata to a checkpoint file.
|
|
119
154
|
|
|
120
155
|
Parameters
|
|
121
156
|
----------
|
|
122
157
|
path : str
|
|
123
158
|
The path to the checkpoint file
|
|
124
|
-
metadata :
|
|
159
|
+
metadata : dict
|
|
125
160
|
A JSON serializable object
|
|
126
|
-
supporting_arrays: dict, optional
|
|
161
|
+
supporting_arrays : dict, optional
|
|
127
162
|
A dictionary of supporting NumPy arrays
|
|
128
163
|
name : str, optional
|
|
129
164
|
The name of the metadata file in the zip archive
|
|
@@ -179,7 +214,20 @@ def save_metadata(path, metadata, *, supporting_arrays=None, name=DEFAULT_NAME,
|
|
|
179
214
|
zipf.writestr(entry["path"], value.tobytes())
|
|
180
215
|
|
|
181
216
|
|
|
182
|
-
def _edit_metadata(path, name, callback, supporting_arrays=None):
|
|
217
|
+
def _edit_metadata(path: str, name: str, callback: Callable, supporting_arrays: dict = None) -> None:
|
|
218
|
+
"""Edit metadata in a checkpoint file.
|
|
219
|
+
|
|
220
|
+
Parameters
|
|
221
|
+
----------
|
|
222
|
+
path : str
|
|
223
|
+
The path to the checkpoint file
|
|
224
|
+
name : str
|
|
225
|
+
The name of the metadata file in the zip archive
|
|
226
|
+
callback : Callable
|
|
227
|
+
A callback function to edit the metadata
|
|
228
|
+
supporting_arrays : dict, optional
|
|
229
|
+
A dictionary of supporting NumPy arrays
|
|
230
|
+
"""
|
|
183
231
|
new_path = f"{path}.anemoi-edit-{time.time()}-{os.getpid()}.tmp"
|
|
184
232
|
|
|
185
233
|
found = False
|
|
@@ -223,8 +271,20 @@ def _edit_metadata(path, name, callback, supporting_arrays=None):
|
|
|
223
271
|
LOG.info("Updated metadata in %s", path)
|
|
224
272
|
|
|
225
273
|
|
|
226
|
-
def replace_metadata(path, metadata, supporting_arrays=None, *, name=DEFAULT_NAME):
|
|
274
|
+
def replace_metadata(path: str, metadata: dict, supporting_arrays: dict = None, *, name: str = DEFAULT_NAME) -> None:
|
|
275
|
+
"""Replace metadata in a checkpoint file.
|
|
227
276
|
|
|
277
|
+
Parameters
|
|
278
|
+
----------
|
|
279
|
+
path : str
|
|
280
|
+
The path to the checkpoint file
|
|
281
|
+
metadata : dict
|
|
282
|
+
A JSON serializable object
|
|
283
|
+
supporting_arrays : dict, optional
|
|
284
|
+
A dictionary of supporting NumPy arrays
|
|
285
|
+
name : str, optional
|
|
286
|
+
The name of the metadata file in the zip archive
|
|
287
|
+
"""
|
|
228
288
|
if not isinstance(metadata, dict):
|
|
229
289
|
raise ValueError(f"metadata must be a dict, got {type(metadata)}")
|
|
230
290
|
|
|
@@ -238,8 +298,16 @@ def replace_metadata(path, metadata, supporting_arrays=None, *, name=DEFAULT_NAM
|
|
|
238
298
|
return _edit_metadata(path, name, callback, supporting_arrays)
|
|
239
299
|
|
|
240
300
|
|
|
241
|
-
def remove_metadata(path, *, name=DEFAULT_NAME):
|
|
301
|
+
def remove_metadata(path: str, *, name: str = DEFAULT_NAME) -> None:
|
|
302
|
+
"""Remove metadata from a checkpoint file.
|
|
242
303
|
|
|
304
|
+
Parameters
|
|
305
|
+
----------
|
|
306
|
+
path : str
|
|
307
|
+
The path to the checkpoint file
|
|
308
|
+
name : str, optional
|
|
309
|
+
The name of the metadata file in the zip archive
|
|
310
|
+
"""
|
|
243
311
|
LOG.info("Removing metadata '%s' from %s", name, path)
|
|
244
312
|
|
|
245
313
|
def callback(full):
|
anemoi/utils/cli.py
CHANGED
|
@@ -14,6 +14,7 @@ import logging
|
|
|
14
14
|
import os
|
|
15
15
|
import sys
|
|
16
16
|
import traceback
|
|
17
|
+
from typing import Callable
|
|
17
18
|
|
|
18
19
|
try:
|
|
19
20
|
import argcomplete
|
|
@@ -24,13 +25,36 @@ LOG = logging.getLogger(__name__)
|
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
class Command:
|
|
28
|
+
"""Base class for commands."""
|
|
29
|
+
|
|
27
30
|
accept_unknown_args = False
|
|
28
31
|
|
|
29
|
-
def run(self, args):
|
|
32
|
+
def run(self, args: argparse.Namespace) -> None:
|
|
33
|
+
"""Run the command.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
args : argparse.Namespace
|
|
38
|
+
The arguments for the command
|
|
39
|
+
"""
|
|
30
40
|
raise NotImplementedError(f"Command not implemented: {args.command}")
|
|
31
41
|
|
|
32
42
|
|
|
33
|
-
def make_parser(description, commands):
|
|
43
|
+
def make_parser(description: str, commands: dict[str, Command]) -> argparse.ArgumentParser:
|
|
44
|
+
"""Create an argument parser for the CLI.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
description : str
|
|
49
|
+
The description of the CLI
|
|
50
|
+
commands : dict[str, Command]
|
|
51
|
+
A dictionary of command names to Command instances
|
|
52
|
+
|
|
53
|
+
Returns
|
|
54
|
+
-------
|
|
55
|
+
argparse.ArgumentParser
|
|
56
|
+
The argument parser
|
|
57
|
+
"""
|
|
34
58
|
parser = argparse.ArgumentParser(
|
|
35
59
|
description=description,
|
|
36
60
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
@@ -60,20 +84,61 @@ def make_parser(description, commands):
|
|
|
60
84
|
class Failed(Command):
|
|
61
85
|
"""Command not available."""
|
|
62
86
|
|
|
63
|
-
def __init__(self, name, error):
|
|
87
|
+
def __init__(self, name: str, error: ImportError):
|
|
88
|
+
"""Initialize the Failed command.
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
name : str
|
|
93
|
+
The name of the command
|
|
94
|
+
error : ImportError
|
|
95
|
+
The error that occurred
|
|
96
|
+
"""
|
|
64
97
|
self.name = name
|
|
65
98
|
self.error = error
|
|
66
99
|
traceback.print_tb(error.__traceback__)
|
|
67
100
|
|
|
68
|
-
def add_arguments(self, command_parser):
|
|
101
|
+
def add_arguments(self, command_parser: argparse.ArgumentParser) -> None:
|
|
102
|
+
"""Add arguments to the command parser.
|
|
103
|
+
|
|
104
|
+
Parameters
|
|
105
|
+
----------
|
|
106
|
+
command_parser : argparse.ArgumentParser
|
|
107
|
+
The command parser
|
|
108
|
+
"""
|
|
69
109
|
command_parser.add_argument("x", nargs=argparse.REMAINDER)
|
|
70
110
|
|
|
71
|
-
def run(self, args):
|
|
111
|
+
def run(self, args: argparse.Namespace) -> None:
|
|
112
|
+
"""Run the command.
|
|
113
|
+
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
args : argparse.Namespace
|
|
117
|
+
The arguments for the command
|
|
118
|
+
"""
|
|
72
119
|
print(f"Command '{self.name}' not available: {self.error}")
|
|
73
120
|
sys.exit(1)
|
|
74
121
|
|
|
75
122
|
|
|
76
|
-
def register_commands(here, package, select, fail=None):
|
|
123
|
+
def register_commands(here: str, package: str, select: Callable, fail: Callable = None) -> dict[str, Command]:
|
|
124
|
+
"""Register commands from a package.
|
|
125
|
+
|
|
126
|
+
Parameters
|
|
127
|
+
----------
|
|
128
|
+
here : str
|
|
129
|
+
The directory containing the commands
|
|
130
|
+
package : str
|
|
131
|
+
The package name
|
|
132
|
+
select : Callable
|
|
133
|
+
A function to select the command object from the module
|
|
134
|
+
fail : Callable, optional
|
|
135
|
+
A function to create a Failed command if a command cannot be imported
|
|
136
|
+
|
|
137
|
+
Returns
|
|
138
|
+
-------
|
|
139
|
+
dict[str, Command]
|
|
140
|
+
A dictionary of command names to Command instances
|
|
141
|
+
"""
|
|
77
142
|
result = {}
|
|
78
143
|
not_available = {}
|
|
79
144
|
|
|
@@ -120,7 +185,18 @@ def register_commands(here, package, select, fail=None):
|
|
|
120
185
|
return result
|
|
121
186
|
|
|
122
187
|
|
|
123
|
-
def cli_main(version, description, commands):
|
|
188
|
+
def cli_main(version: str, description: str, commands: dict[str, Command]) -> None:
|
|
189
|
+
"""Main entry point for the CLI.
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
version : str
|
|
194
|
+
The version of the CLI
|
|
195
|
+
description : str
|
|
196
|
+
The description of the CLI
|
|
197
|
+
commands : dict[str, Command]
|
|
198
|
+
A dictionary of command names to Command instances
|
|
199
|
+
"""
|
|
124
200
|
parser = make_parser(description, commands)
|
|
125
201
|
args, unknown = parser.parse_known_args()
|
|
126
202
|
if argcomplete:
|
|
@@ -7,6 +7,10 @@
|
|
|
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
|
+
"""This module initializes and registers command-line interface (CLI) commands
|
|
11
|
+
for the Anemoi utilities.
|
|
12
|
+
"""
|
|
13
|
+
|
|
10
14
|
import os
|
|
11
15
|
|
|
12
16
|
from anemoi.utils.cli import Command
|
anemoi/utils/commands/config.py
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
import json
|
|
12
|
+
from argparse import ArgumentParser
|
|
13
|
+
from argparse import Namespace
|
|
12
14
|
|
|
13
15
|
from ..config import config_path
|
|
14
16
|
from ..config import load_config
|
|
@@ -16,11 +18,26 @@ from . import Command
|
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
class Config(Command):
|
|
21
|
+
"""Handle configuration related commands."""
|
|
19
22
|
|
|
20
|
-
def add_arguments(self, command_parser):
|
|
23
|
+
def add_arguments(self, command_parser: ArgumentParser) -> None:
|
|
24
|
+
"""Add arguments to the command parser.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
command_parser : ArgumentParser
|
|
29
|
+
The argument parser to which the arguments will be added.
|
|
30
|
+
"""
|
|
21
31
|
command_parser.add_argument("--path", help="Print path to config file")
|
|
22
32
|
|
|
23
|
-
def run(self, args):
|
|
33
|
+
def run(self, args: Namespace) -> None:
|
|
34
|
+
"""Execute the command with the provided arguments.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
args : Namespace
|
|
39
|
+
The arguments passed to the command.
|
|
40
|
+
"""
|
|
24
41
|
if args.path:
|
|
25
42
|
print(config_path())
|
|
26
43
|
else:
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
# nor does it submit to any jurisdiction.
|
|
7
7
|
|
|
8
8
|
import json
|
|
9
|
+
import sys
|
|
10
|
+
from argparse import ArgumentParser
|
|
11
|
+
from argparse import Namespace
|
|
9
12
|
|
|
10
13
|
from anemoi.utils.mars.requests import print_request
|
|
11
14
|
|
|
@@ -15,15 +18,32 @@ from . import Command
|
|
|
15
18
|
class Requests(Command):
|
|
16
19
|
"""Convert a JSON requests file to MARS format."""
|
|
17
20
|
|
|
18
|
-
def add_arguments(self, command_parser):
|
|
21
|
+
def add_arguments(self, command_parser: ArgumentParser) -> None:
|
|
22
|
+
"""Add arguments to the command parser.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
command_parser : ArgumentParser
|
|
27
|
+
The argument parser to which the arguments will be added.
|
|
28
|
+
"""
|
|
19
29
|
command_parser.add_argument("input")
|
|
20
30
|
command_parser.add_argument("output")
|
|
21
31
|
command_parser.add_argument("--verb", default="retrieve")
|
|
22
32
|
command_parser.add_argument("--only-one-field", action="store_true")
|
|
23
33
|
|
|
24
|
-
def run(self, args):
|
|
25
|
-
with
|
|
26
|
-
|
|
34
|
+
def run(self, args: Namespace) -> None:
|
|
35
|
+
"""Execute the command with the provided arguments.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
args : Namespace
|
|
40
|
+
The arguments passed to the command.
|
|
41
|
+
"""
|
|
42
|
+
if args.input == "-":
|
|
43
|
+
requests = json.load(sys.stdin)
|
|
44
|
+
else:
|
|
45
|
+
with open(args.input) as f:
|
|
46
|
+
requests = json.load(f)
|
|
27
47
|
|
|
28
48
|
if args.only_one_field:
|
|
29
49
|
for r in requests:
|