anemoi-utils 0.4.16__py3-none-any.whl → 0.4.18__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.16'
21
- __version_tuple__ = version_tuple = (0, 4, 16)
20
+ __version__ = version = '0.4.18'
21
+ __version_tuple__ = version_tuple = (0, 4, 18)
anemoi/utils/config.py CHANGED
@@ -459,6 +459,24 @@ def _load_config(
459
459
  secret_config = _load_config(secret_name)
460
460
  _merge_dicts(config, secret_config)
461
461
 
462
+ for env, value in os.environ.items():
463
+
464
+ if not env.startswith("ANEMOI_CONFIG_"):
465
+ continue
466
+ rest = env[len("ANEMOI_CONFIG_") :]
467
+
468
+ package = rest.split("_")[0]
469
+ sub = rest[len(package) + 1 :]
470
+
471
+ package = package.lower()
472
+ sub = sub.lower()
473
+
474
+ LOG.info(f"Using environment variable {env} to override the anemoi config key '{package}.{sub}'")
475
+
476
+ if package not in config:
477
+ config[package] = {}
478
+ config[package][sub] = value
479
+
462
480
  CONFIG[key] = DotDict(config)
463
481
  return CONFIG[key]
464
482
 
@@ -199,12 +199,12 @@ def _module_versions(full: bool) -> Tuple[Dict[str, Any], set]:
199
199
 
200
200
  versions = {}
201
201
  namespaces = set()
202
- for k, v in sorted(sys.modules.items()):
202
+ for k, v in sorted(sys.modules.copy().items()):
203
203
  if "." not in k:
204
204
  version(versions, k, v, roots, namespaces, paths, full)
205
205
 
206
206
  # Catter for modules like "earthkit.meteo"
207
- for k, v in sorted(sys.modules.items()):
207
+ for k, v in sorted(sys.modules.copy().items()):
208
208
  bits = k.split(".")
209
209
  if len(bits) == 2 and bits[0] in namespaces:
210
210
  version(versions, k, v, roots, namespaces, paths, full)
anemoi/utils/registry.py CHANGED
@@ -162,12 +162,16 @@ class Registry:
162
162
  file : str
163
163
  The file to load.
164
164
  """
165
+
165
166
  name, _ = os.path.splitext(file)
167
+
166
168
  try:
167
169
  importlib.import_module(f".{name}", package=self.package)
168
170
  except Exception as e:
169
171
  if DEBUG_ANEMOI_REGISTRY:
170
172
  raise
173
+
174
+ name = name.replace("_", "-")
171
175
  self.__registered[name] = Error(e)
172
176
 
173
177
  def is_registered(self, name: str) -> bool:
@@ -627,7 +627,7 @@ def _find_transfer_class(source: str, target: str) -> type:
627
627
  raise TransferMethodNotImplementedError(f"Transfer from {source} to {target} is not implemented")
628
628
 
629
629
 
630
- # this is the public API
630
+ # This function is the main entry point for the transfer mechanism for the other anemoi packages
631
631
  def transfer(
632
632
  source, target, *, overwrite=False, resume=False, verbosity=1, progress=None, threads=1, temporary_target=False
633
633
  ) -> Loader:
anemoi/utils/rules.py ADDED
@@ -0,0 +1,218 @@
1
+ # (C) Copyright 2025 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
+ from typing import Any
11
+ from typing import Dict
12
+ from typing import List
13
+ from typing import Mapping
14
+ from typing import Optional
15
+ from typing import Union
16
+
17
+
18
+ class Rule:
19
+
20
+ def __init__(self, match: Dict[str, Any], result: Any):
21
+ """Initialize a Rule object.
22
+
23
+ Parameters
24
+ ----------
25
+ match : Dict[str, Any]
26
+ A dictionary defining the conditions for the rule to match.
27
+ result : Any
28
+ The result to return if the rule matches.
29
+ """
30
+ self._match = match
31
+ self._result = result
32
+
33
+ def match(self, obj: Mapping[str, Any]) -> bool:
34
+ """Check if the rule matches the given object.
35
+
36
+ Parameters
37
+ ----------
38
+ obj : Mapping[str, Any]
39
+ The object to check against the rule's conditions.
40
+
41
+ Returns
42
+ -------
43
+ bool
44
+ True if the rule matches, False otherwise.
45
+ """
46
+ for key, value in self._match.items():
47
+ if key not in obj or obj[key] != value:
48
+ return False
49
+
50
+ return True
51
+
52
+ @property
53
+ def result(self) -> Any:
54
+ return self._result
55
+
56
+ @property
57
+ def condition(self) -> Dict[str, Any]:
58
+ return self._match
59
+
60
+
61
+ class RuleSet:
62
+
63
+ def __init__(self, rules: List[Union[Rule, Dict[str, Any], List[Any]]]):
64
+ """Initialize a RuleSet object.
65
+
66
+ Parameters
67
+ ----------
68
+ rules : List[Union[Rule, Dict[str, Any], List[Any]]]
69
+ A list of rules, where each rule can be a Rule object, a dictionary with
70
+ 'match' and 'result' keys, or a list with two elements (match and result).
71
+ """
72
+ assert isinstance(rules, list), "rules must be a list"
73
+
74
+ self.rules: List[Rule] = []
75
+
76
+ for rule in rules:
77
+ if isinstance(rule, Rule):
78
+ self.rules.append(rule)
79
+ continue
80
+
81
+ if isinstance(rule, dict):
82
+
83
+ assert len(rule) == 2, "Rule dictionary must contain exactly two key-value pair."
84
+
85
+ match = rule.get("match")
86
+ if match is None:
87
+ raise ValueError("Rule dictionary must contain a 'match' key.")
88
+
89
+ result = rule.get("result")
90
+ if result is None:
91
+ raise ValueError("Rule dictionary must contain a 'result' key.")
92
+
93
+ self.rules.append(Rule(match, result))
94
+ continue
95
+
96
+ if isinstance(rule, list):
97
+ assert len(rule) == 2, "Rule list must contain exactly two elements."
98
+ match = rule[0]
99
+ result = rule[1]
100
+ self.rules.append(Rule(match, result))
101
+ continue
102
+
103
+ raise ValueError(
104
+ "Rule must be either a Rule object, a dictionary with 'match' and 'result' keys, or a list with two elements."
105
+ )
106
+
107
+ @classmethod
108
+ def from_list(cls, rules: List[Any]) -> "RuleSet":
109
+ """Create a RuleSet from a list of rules.
110
+
111
+ Parameters
112
+ ----------
113
+ rules : List[Any]
114
+ A list of rules to initialize the RuleSet.
115
+
116
+ Returns
117
+ -------
118
+ RuleSet
119
+ A new RuleSet object.
120
+ """
121
+ return cls(rules)
122
+
123
+ @classmethod
124
+ def from_files(cls, path: str) -> "RuleSet":
125
+ """Create a RuleSet from a file.
126
+
127
+ Parameters
128
+ ----------
129
+ path : str
130
+ The path to the file containing the rules. Supported formats are .json and .yaml/.yml.
131
+
132
+ Returns
133
+ -------
134
+ RuleSet
135
+ A new RuleSet object.
136
+
137
+ Raises
138
+ ------
139
+ ValueError
140
+ If the file format is unsupported.
141
+ """
142
+ if path.endswith(".json"):
143
+ import json
144
+
145
+ with open(path, "r") as f:
146
+ return cls.from_list(json.load(f))
147
+
148
+ if path.endswith(".yaml") or path.endswith(".yml"):
149
+ import yaml
150
+
151
+ with open(path, "r") as f:
152
+ return cls.from_list(yaml.safe_load(f))
153
+
154
+ raise ValueError("Unsupported file format. Supported formats are .json and .yaml/.yml.")
155
+
156
+ @classmethod
157
+ def from_any(cls, rules: Union[str, List[Any]]) -> "RuleSet":
158
+ """Create a RuleSet from a list or a file path.
159
+
160
+ Parameters
161
+ ----------
162
+ rules : Union[str, List[Any]]
163
+ The rules to initialize the RuleSet, either as a list or a file path.
164
+
165
+ Returns
166
+ -------
167
+ RuleSet
168
+ A new RuleSet object.
169
+
170
+ Raises
171
+ ------
172
+ ValueError
173
+ If the rules format is unsupported.
174
+ """
175
+ if isinstance(rules, str):
176
+ return cls.from_files(rules)
177
+
178
+ if isinstance(rules, list):
179
+ return cls.from_list(rules)
180
+
181
+ raise ValueError("Unsupported rules format. Must be a list or a file path.")
182
+
183
+ def match(self, obj: Mapping[str, Any], strategy: str = "first-match") -> Optional[Rule]:
184
+ """Match an object against the rules in the RuleSet.
185
+
186
+ Parameters
187
+ ----------
188
+ obj : Mapping[str, Any]
189
+ The object to match against the rules.
190
+ strategy : str, optional
191
+ The matching strategy to use. Currently, only 'first-match' is supported.
192
+
193
+ Returns
194
+ -------
195
+ Optional[Rule]
196
+ The first matching rule, or None if no match is found.
197
+
198
+ Raises
199
+ ------
200
+ AssertionError
201
+ If an unsupported strategy is provided.
202
+ """
203
+ assert strategy == "first-match", "Only 'first-match' strategy is supported for now."
204
+ for rule in self.rules:
205
+ if rule.match(obj):
206
+ return rule
207
+
208
+ return None
209
+
210
+ def __iter__(self) -> iter:
211
+ """Return an iterator over the rules in the RuleSet.
212
+
213
+ Returns
214
+ -------
215
+ iter
216
+ An iterator over the Rule objects in the RuleSet.
217
+ """
218
+ return iter(self.rules)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anemoi-utils
3
- Version: 0.4.16
3
+ Version: 0.4.18
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,11 +1,11 @@
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=aZNmGWWE7ndWaFJGD1G8axbqrCNEX3UMJLUyVC4HPEM,513
3
+ anemoi/utils/_version.py,sha256=k9MQ8BJ_knOnasrd-KVQ3E0l9YzHo7DymdQPhaKLLNU,513
4
4
  anemoi/utils/caching.py,sha256=rXbeAmpBcMbbfN4EVblaHWKicsrtx1otER84FEBtz98,6183
5
5
  anemoi/utils/checkpoints.py,sha256=N4WpAZXa4etrpSEKhHqUUtG2-x9w3FJMHcLO-dDAXPY,9600
6
6
  anemoi/utils/cli.py,sha256=IyZfnSw0u0yYnrjOrzvm2RuuKvDk4cVb8pf8BkaChgA,6209
7
7
  anemoi/utils/compatibility.py,sha256=wRBRMmxQP88rNcWiP5gqXliwYQbBv1iCAsDjcCRi5UY,2234
8
- anemoi/utils/config.py,sha256=4K4_ePeuUPyIMsE7-r77OC87ntFGdVLskB0ow9XNzXI,16353
8
+ anemoi/utils/config.py,sha256=UtxlkSMhqZR0LAKi19aG6jaaNiSmsXCzE6v2AWHhx5E,16861
9
9
  anemoi/utils/dates.py,sha256=CnY6JOdpk0T-waPEowMRTkcMzxcN0GcjPVtLkwH_byw,17196
10
10
  anemoi/utils/devtools.py,sha256=W3OBu96MkXRIl7Qh1SE5Zd6aB1R0QlnmlrlpBYM0fVY,3527
11
11
  anemoi/utils/grib.py,sha256=201WcxjjAl92Y2HX2kZ2S8Qr5dN-oG7nV-vQLaybzP4,3610
@@ -13,8 +13,9 @@ anemoi/utils/grids.py,sha256=edTrMK8hpE9ZBzSfwcRftgk0jljNAK3i8CraadILQoM,4427
13
13
  anemoi/utils/hindcasts.py,sha256=iYVIxSNFL2HJcc_k1abCFLkpJFGHT8WKRIR4wcAwA3s,2144
14
14
  anemoi/utils/humanize.py,sha256=pjnFJAKHbEAOfcvn8c48kt-8eFy6FGW_U2ruJvfamrA,25189
15
15
  anemoi/utils/logs.py,sha256=naTgrmPwWHD4eekFttXftS4gtcAGYHpCqG4iwYprNDA,1804
16
- anemoi/utils/provenance.py,sha256=tIIgweS0EJyaYzgKwuv3iWny-Gz7N5e5CNWH5MeSYWU,14615
17
- anemoi/utils/registry.py,sha256=7r14_znm8yr6kv38a7F33MJHM8riRp3tOB_fFF_QQc4,9592
16
+ anemoi/utils/provenance.py,sha256=xC6mTstF7f_asqtPSrulC7c34xjOSuAxWhkwc3yKhHg,14629
17
+ anemoi/utils/registry.py,sha256=vIaHMT66m0_w8lrY3u5GmT70tIRNyA_Z_p_T7pTFx_k,9637
18
+ anemoi/utils/rules.py,sha256=xYCiUV_HXTGFe93diqMLQsMJCWGi5umd_bWEeYP8XFY,6318
18
19
  anemoi/utils/s3.py,sha256=xMT48kbcelcjjqsaU567WI3oZ5eqo88Rlgyx5ECszAU,4074
19
20
  anemoi/utils/sanitise.py,sha256=ZYGdSX6qihQANr3pHZjbKnoapnzP1KcrWdW1Ul1mOGk,3668
20
21
  anemoi/utils/sanitize.py,sha256=43ZKDcfVpeXSsJ9TFEc9aZnD6oe2cUh151XnDspM98M,462
@@ -27,12 +28,12 @@ anemoi/utils/commands/requests.py,sha256=AEbssF1OlpbmSwrV5Lj6amCCn0w_-nbajBWTwYV
27
28
  anemoi/utils/mars/__init__.py,sha256=b-Lc3L1TAQd9ODs0Z1YSJzgZCO1K_M3DSgx_yd2qXvM,2724
28
29
  anemoi/utils/mars/mars.yaml,sha256=R0dujp75lLA4wCWhPeOQnzJ45WZAYLT8gpx509cBFlc,66
29
30
  anemoi/utils/mars/requests.py,sha256=VFMHBVAAl0_2lOcMBa1lvaKHctN0lDJsI6_U4BucGew,1142
30
- anemoi/utils/remote/__init__.py,sha256=-uaYFi4yRYFRf46ubQbJo86GCn6HE5VQrcaoyrmyW28,20704
31
+ anemoi/utils/remote/__init__.py,sha256=swPWHQoh-B6Xq9R489tPw0FykMue7f-bJ8enneFYSYE,20776
31
32
  anemoi/utils/remote/s3.py,sha256=spQ8l0rwQjLZh9dZu5cOsYIvNwKihQfCJ6YsFYegeqI,17339
32
33
  anemoi/utils/remote/ssh.py,sha256=xNtsawh8okytCKRehkRCVExbHZj-CRUQNormEHglfuw,8088
33
- anemoi_utils-0.4.16.dist-info/licenses/LICENSE,sha256=8HznKF1Vi2IvfLsKNE5A2iVyiri3pRjRPvPC9kxs6qk,11354
34
- anemoi_utils-0.4.16.dist-info/METADATA,sha256=36Oea7pKpe5I3NxOuPP1ILtAm5pGZ2xtvMqsRz-UyvM,15360
35
- anemoi_utils-0.4.16.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
36
- anemoi_utils-0.4.16.dist-info/entry_points.txt,sha256=LENOkn88xzFQo-V59AKoA_F_cfYQTJYtrNTtf37YgHY,60
37
- anemoi_utils-0.4.16.dist-info/top_level.txt,sha256=DYn8VPs-fNwr7fNH9XIBqeXIwiYYd2E2k5-dUFFqUz0,7
38
- anemoi_utils-0.4.16.dist-info/RECORD,,
34
+ anemoi_utils-0.4.18.dist-info/licenses/LICENSE,sha256=8HznKF1Vi2IvfLsKNE5A2iVyiri3pRjRPvPC9kxs6qk,11354
35
+ anemoi_utils-0.4.18.dist-info/METADATA,sha256=JfNRSTBhVM7aDRWBmhpDWtrkCoTVT1fUwGYyWcR45BU,15360
36
+ anemoi_utils-0.4.18.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
37
+ anemoi_utils-0.4.18.dist-info/entry_points.txt,sha256=LENOkn88xzFQo-V59AKoA_F_cfYQTJYtrNTtf37YgHY,60
38
+ anemoi_utils-0.4.18.dist-info/top_level.txt,sha256=DYn8VPs-fNwr7fNH9XIBqeXIwiYYd2E2k5-dUFFqUz0,7
39
+ anemoi_utils-0.4.18.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (77.0.3)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5