anemoi-utils 0.3.6__tar.gz → 0.3.7__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 (53) hide show
  1. {anemoi_utils-0.3.6/src/anemoi_utils.egg-info → anemoi_utils-0.3.7}/PKG-INFO +1 -1
  2. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/pyproject.toml +1 -0
  3. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/src/anemoi/utils/_version.py +2 -2
  4. anemoi_utils-0.3.7/src/anemoi/utils/commands/config.py +31 -0
  5. anemoi_utils-0.3.7/src/anemoi/utils/config.py +247 -0
  6. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/src/anemoi/utils/s3.py +16 -0
  7. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7/src/anemoi_utils.egg-info}/PKG-INFO +1 -1
  8. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/src/anemoi_utils.egg-info/SOURCES.txt +1 -1
  9. anemoi_utils-0.3.6/src/anemoi/utils/commands/checkpoint.py +0 -61
  10. anemoi_utils-0.3.6/src/anemoi/utils/config.py +0 -142
  11. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/.github/workflows/python-publish.yml +0 -0
  12. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/.gitignore +0 -0
  13. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/.pre-commit-config.yaml +0 -0
  14. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/.readthedocs.yaml +0 -0
  15. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/LICENSE +0 -0
  16. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/README.md +0 -0
  17. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/docs/Makefile +0 -0
  18. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/docs/_static/logo.png +0 -0
  19. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/docs/_static/style.css +0 -0
  20. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/docs/_templates/.gitkeep +0 -0
  21. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/docs/conf.py +0 -0
  22. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/docs/index.rst +0 -0
  23. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/docs/installing.rst +0 -0
  24. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/docs/modules/checkpoints.rst +0 -0
  25. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/docs/modules/config.rst +0 -0
  26. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/docs/modules/dates.rst +0 -0
  27. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/docs/modules/grib.rst +0 -0
  28. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/docs/modules/humanize.rst +0 -0
  29. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/docs/modules/provenance.rst +0 -0
  30. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/docs/modules/s3.rst +0 -0
  31. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/docs/modules/text.rst +0 -0
  32. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/docs/requirements.txt +0 -0
  33. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/setup.cfg +0 -0
  34. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/src/anemoi/utils/__init__.py +0 -0
  35. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/src/anemoi/utils/__main__.py +0 -0
  36. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/src/anemoi/utils/caching.py +0 -0
  37. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/src/anemoi/utils/checkpoints.py +0 -0
  38. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/src/anemoi/utils/cli.py +0 -0
  39. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/src/anemoi/utils/commands/__init__.py +0 -0
  40. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/src/anemoi/utils/dates.py +0 -0
  41. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/src/anemoi/utils/grib.py +0 -0
  42. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/src/anemoi/utils/humanize.py +0 -0
  43. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/src/anemoi/utils/mars/__init__.py +0 -0
  44. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/src/anemoi/utils/mars/mars.yaml +0 -0
  45. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/src/anemoi/utils/provenance.py +0 -0
  46. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/src/anemoi/utils/text.py +0 -0
  47. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/src/anemoi/utils/timer.py +0 -0
  48. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/src/anemoi_utils.egg-info/dependency_links.txt +0 -0
  49. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/src/anemoi_utils.egg-info/entry_points.txt +0 -0
  50. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/src/anemoi_utils.egg-info/requires.txt +0 -0
  51. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/src/anemoi_utils.egg-info/top_level.txt +0 -0
  52. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/tests/test_dates.py +0 -0
  53. {anemoi_utils-0.3.6 → anemoi_utils-0.3.7}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: anemoi-utils
3
- Version: 0.3.6
3
+ Version: 0.3.7
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,6 +104,7 @@ urls.Documentation = "https://anemoi-utils.readthedocs.io/"
104
104
  urls.Homepage = "https://github.com/ecmwf/anemoi-utils/"
105
105
  urls.Issues = "https://github.com/ecmwf/anemoi-utils/issues"
106
106
  urls.Repository = "https://github.com/ecmwf/anemoi-utils/"
107
+
107
108
  scripts.anemoi-utils = "anemoi.utils.__main__:main"
108
109
 
109
110
  [tool.setuptools.package-data]
