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.

Files changed (52) hide show
  1. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/.github/workflows/python-publish.yml +1 -1
  2. {anemoi_utils-0.3.8/src/anemoi_utils.egg-info → anemoi_utils-0.3.10}/PKG-INFO +1 -1
  3. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/_version.py +2 -2
  4. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/cli.py +2 -0
  5. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/config.py +96 -16
  6. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/humanize.py +5 -2
  7. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/s3.py +12 -7
  8. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10/src/anemoi_utils.egg-info}/PKG-INFO +1 -1
  9. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/tests/test_utils.py +17 -1
  10. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/.gitignore +0 -0
  11. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/.pre-commit-config.yaml +0 -0
  12. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/.readthedocs.yaml +0 -0
  13. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/LICENSE +0 -0
  14. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/README.md +0 -0
  15. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/Makefile +0 -0
  16. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/_static/logo.png +0 -0
  17. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/_static/style.css +0 -0
  18. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/_templates/.gitkeep +0 -0
  19. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/conf.py +0 -0
  20. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/index.rst +0 -0
  21. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/installing.rst +0 -0
  22. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/modules/checkpoints.rst +0 -0
  23. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/modules/config.rst +0 -0
  24. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/modules/dates.rst +0 -0
  25. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/modules/grib.rst +0 -0
  26. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/modules/humanize.rst +0 -0
  27. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/modules/provenance.rst +0 -0
  28. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/modules/s3.rst +0 -0
  29. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/modules/text.rst +0 -0
  30. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/docs/requirements.txt +0 -0
  31. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/pyproject.toml +0 -0
  32. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/setup.cfg +0 -0
  33. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/__init__.py +0 -0
  34. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/__main__.py +0 -0
  35. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/caching.py +0 -0
  36. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/checkpoints.py +0 -0
  37. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/commands/__init__.py +0 -0
  38. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/commands/config.py +0 -0
  39. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/dates.py +0 -0
  40. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/grib.py +0 -0
  41. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/hindcasts.py +0 -0
  42. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/mars/__init__.py +0 -0
  43. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/mars/mars.yaml +0 -0
  44. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/provenance.py +0 -0
  45. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/text.py +0 -0
  46. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi/utils/timer.py +0 -0
  47. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi_utils.egg-info/SOURCES.txt +0 -0
  48. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi_utils.egg-info/dependency_links.txt +0 -0
  49. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi_utils.egg-info/entry_points.txt +0 -0
  50. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi_utils.egg-info/requires.txt +0 -0
  51. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/src/anemoi_utils.egg-info/top_level.txt +0 -0
  52. {anemoi_utils-0.3.8 → anemoi_utils-0.3.10}/tests/test_dates.py +0 -0
@@ -6,7 +6,7 @@ name: Upload Python Package
6
6
  on:
7
7
 
8
8
  push: {}
9
-
9
+ pull_request:
10
10
  release:
11
11
  types: [created]
12
12
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: anemoi-utils
3
- Version: 0.3.8
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
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.3.8'
16
- __version_tuple__ = version_tuple = (0, 3, 8)
15
+ __version__ = version = '0.3.10'
16
+ __version_tuple__ = version_tuple = (0, 3, 10)
@@ -16,6 +16,8 @@ LOG = logging.getLogger(__name__)
16
16
 
17
17
 
18
18
  class Command:
19
+ accept_unknown_args = False
20
+
19
21
  def run(self, args):
20
22
  raise NotImplementedError(f"Command not implemented: {args.command}")
21
23
 
@@ -104,12 +104,54 @@ class DotDict(dict):
104
104
 
105
105
  CONFIG = {}
106
106
  CHECKED = {}
107
- CONFIG_LOCK = threading.Lock()
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 _load(path):
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
- conf = config_path(name)
162
-
163
- if os.path.exists(conf):
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 isinstance(config, dict):
169
- CONFIG[name] = DotDict(config)
170
- else:
171
- CONFIG[name] = config
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 _load(path)
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
- raise SystemError(f"Configuration file {conf} is not secure. " "Please run `chmod 600 {conf}`.")
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
- now = datetime.datetime.now()
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
- options.update(config.get("object-storage", {}))
55
- options.update(config.get("object-storage", {}).get(bucket, {}))
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.8
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
- test_grib()
55
+ test_set_defaults()
File without changes
File without changes
File without changes
File without changes
File without changes