anemoi-utils 0.3.8__tar.gz → 0.3.10__tar.gz
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-0.3.8 → anemoi_utils-0.3.10}/.github/workflows/python-publish.yml +1 -1
- {anemoi_utils-0.3.8/src/anemoi_utils.egg-info → anemoi_utils-0.3.10}/PKG-INFO +1 -1
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/_version.py +2 -2
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/cli.py +2 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/config.py +96 -16
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/humanize.py +5 -2
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/s3.py +12 -7
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10/src/anemoi_utils.egg-info}/PKG-INFO +1 -1
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/tests/test_utils.py +17 -1
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/.gitignore +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/.pre-commit-config.yaml +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/.readthedocs.yaml +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/LICENSE +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/README.md +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/Makefile +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/_static/logo.png +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/_static/style.css +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/_templates/.gitkeep +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/conf.py +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/index.rst +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/installing.rst +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/modules/checkpoints.rst +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/modules/config.rst +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/modules/dates.rst +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/modules/grib.rst +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/modules/humanize.rst +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/modules/provenance.rst +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/modules/s3.rst +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/modules/text.rst +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/requirements.txt +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/pyproject.toml +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/setup.cfg +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/__init__.py +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/__main__.py +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/caching.py +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/checkpoints.py +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/commands/__init__.py +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/commands/config.py +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/dates.py +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/grib.py +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/hindcasts.py +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/mars/__init__.py +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/mars/mars.yaml +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/provenance.py +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/text.py +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/timer.py +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi_utils.egg-info/SOURCES.txt +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi_utils.egg-info/dependency_links.txt +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi_utils.egg-info/entry_points.txt +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi_utils.egg-info/requires.txt +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi_utils.egg-info/top_level.txt +0 -0
- {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/tests/test_dates.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: anemoi-utils
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.10
|
|
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
|
|
@@ -104,12 +104,54 @@ class DotDict(dict):
|
|
|
104
104
|
|
|
105
105
|
CONFIG = {}
|
|
106
106
|
CHECKED = {}
|
|
107
|
-
CONFIG_LOCK = threading.
|
|
107
|
+
CONFIG_LOCK = threading.RLock()
|
|
108
108
|
QUIET = False
|
|
109
109
|
|
|
110
110
|
|
|
111
|
+
def _find(config, what, result=None):
|
|
112
|
+
if result is None:
|
|
113
|
+
result = []
|
|
114
|
+
|
|
115
|
+
if isinstance(config, list):
|
|
116
|
+
for i in config:
|
|
117
|
+
_find(i, what, result)
|
|
118
|
+
return result
|
|
119
|
+
|
|
120
|
+
if isinstance(config, dict):
|
|
121
|
+
if what in config:
|
|
122
|
+
result.append(config[what])
|
|
123
|
+
|
|
124
|
+
for k, v in config.items():
|
|
125
|
+
_find(v, what, result)
|
|
126
|
+
|
|
127
|
+
return result
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _merge_dicts(a, b):
|
|
131
|
+
for k, v in b.items():
|
|
132
|
+
if k in a and isinstance(a[k], dict) and isinstance(v, dict):
|
|
133
|
+
_merge_dicts(a[k], v)
|
|
134
|
+
else:
|
|
135
|
+
a[k] = v
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _set_defaults(a, b):
|
|
139
|
+
for k, v in b.items():
|
|
140
|
+
if k in a and isinstance(a[k], dict) and isinstance(v, dict):
|
|
141
|
+
_set_defaults(a[k], v)
|
|
142
|
+
else:
|
|
143
|
+
a.setdefault(k, v)
|
|
144
|
+
|
|
145
|
+
|
|
111
146
|
def config_path(name="settings.toml"):
|
|
112
147
|
global QUIET
|
|
148
|
+
|
|
149
|
+
if name.startswith("/") or name.startswith("."):
|
|
150
|
+
return name
|
|
151
|
+
|
|
152
|
+
if name.startswith("~"):
|
|
153
|
+
return os.path.expanduser(name)
|
|
154
|
+
|
|
113
155
|
full = os.path.join(os.path.expanduser("~"), ".config", "anemoi", name)
|
|
114
156
|
os.makedirs(os.path.dirname(full), exist_ok=True)
|
|
115
157
|
|
|
@@ -133,7 +175,15 @@ def config_path(name="settings.toml"):
|
|
|
133
175
|
return full
|
|
134
176
|
|
|
135
177
|
|
|
136
|
-
def
|
|
178
|
+
def load_any_dict_format(path):
|
|
179
|
+
"""Load a configuration file in any supported format: JSON, YAML and TOML.
|
|
180
|
+
|
|
181
|
+
Returns
|
|
182
|
+
-------
|
|
183
|
+
dict
|
|
184
|
+
The decoded configuration file.
|
|
185
|
+
"""
|
|
186
|
+
|
|
137
187
|
try:
|
|
138
188
|
if path.endswith(".json"):
|
|
139
189
|
with open(path, "rb") as f:
|
|
@@ -153,23 +203,42 @@ def _load(path):
|
|
|
153
203
|
return open(path).read()
|
|
154
204
|
|
|
155
205
|
|
|
156
|
-
def _load_config(name="settings.toml"):
|
|
206
|
+
def _load_config(name="settings.toml", secrets=None, defaults=None):
|
|
157
207
|
|
|
158
208
|
if name in CONFIG:
|
|
159
209
|
return CONFIG[name]
|
|
160
210
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
config = _load(conf)
|
|
211
|
+
path = config_path(name)
|
|
212
|
+
if os.path.exists(path):
|
|
213
|
+
config = load_any_dict_format(path)
|
|
165
214
|
else:
|
|
166
215
|
config = {}
|
|
167
216
|
|
|
168
|
-
if
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
217
|
+
if defaults is not None:
|
|
218
|
+
if isinstance(defaults, str):
|
|
219
|
+
defaults = load_raw_config(defaults)
|
|
220
|
+
_set_defaults(config, defaults)
|
|
221
|
+
|
|
222
|
+
if secrets is not None:
|
|
223
|
+
if isinstance(secrets, str):
|
|
224
|
+
secrets = [secrets]
|
|
172
225
|
|
|
226
|
+
base, ext = os.path.splitext(path)
|
|
227
|
+
secret_name = base + ".secrets" + ext
|
|
228
|
+
|
|
229
|
+
found = set()
|
|
230
|
+
for secret in secrets:
|
|
231
|
+
if _find(config, secret):
|
|
232
|
+
found.add(secret)
|
|
233
|
+
|
|
234
|
+
if found:
|
|
235
|
+
check_config_mode(name, secret_name, found)
|
|
236
|
+
|
|
237
|
+
check_config_mode(secret_name, None)
|
|
238
|
+
secret_config = _load_config(secret_name)
|
|
239
|
+
_merge_dicts(config, secret_config)
|
|
240
|
+
|
|
241
|
+
CONFIG[name] = DotDict(config)
|
|
173
242
|
return CONFIG[name]
|
|
174
243
|
|
|
175
244
|
|
|
@@ -211,7 +280,7 @@ def save_config(name, data):
|
|
|
211
280
|
_save_config(name, data)
|
|
212
281
|
|
|
213
282
|
|
|
214
|
-
def load_config(name="settings.toml"):
|
|
283
|
+
def load_config(name="settings.toml", secrets=None, defaults=None):
|
|
215
284
|
"""Read a configuration file.
|
|
216
285
|
|
|
217
286
|
Parameters
|
|
@@ -224,20 +293,21 @@ def load_config(name="settings.toml"):
|
|
|
224
293
|
DotDict or str
|
|
225
294
|
Return DotDict if it is a dictionary, otherwise the raw data
|
|
226
295
|
"""
|
|
296
|
+
|
|
227
297
|
with CONFIG_LOCK:
|
|
228
|
-
return _load_config(name)
|
|
298
|
+
return _load_config(name, secrets, defaults)
|
|
229
299
|
|
|
230
300
|
|
|
231
301
|
def load_raw_config(name, default=None):
|
|
232
302
|
|
|
233
303
|
path = config_path(name)
|
|
234
304
|
if os.path.exists(path):
|
|
235
|
-
return
|
|
305
|
+
return load_any_dict_format(path)
|
|
236
306
|
|
|
237
307
|
return default
|
|
238
308
|
|
|
239
309
|
|
|
240
|
-
def check_config_mode(name="settings.toml"):
|
|
310
|
+
def check_config_mode(name="settings.toml", secrets_name=None, secrets=None):
|
|
241
311
|
"""Check that a configuration file is secure.
|
|
242
312
|
|
|
243
313
|
Parameters
|
|
@@ -253,8 +323,18 @@ def check_config_mode(name="settings.toml"):
|
|
|
253
323
|
with CONFIG_LOCK:
|
|
254
324
|
if name in CHECKED:
|
|
255
325
|
return
|
|
326
|
+
|
|
256
327
|
conf = config_path(name)
|
|
328
|
+
if not os.path.exists(conf):
|
|
329
|
+
return
|
|
257
330
|
mode = os.stat(conf).st_mode
|
|
258
331
|
if mode & 0o777 != 0o600:
|
|
259
|
-
|
|
332
|
+
if secrets_name:
|
|
333
|
+
secret_path = config_path(secrets_name)
|
|
334
|
+
raise SystemError(
|
|
335
|
+
f"Configuration file {conf} should not hold entries {secrets}.\n"
|
|
336
|
+
f"Please move them to {secret_path}."
|
|
337
|
+
)
|
|
338
|
+
raise SystemError(f"Configuration file {conf} is not secure.\n" f"Please run `chmod 600 {conf}`.")
|
|
339
|
+
|
|
260
340
|
CHECKED[name] = True
|
|
@@ -190,7 +190,7 @@ def __(n):
|
|
|
190
190
|
return "th"
|
|
191
191
|
|
|
192
192
|
|
|
193
|
-
def when(then, now=None, short=True):
|
|
193
|
+
def when(then, now=None, short=True, use_utc=False):
|
|
194
194
|
"""Generate a human readable string for a date, relative to now
|
|
195
195
|
|
|
196
196
|
>>> when(datetime.datetime.now() - datetime.timedelta(hours=2))
|
|
@@ -226,7 +226,10 @@ def when(then, now=None, short=True):
|
|
|
226
226
|
last = "last"
|
|
227
227
|
|
|
228
228
|
if now is None:
|
|
229
|
-
|
|
229
|
+
if use_utc:
|
|
230
|
+
now = datetime.datetime.utcnow()
|
|
231
|
+
else:
|
|
232
|
+
now = datetime.datetime.now()
|
|
230
233
|
|
|
231
234
|
diff = (now - then).total_seconds()
|
|
232
235
|
|
|
@@ -18,7 +18,7 @@ to use a different S3 compatible service::
|
|
|
18
18
|
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
-
import concurrent
|
|
21
|
+
import concurrent.futures
|
|
22
22
|
import logging
|
|
23
23
|
import os
|
|
24
24
|
import threading
|
|
@@ -26,7 +26,6 @@ from copy import deepcopy
|
|
|
26
26
|
|
|
27
27
|
import tqdm
|
|
28
28
|
|
|
29
|
-
from .config import check_config_mode
|
|
30
29
|
from .config import load_config
|
|
31
30
|
from .humanize import bytes
|
|
32
31
|
|
|
@@ -41,9 +40,7 @@ thread_local = threading.local()
|
|
|
41
40
|
def s3_client(bucket):
|
|
42
41
|
import boto3
|
|
43
42
|
|
|
44
|
-
config = load_config()
|
|
45
|
-
if "object-storage" in config:
|
|
46
|
-
check_config_mode()
|
|
43
|
+
config = load_config(secrets=["aws_access_key_id", "aws_secret_access_key"])
|
|
47
44
|
|
|
48
45
|
if not hasattr(thread_local, "s3_clients"):
|
|
49
46
|
thread_local.s3_clients = {}
|
|
@@ -51,8 +48,14 @@ def s3_client(bucket):
|
|
|
51
48
|
if bucket not in thread_local.s3_clients:
|
|
52
49
|
|
|
53
50
|
options = {}
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
cfg = config.get("object-storage", {})
|
|
52
|
+
for k, v in cfg.items():
|
|
53
|
+
if isinstance(v, (str, int, float, bool)):
|
|
54
|
+
options[k] = v
|
|
55
|
+
|
|
56
|
+
for k, v in cfg.get(bucket, {}).items():
|
|
57
|
+
if isinstance(v, (str, int, float, bool)):
|
|
58
|
+
options[k] = v
|
|
56
59
|
|
|
57
60
|
type = options.pop("type", "s3")
|
|
58
61
|
if type != "s3":
|
|
@@ -420,11 +423,13 @@ def list_folder(folder):
|
|
|
420
423
|
A list of the subfolders names in the folder.
|
|
421
424
|
"""
|
|
422
425
|
|
|
426
|
+
print(folder)
|
|
423
427
|
assert folder.startswith("s3://")
|
|
424
428
|
if not folder.endswith("/"):
|
|
425
429
|
folder += "/"
|
|
426
430
|
|
|
427
431
|
_, _, bucket, prefix = folder.split("/", 3)
|
|
432
|
+
print(bucket, prefix)
|
|
428
433
|
|
|
429
434
|
s3 = s3_client(bucket)
|
|
430
435
|
paginator = s3.get_paginator("list_objects_v2")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: anemoi-utils
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.10
|
|
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
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
from anemoi.utils.config import DotDict
|
|
10
|
+
from anemoi.utils.config import _merge_dicts
|
|
11
|
+
from anemoi.utils.config import _set_defaults
|
|
10
12
|
from anemoi.utils.grib import paramid_to_shortname
|
|
11
13
|
from anemoi.utils.grib import shortname_to_paramid
|
|
12
14
|
|
|
@@ -30,10 +32,24 @@ def test_dotdict():
|
|
|
30
32
|
assert d.e[1].a == 3
|
|
31
33
|
|
|
32
34
|
|
|
35
|
+
def test_merge_dicts():
|
|
36
|
+
a = dict(a=1, b=2, c=dict(d=3, e=4))
|
|
37
|
+
b = dict(a=10, c=dict(a=30, e=40), d=9)
|
|
38
|
+
_merge_dicts(a, b)
|
|
39
|
+
assert a == {"a": 10, "b": 2, "c": {"d": 3, "e": 40, "a": 30}, "d": 9}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_set_defaults():
|
|
43
|
+
a = dict(a=1, b=2, c=dict(d=3, e=4))
|
|
44
|
+
b = dict(a=10, c=dict(a=30, e=40), d=9)
|
|
45
|
+
_set_defaults(a, b)
|
|
46
|
+
assert a == {"a": 1, "b": 2, "c": {"d": 3, "e": 4, "a": 30}, "d": 9}
|
|
47
|
+
|
|
48
|
+
|
|
33
49
|
def test_grib():
|
|
34
50
|
assert shortname_to_paramid("2t") == 167
|
|
35
51
|
assert paramid_to_shortname(167) == "2t"
|
|
36
52
|
|
|
37
53
|
|
|
38
54
|
if __name__ == "__main__":
|
|
39
|
-
|
|
55
|
+
test_set_defaults()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|