@@ -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.6'
16
- __version_tuple__ = version_tuple = (0, 3, 6)
15
+ __version__ = version = '0.3.7'
16
+ __version_tuple__ = version_tuple = (0, 3, 7)
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env python
2
+ # (C) Copyright 2024 ECMWF.
3
+ #
4
+ # This software is licensed under the terms of the Apache Licence Version 2.0
5
+ # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
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
+
12
+ import json
13
+
14
+ from ..config import config_path
15
+ from ..config import load_config
16
+ from . import Command
17
+
18
+
19
+ class Config(Command):
20
+
21
+ def add_arguments(self, command_parser):
22
+ command_parser.add_argument("--path", help="Print path to config file")
23
+
24
+ def run(self, args):
25
+ if args.path:
26
+ print(config_path())
27
+ else:
28
+ print(json.dumps(load_config(), indent=4))
29
+
30
+
31
+ command = Config
@@ -0,0 +1,247 @@
1
+ # (C) Copyright 2024 European Centre for Medium-Range Weather Forecasts.
2
+ # This software is licensed under the terms of the Apache Licence Version 2.0
3
+ # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
4
+ # In applying this licence, ECMWF does not waive the privileges and immunities
5
+ # granted to it by virtue of its status as an intergovernmental organisation
6
+ # nor does it submit to any jurisdiction.
7
+
8
+
9
+ import json
10
+ import logging
11
+ import os
12
+ import threading
13
+
14
+ import yaml
15
+
16
+ try:
17
+ import tomllib # Only available since 3.11
18
+ except ImportError:
19
+ import tomli as tomllib
20
+
21
+
22
+ LOG = logging.getLogger(__name__)
23
+
24
+
25
+ class DotDict(dict):
26
+ """A dictionary that allows access to its keys as attributes.
27
+
28
+ >>> d = DotDict({"a": 1, "b": {"c": 2}})
29
+ >>> d.a
30
+ 1
31
+ >>> d.b.c
32
+ 2
33
+ >>> d.b = 3
34
+ >>> d.b
35
+ 3
36
+
37
+ The class is recursive, so nested dictionaries are also DotDicts.
38
+
39
+ The DotDict class has the same constructor as the dict class.
40
+
41
+ >>> d = DotDict(a=1, b=2)
42
+
43
+ """
44
+
45
+ def __init__(self, *args, **kwargs):
46
+ super().__init__(*args, **kwargs)
47
+
48
+ for k, v in self.items():
49
+ if isinstance(v, dict):
50
+ self[k] = DotDict(v)
51
+
52
+ if isinstance(v, list):
53
+ self[k] = [DotDict(i) if isinstance(i, dict) else i for i in v]
54
+
55
+ if isinstance(v, tuple):
56
+ self[k] = [DotDict(i) if isinstance(i, dict) else i for i in v]
57
+
58
+ @classmethod
59
+ def from_file(cls, path: str):
60
+ _, ext = os.path.splitext(path)
61
+ if ext == ".yaml" or ext == ".yml":
62
+ return cls.from_yaml_file(path)
63
+ elif ext == ".json":
64
+ return cls.from_json_file(path)
65
+ elif ext == ".toml":
66
+ return cls.from_toml_file(path)
67
+ else:
68
+ raise ValueError(f"Unknown file extension {ext}")
69
+
70
+ @classmethod
71
+ def from_yaml_file(cls, path: str):
72
+ with open(path, "r") as file:
73
+ data = yaml.safe_load(file)
74
+
75
+ return cls(data)
76
+
77
+ @classmethod
78
+ def from_json_file(cls, path: str):
79
+ with open(path, "r") as file:
80
+ data = json.load(file)
81
+
82
+ return cls(data)
83
+
84
+ @classmethod
85
+ def from_toml_file(cls, path: str):
86
+ with open(path, "r") as file:
87
+ data = tomllib.load(file)
88
+ return cls(data)
89
+
90
+ def __getattr__(self, attr):
91
+ try:
92
+ return self[attr]
93
+ except KeyError:
94
+ raise AttributeError(attr)
95
+
96
+ def __setattr__(self, attr, value):
97
+ if isinstance(value, dict):
98
+ value = DotDict(value)
99
+ self[attr] = value
100
+
101
+ def __repr__(self) -> str:
102
+ return f"DotDict({super().__repr__()})"
103
+
104
+
105
+ CONFIG = {}
106
+ CHECKED = {}
107
+ CONFIG_LOCK = threading.Lock()
108
+ QUIET = False
109
+
110
+
111
+ def config_path(name="settings.toml"):
112
+ global QUIET
113
+ full = os.path.join(os.path.expanduser("~"), ".config", "anemoi", name)
114
+ os.makedirs(os.path.dirname(full), exist_ok=True)
115
+
116
+ if name == "settings.toml":
117
+ old = os.path.join(os.path.expanduser("~"), ".anemoi.toml")
118
+ if not os.path.exists(full) and os.path.exists(old):
119
+ if not QUIET:
120
+ LOG.warning(
121
+ "Configuration file found at ~/.anemoi.toml. Please move it to ~/.config/anemoi/settings.toml"
122
+ )
123
+ QUIET = True
124
+ return old
125
+ else:
126
+ if os.path.exists(old):
127
+ if not QUIET:
128
+ LOG.warning(
129
+ "Configuration file found at ~/.anemoi.toml and ~/.config/anemoi/settings.toml, ignoring the former"
130
+ )
131
+ QUIET = True
132
+
133
+ return full
134
+
135
+
136
+ def _load(path):
137
+ if path.endswith(".json"):
138
+ with open(path, "rb") as f:
139
+ return json.load(f)
140
+
141
+ if path.endswith(".yaml") or path.endswith(".yml"):
142
+ with open(path, "rb") as f:
143
+ return yaml.safe_load(f)
144
+
145
+ if path.endswith(".toml"):
146
+ with open(path, "rb") as f:
147
+ return tomllib.load(f)
148
+
149
+ return open(path).read()
150
+
151
+
152
+ def _load_config(name="settings.toml"):
153
+
154
+ if name in CONFIG:
155
+ return CONFIG[name]
156
+
157
+ conf = config_path(name)
158
+
159
+ if os.path.exists(conf):
160
+ config = _load(conf)
161
+ else:
162
+ config = {}
163
+
164
+ if isinstance(config, dict):
165
+ CONFIG[name] = DotDict(config)
166
+ else:
167
+ CONFIG[name] = config
168
+
169
+ return CONFIG[name]
170
+
171
+
172
+ def _save_config(name, data):
173
+ CONFIG.pop(name, None)
174
+
175
+ conf = config_path(name)
176
+
177
+ if conf.endswith(".json"):
178
+ with open(conf, "w") as f:
179
+ json.dump(data, f, indent=4)
180
+ return
181
+
182
+ if conf.endswith(".yaml") or conf.endswith(".yml"):
183
+ with open(conf, "w") as f:
184
+ yaml.dump(data, f)
185
+ return
186
+
187
+ if conf.endswith(".toml"):
188
+ raise NotImplementedError("Saving to TOML is not implemented yet")
189
+
190
+ with open(conf, "w") as f:
191
+ f.write(data)
192
+
193
+
194
+ def save_config(name, data):
195
+ """Save a configuration file.
196
+
197
+ Parameters
198
+ ----------
199
+ name : str
200
+ The name of the configuration file to save.
201
+
202
+ data : Any
203
+ The data to save.
204
+
205
+ """
206
+ with CONFIG_LOCK:
207
+ _save_config(name, data)
208
+
209
+
210
+ def load_config(name="settings.toml"):
211
+ """Read a configuration file.
212
+
213
+ Parameters
214
+ ----------
215
+ name : str, optional
216
+ The name of the config file to read, by default "settings.toml"
217
+
218
+ Returns
219
+ -------
220
+ DotDict or str
221
+ Return DotDict if it is a dictionary, otherwise the raw data
222
+ """
223
+ with CONFIG_LOCK:
224
+ return _load_config(name)
225
+
226
+
227
+ def check_config_mode(name="settings.toml"):
228
+ """Check that a configuration file is secure.
229
+
230
+ Parameters
231
+ ----------
232
+ name : str, optional
233
+ The name of the configuration file, by default "settings.toml"
234
+
235
+ Raises
236
+ ------
237
+ SystemError
238
+ If the configuration file is not secure.
239
+ """
240
+ with CONFIG_LOCK:
241
+ if name in CHECKED:
242
+ return
243
+ conf = config_path(name)
244
+ mode = os.stat(conf).st_mode
245
+ if mode & 0o777 != 0o600:
246
+ raise SystemError(f"Configuration file {conf} is not secure. " "Please run `chmod 600 {conf}`.")
247
+ CHECKED[name] = True
@@ -146,6 +146,14 @@ class Upload(Transfer):
146
146
  return os.path.getsize(local_path)
147
147
 
148
148
  def transfer_file(self, source, target, overwrite, resume, verbosity, config=None):
149
+ try:
150
+ return self._transfer_file(source, target, overwrite, resume, verbosity, config=config)
151
+ except Exception as e:
152
+ LOGGER.exception(f"Error transferring {source} to {target}")
153
+ LOGGER.error(e)
154
+ raise
155
+
156
+ def _transfer_file(self, source, target, overwrite, resume, verbosity, config=None):
149
157
 
150
158
  from botocore.exceptions import ClientError
151
159
 
@@ -208,6 +216,14 @@ class Download(Transfer):
208
216
  return s3_object["Size"]
209
217
 
210
218
  def transfer_file(self, source, target, overwrite, resume, verbosity, config=None):
219
+ try:
220
+ return self._transfer_file(source, target, overwrite, resume, verbosity, config=config)
221
+ except Exception as e:
222
+ LOGGER.exception(f"Error transferring {source} to {target}")
223
+ LOGGER.error(e)
224
+ raise
225
+
226
+ def _transfer_file(self, source, target, overwrite, resume, verbosity, config=None):
211
227
  # from boto3.s3.transfer import TransferConfig
212
228
 
213
229
  _, _, bucket, key = source.split("/", 3)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: anemoi-utils
3
- Version: 0.3.6
3
+ Version: 0.3.7
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
@@ -36,7 +36,7 @@ src/anemoi/utils/s3.py
36
36
  src/anemoi/utils/text.py
37
37
  src/anemoi/utils/timer.py
38
38
  src/anemoi/utils/commands/__init__.py
39
- src/anemoi/utils/commands/checkpoint.py
39
+ src/anemoi/utils/commands/config.py
40
40
  src/anemoi/utils/mars/__init__.py
41
41
  src/anemoi/utils/mars/mars.yaml
42
42
  src/anemoi_utils.egg-info/PKG-INFO
@@ -1,61 +0,0 @@
1
- #!/usr/bin/env python
2
- # (C) Copyright 2024 ECMWF.
3
- #
4
- # This software is licensed under the terms of the Apache Licence Version 2.0
5
- # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
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
-
12
- import json
13
-
14
- from . import Command
15
-
16
-
17
- def visit(x, path, name, value):
18
- if isinstance(x, dict):
19
- for k, v in x.items():
20
- if k == name:
21
- print(".".join(path), k, v)
22
-
23
- if v == value:
24
- print(".".join(path), k, v)
25
-
26
- path.append(k)
27
- visit(v, path, name, value)
28
- path.pop()
29
-
30
- if isinstance(x, list):
31
- for i, v in enumerate(x):
32
- path.append(str(i))
33
- visit(v, path, name, value)
34
- path.pop()
35
-
36
-
37
- class Checkpoint(Command):
38
-
39
- def add_arguments(self, command_parser):
40
- command_parser.add_argument("path", help="Path to the checkpoint.")
41
- command_parser.add_argument("--name", help="Search for a specific name.")
42
- command_parser.add_argument("--value", help="Search for a specific value.")
43
-
44
- def run(self, args):
45
- from anemoi.utils.checkpoints import load_metadata
46
-
47
- checkpoint = load_metadata(args.path, "*.json")
48
-
49
- if args.name or args.value:
50
- visit(
51
- checkpoint,
52
- [],
53
- args.name if args.name is not None else object(),
54
- args.value if args.value is not None else object(),
55
- )
56
- return
57
-
58
- print(json.dumps(checkpoint, sort_keys=True, indent=4))
59
-
60
-
61
- command = Checkpoint
@@ -1,142 +0,0 @@
1
- # (C) Copyright 2024 European Centre for Medium-Range Weather Forecasts.
2
- # This software is licensed under the terms of the Apache Licence Version 2.0
3
- # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
4
- # In applying this licence, ECMWF does not waive the privileges and immunities
5
- # granted to it by virtue of its status as an intergovernmental organisation
6
- # nor does it submit to any jurisdiction.
7
-
8
-
9
- import json
10
- import logging
11
- import os
12
- import threading
13
-
14
- import yaml
15
-
16
- try:
17
- import tomllib # Only available since 3.11
18
- except ImportError:
19
- import tomli as tomllib
20
-
21
-
22
- LOG = logging.getLogger(__name__)
23
-
24
-
25
- class DotDict(dict):
26
- """A dictionary that allows access to its keys as attributes.
27
-
28
- >>> d = DotDict({"a": 1, "b": {"c": 2}})
29
- >>> d.a
30
- 1
31
- >>> d.b.c
32
- 2
33
- >>> d.b = 3
34
- >>> d.b
35
- 3
36
-
37
- The class is recursive, so nested dictionaries are also DotDicts.
38
-
39
- The DotDict class has the same constructor as the dict class.
40
-
41
- >>> d = DotDict(a=1, b=2)
42
-
43
- """
44
-
45
- def __init__(self, *args, **kwargs):
46
- super().__init__(*args, **kwargs)
47
-
48
- for k, v in self.items():
49
- if isinstance(v, dict):
50
- self[k] = DotDict(v)
51
-
52
- if isinstance(v, list):
53
- self[k] = [DotDict(i) if isinstance(i, dict) else i for i in v]
54
-
55
- if isinstance(v, tuple):
56
- self[k] = [DotDict(i) if isinstance(i, dict) else i for i in v]
57
-
58
- @classmethod
59
- def from_file(cls, path: str):
60
- _, ext = os.path.splitext(path)
61
- if ext == ".yaml" or ext == ".yml":
62
- return cls.from_yaml_file(path)
63
- elif ext == ".json":
64
- return cls.from_json_file(path)
65
- elif ext == ".toml":
66
- return cls.from_toml_file(path)
67
- else:
68
- raise ValueError(f"Unknown file extension {ext}")
69
-
70
- @classmethod
71
- def from_yaml_file(cls, path: str):
72
- with open(path, "r") as file:
73
- data = yaml.safe_load(file)
74
-
75
- return cls(data)
76
-
77
- @classmethod
78
- def from_json_file(cls, path: str):
79
- with open(path, "r") as file:
80
- data = json.load(file)
81
-
82
- return cls(data)
83
-
84
- @classmethod
85
- def from_toml_file(cls, path: str):
86
- with open(path, "r") as file:
87
- data = tomllib.load(file)
88
- return cls(data)
89
-
90
- def __getattr__(self, attr):
91
- try:
92
- return self[attr]
93
- except KeyError:
94
- raise AttributeError(attr)
95
-
96
- def __setattr__(self, attr, value):
97
- if isinstance(value, dict):
98
- value = DotDict(value)
99
- self[attr] = value
100
-
101
- def __repr__(self) -> str:
102
- return f"DotDict({super().__repr__()})"
103
-
104
-
105
- CONFIG = None
106
- CONFIG_LOCK = threading.Lock()
107
-
108
-
109
- def _load_config():
110
- global CONFIG
111
- if CONFIG is not None:
112
- return CONFIG
113
-
114
- conf = os.path.expanduser("~/.anemoi.toml")
115
-
116
- if os.path.exists(conf):
117
-
118
- with open(conf, "rb") as f:
119
- CONFIG = tomllib.load(f)
120
- else:
121
- CONFIG = {}
122
-
123
- return DotDict(CONFIG)
124
-
125
-
126
- def load_config():
127
- """Load the configuration from `~/.anemoi.toml`.
128
-
129
- Returns
130
- -------
131
- DotDict
132
- The configuration
133
- """
134
- with CONFIG_LOCK:
135
- return _load_config()
136
-
137
-
138
- def check_config_mode():
139
- conf = os.path.expanduser("~/.anemoi.toml")
140
- mode = os.stat(conf).st_mode
141
- if mode & 0o777 != 0o600:
142
- raise SystemError(f"Configuration file {conf} is not secure. " "Please run `chmod 600 ~/.anemoi.toml`.")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes