anemoi-utils 0.4.28__py3-none-any.whl → 0.4.29__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.
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.28'
21
- __version_tuple__ = version_tuple = (0, 4, 28)
20
+ __version__ = version = '0.4.29'
21
+ __version_tuple__ = version_tuple = (0, 4, 29)
anemoi/utils/dates.py CHANGED
@@ -199,6 +199,15 @@ def as_timedelta(frequency: Union[int, str, datetime.timedelta]) -> datetime.tim
199
199
  except ValueError:
200
200
  pass
201
201
 
202
+ if frequency.startswith(" ") or frequency.startswith(" "):
203
+ frequency = frequency.strip()
204
+
205
+ if frequency.startswith("-"):
206
+ return -as_timedelta(frequency[1:])
207
+
208
+ if frequency.startswith("+"):
209
+ return as_timedelta(frequency[1:])
210
+
202
211
  if re.match(r"^\d+[hdms]$", frequency, re.IGNORECASE):
203
212
  unit = frequency[-1].lower()
204
213
  v = int(frequency[:-1])
@@ -261,6 +270,8 @@ def frequency_to_string(frequency: datetime.timedelta) -> str:
261
270
  frequency = frequency_to_timedelta(frequency)
262
271
 
263
272
  total_seconds = frequency.total_seconds()
273
+ if total_seconds < 0:
274
+ return f"-{frequency_to_string(-frequency)}"
264
275
  assert int(total_seconds) == total_seconds, total_seconds
265
276
  total_seconds = int(total_seconds)
266
277
 
@@ -22,6 +22,7 @@ from typing import TYPE_CHECKING
22
22
  import requests
23
23
  from requests.exceptions import HTTPError
24
24
 
25
+ from ..config import config_path
25
26
  from ..config import load_config
26
27
  from ..config import save_config
27
28
  from ..remote import robust
@@ -87,6 +88,14 @@ class TokenAuth:
87
88
 
88
89
  @staticmethod
89
90
  def load_config() -> dict:
91
+ path = config_path(TokenAuth.config_file)
92
+
93
+ if not os.path.exists(path):
94
+ save_config(TokenAuth.config_file, {})
95
+
96
+ if os.path.exists(path) and os.stat(path).st_mode & 0o777 != 0o600:
97
+ os.chmod(path, 0o600)
98
+
90
99
  return load_config(TokenAuth.config_file)
91
100
 
92
101
  def enabled(fn: Callable) -> Callable: # noqa: N805
@@ -8,9 +8,7 @@
8
8
  # nor does it submit to any jurisdiction.
9
9
  from __future__ import annotations
10
10
 
11
- import functools
12
11
  import os
13
- from typing import Any
14
12
 
15
13
  import requests
16
14
 
@@ -44,116 +42,3 @@ def health_check(tracking_uri: str) -> None:
44
42
  if not token:
45
43
  error_msg += "The server may require authentication, did you forget to turn it on?"
46
44
  raise ConnectionError(error_msg)
47
-
48
-
49
- def expand_iterables(
50
- params: dict[str, Any],
51
- *,
52
- size_threshold: int | None = None,
53
- recursive: bool = True,
54
- delimiter: str = ".",
55
- ) -> dict[str, Any]:
56
- """Expand any iterable values to the form {key.i: value_i}.
57
-
58
- If expanded will also add {key.all: [value_0, value_1, ...], key.length: len([value_0, value_1, ...])}.
59
-
60
- If `size_threshold` is not None, expand the iterable only if the length of str(value) is
61
- greater than `size_threshold`.
62
-
63
- Parameters
64
- ----------
65
- params : dict[str, Any]
66
- Parameters to be expanded.
67
- size_threshold : int | None, optional
68
- Threshold of str(value) to expand iterable at.
69
- Default is None.
70
- recursive : bool, optional
71
- Expand nested dictionaries.
72
- Default is True.
73
- delimiter: str, optional
74
- Delimiter to use for keys.
75
- Default is ".".
76
-
77
- Returns
78
- -------
79
- dict[str, Any]
80
- Dictionary with all iterable values expanded.
81
-
82
- Examples
83
- --------
84
- >>> expand_iterables({'a': ['a', 'b', 'c']})
85
- {'a.0': 'a', 'a.1': 'b', 'a.2': 'c', 'a.all': ['a', 'b', 'c'], 'a.length': 3}
86
- >>> expand_iterables({'a': {'b': ['a', 'b', 'c']}})
87
- {'a': {'b.0': 'a', 'b.1': 'b', 'b.2': 'c', 'b.all': ['a', 'b', 'c'], 'b.length': 3}}
88
- >>> expand_iterables({'a': ['a', 'b', 'c']}, size_threshold=100)
89
- {'a': ['a', 'b', 'c']}
90
- >>> expand_iterables({'a': [[0,1,2], 'b', 'c']})
91
- {'a.0': {0: 0, 1: 1, 2: 2}, 'a.1': 'b', 'a.2': 'c', 'a.all': [[0, 1, 2], 'b', 'c'], 'a.length': 3}
92
- """
93
-
94
- def should_be_expanded(x: Any) -> bool:
95
- return size_threshold is None or len(str(x)) > size_threshold
96
-
97
- nested_func = functools.partial(expand_iterables, size_threshold=size_threshold, recursive=recursive)
98
-
99
- def expand(val: dict | list) -> dict[str, Any]:
100
- if not recursive:
101
- return val
102
- if isinstance(val, dict):
103
- return nested_func(val)
104
- if isinstance(val, list):
105
- return nested_func(dict(enumerate(val)))
106
- return val
107
-
108
- expanded_params = {}
109
-
110
- for key, value in params.items():
111
- if isinstance(value, (list, tuple)):
112
- if should_be_expanded(value):
113
- for i, v in enumerate(value):
114
- expanded_params[f"{key}{delimiter}{i}"] = expand(v)
115
-
116
- expanded_params[f"{key}{delimiter}all"] = value
117
- expanded_params[f"{key}{delimiter}length"] = len(value)
118
- else:
119
- expanded_params[key] = value
120
- else:
121
- expanded_params[key] = expand(value)
122
- return expanded_params
123
-
124
-
125
- def clean_config_params(params: dict[str, Any]) -> dict[str, Any]:
126
- """Clean up params to avoid issues with mlflow.
127
-
128
- Too many logged params will make the server take longer to render the
129
- experiment.
130
-
131
- Parameters
132
- ----------
133
- params : dict[str, Any]
134
- Parameters to clean up.
135
-
136
- Returns
137
- -------
138
- dict[str, Any]
139
- Cleaned up params ready for MlFlow.
140
- """
141
- prefixes_to_remove = [
142
- "hardware",
143
- "data",
144
- "dataloader",
145
- "model",
146
- "training",
147
- "diagnostics",
148
- "graph",
149
- "metadata.config",
150
- "config.dataset.sourcesmetadata.dataset.variables_metadata",
151
- "metadata.dataset.sources",
152
- "metadata.dataset.specific",
153
- "metadata.dataset.variables_metadata",
154
- ]
155
-
156
- keys_to_remove = [key for key in params if any(key.startswith(prefix) for prefix in prefixes_to_remove)]
157
- for key in keys_to_remove:
158
- del params[key]
159
- return params
anemoi/utils/sanitise.py CHANGED
@@ -17,19 +17,24 @@ from urllib.parse import urlencode
17
17
  from urllib.parse import urlparse
18
18
  from urllib.parse import urlunparse
19
19
 
20
- # Patterns used but earthkit-data for url-patterns and path-patterns
20
+ # Patterns used by earthkit-data for url-patterns and path-patterns
21
21
 
22
- RE1 = re.compile(r"{([^}]*)}")
23
- RE2 = re.compile(r"\(([^}]*)\)")
22
+ RE1 = re.compile(r"{([^}]*)}") # {*}
23
+ RE2 = re.compile(r"\(([^}]*)\)") # (*)
24
24
 
25
25
 
26
- def sanitise(obj: Any) -> Any:
27
- """Sanitise an object by replacing all full paths with shortened versions and URL passwords with '***'.
26
+ def sanitise(obj: Any, level=1) -> Any:
27
+ """Sanitise an object by replacing all full paths with shortened versions and URL credentials with '***'.
28
28
 
29
29
  Parameters
30
30
  ----------
31
31
  obj : Any
32
32
  The object to sanitise.
33
+ level : int, optional
34
+ The level of sanitation. The higher levels will also apply the levels below it.
35
+ - 1: Shorten file paths to file name and hide credentials in URLs (default).
36
+ - 2: Hide hostnames in URLs.
37
+ - 3: Hide full file paths and URLs.
33
38
 
34
39
  Returns
35
40
  -------
@@ -37,6 +42,8 @@ def sanitise(obj: Any) -> Any:
37
42
  The sanitised object.
38
43
  """
39
44
 
45
+ assert level in (1, 2, 3), "level must be 1, 2 or 3"
46
+
40
47
  if isinstance(obj, dict):
41
48
  return {sanitise(k): sanitise(v) for k, v in obj.items()}
42
49
 
@@ -47,29 +54,21 @@ def sanitise(obj: Any) -> Any:
47
54
  return tuple(sanitise(v) for v in obj)
48
55
 
49
56
  if isinstance(obj, str):
50
- return _sanitise_string(obj)
57
+ return _sanitise_string(obj, level)
51
58
 
52
59
  return obj
53
60
 
54
61
 
55
- def _sanitise_string(obj: str) -> str:
56
- """Sanitise a string by replacing full paths and URL passwords.
57
-
58
- Parameters
59
- ----------
60
- obj : str
61
- The string to sanitise.
62
-
63
- Returns
64
- -------
65
- str
66
- The sanitised string.
67
- """
62
+ def _sanitise_string(obj: str, level=1) -> str:
63
+ """Sanitise a string by replacing full paths and URL passwords."""
68
64
 
69
65
  parsed = urlparse(obj, allow_fragments=True)
70
66
 
71
67
  if parsed.scheme and parsed.scheme[0].isalpha():
72
- return _sanitise_url(parsed)
68
+ return _sanitise_url(parsed, level)
69
+
70
+ if level > 2:
71
+ return "hidden"
73
72
 
74
73
  if obj.startswith("/") or obj.startswith("~"):
75
74
  return _sanitise_path(obj)
@@ -77,19 +76,8 @@ def _sanitise_string(obj: str) -> str:
77
76
  return obj
78
77
 
79
78
 
80
- def _sanitise_url(parsed: Any) -> str:
81
- """Sanitise a URL by replacing passwords with '***'.
82
-
83
- Parameters
84
- ----------
85
- parsed : Any
86
- The parsed URL.
87
-
88
- Returns
89
- -------
90
- str
91
- The sanitised URL.
92
- """
79
+ def _sanitise_url(parsed: Any, level=1) -> str:
80
+ """Sanitise a URL by replacing passwords with '***'."""
93
81
 
94
82
  LIST = [
95
83
  "pass",
@@ -107,6 +95,9 @@ def _sanitise_url(parsed: Any) -> str:
107
95
  "_api_key",
108
96
  "username",
109
97
  "login",
98
+ "auth",
99
+ "auth_token",
100
+ "auth_key",
110
101
  ]
111
102
 
112
103
  scheme, netloc, path, params, query, fragment = parsed
@@ -130,26 +121,25 @@ def _sanitise_url(parsed: Any) -> str:
130
121
  qs[k] = "hidden"
131
122
  params = urlencode(qs, doseq=True)
132
123
 
133
- return urlunparse([scheme, netloc, path, params, query, fragment])
124
+ if level > 1:
125
+ if (bits := netloc.split("@")) and len(bits) > 1:
126
+ netloc = f"{bits[0]}@hidden"
127
+ else:
128
+ netloc = "hidden"
134
129
 
130
+ if level > 2:
131
+ return urlunparse([scheme, netloc, "", "", "", ""])
135
132
 
136
- def _sanitise_path(path: str) -> str:
137
- """Sanitise a file path by shortening it.
133
+ return urlunparse([scheme, netloc, path, params, query, fragment])
138
134
 
139
- Parameters
140
- ----------
141
- path : str
142
- The file path to sanitise.
143
135
 
144
- Returns
145
- -------
146
- str
147
- The sanitised file path.
148
- """
136
+ def _sanitise_path(path: str) -> str:
137
+ """Sanitise a file path by shortening it."""
149
138
  bits = list(reversed(Path(path).parts))
150
139
  result = [bits.pop(0)]
151
140
  for bit in bits:
152
141
  if RE1.match(bit) or RE2.match(bit):
142
+ # keep earthkit-data folder patterns
153
143
  result.append(bit)
154
144
  continue
155
145
  if result[-1] == "...":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anemoi-utils
3
- Version: 0.4.28
3
+ Version: 0.4.29
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
@@ -251,7 +251,7 @@ Requires-Dist: termcolor; extra == "docs"
251
251
  Provides-Extra: grib
252
252
  Requires-Dist: requests; extra == "grib"
253
253
  Provides-Extra: mlflow
254
- Requires-Dist: mlflow>=2.11.1; extra == "mlflow"
254
+ Requires-Dist: mlflow-skinny>=2.11.1; extra == "mlflow"
255
255
  Requires-Dist: requests; extra == "mlflow"
256
256
  Provides-Extra: provenance
257
257
  Requires-Dist: gitpython; extra == "provenance"
@@ -259,6 +259,7 @@ Requires-Dist: nvsmi; extra == "provenance"
259
259
  Provides-Extra: s3
260
260
  Requires-Dist: boto3>1.36; extra == "s3"
261
261
  Provides-Extra: tests
262
+ Requires-Dist: anemoi-utils[mlflow]; extra == "tests"
262
263
  Requires-Dist: pytest; extra == "tests"
263
264
  Requires-Dist: pytest-mock>=3; extra == "tests"
264
265
  Provides-Extra: text
@@ -1,12 +1,12 @@
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=B7w9p3e_jRrhCAcRVz7MulIQ9D0PoLvF57YvgUIilfM,513
3
+ anemoi/utils/_version.py,sha256=M33mpOYpqw35qLoEcg5GxMG4YQ25eohx8wEQkM6FTT0,513
4
4
  anemoi/utils/caching.py,sha256=rXbeAmpBcMbbfN4EVblaHWKicsrtx1otER84FEBtz98,6183
5
5
  anemoi/utils/checkpoints.py,sha256=PydBqA-wI8_05zU-3yT-ZP53GnvuqB7vPXl-w6-9XX8,9541
6
6
  anemoi/utils/cli.py,sha256=Cd3ESQkNWecbGnJjkR_SKHdFPETJWFrHqg5ovtANDKs,6522
7
7
  anemoi/utils/compatibility.py,sha256=wRBRMmxQP88rNcWiP5gqXliwYQbBv1iCAsDjcCRi5UY,2234
8
8
  anemoi/utils/config.py,sha256=EEfcSxW2CD6fFOzDtqz_uYlMKuYq4X5QinJW_8GBYj4,17325
9
- anemoi/utils/dates.py,sha256=CnY6JOdpk0T-waPEowMRTkcMzxcN0GcjPVtLkwH_byw,17196
9
+ anemoi/utils/dates.py,sha256=Ew11G-Upl0VM9R9Sgg3Be6KIkXfpMCD69kgiao7Xv48,17534
10
10
  anemoi/utils/devtools.py,sha256=W3OBu96MkXRIl7Qh1SE5Zd6aB1R0QlnmlrlpBYM0fVY,3527
11
11
  anemoi/utils/grib.py,sha256=201WcxjjAl92Y2HX2kZ2S8Qr5dN-oG7nV-vQLaybzP4,3610
12
12
  anemoi/utils/grids.py,sha256=VlhuN8MZDBu-G50QFI1XHV9IXLway2LpvI9X8sDjb5s,6047
@@ -17,7 +17,7 @@ anemoi/utils/provenance.py,sha256=iTsn4r-VPq2D8tSHPSuAIqG077_opkqMT42G03DRWJg,14
17
17
  anemoi/utils/registry.py,sha256=e3nOIRyMYQ-mpEvaHAv5tuvMYNbkJ5yz94ns7BnvkjM,9717
18
18
  anemoi/utils/rules.py,sha256=VspUoPmw7tijrs6l_wl4vDjr_zVQsFjx9ITiBSvxgc8,6972
19
19
  anemoi/utils/s3.py,sha256=xMT48kbcelcjjqsaU567WI3oZ5eqo88Rlgyx5ECszAU,4074
20
- anemoi/utils/sanitise.py,sha256=ZYGdSX6qihQANr3pHZjbKnoapnzP1KcrWdW1Ul1mOGk,3668
20
+ anemoi/utils/sanitise.py,sha256=XkQzibDbu-VFJkJC4WcB9ovkcTkVAynXtkn1Tlc2CC4,4019
21
21
  anemoi/utils/sanitize.py,sha256=43ZKDcfVpeXSsJ9TFEc9aZnD6oe2cUh151XnDspM98M,462
22
22
  anemoi/utils/testing.py,sha256=DeTAkmg-RCMPXBBRUy_Gti5UJriUPRKT6ruE3JL2GVc,10372
23
23
  anemoi/utils/text.py,sha256=HkzIvi24obDceFLpJEwBJ9PmPrJUkQN2TrElJ-A87gU,14441
@@ -31,17 +31,17 @@ anemoi/utils/mars/__init__.py,sha256=b-Lc3L1TAQd9ODs0Z1YSJzgZCO1K_M3DSgx_yd2qXvM
31
31
  anemoi/utils/mars/mars.yaml,sha256=R0dujp75lLA4wCWhPeOQnzJ45WZAYLT8gpx509cBFlc,66
32
32
  anemoi/utils/mars/requests.py,sha256=VFMHBVAAl0_2lOcMBa1lvaKHctN0lDJsI6_U4BucGew,1142
33
33
  anemoi/utils/mlflow/__init__.py,sha256=hCW0QcLHJmE-C1r38P27_ZOvCLNewex5iQEtZqx2ckI,393
34
- anemoi/utils/mlflow/auth.py,sha256=hMuS3vrX4VDfEFiIlRM_7U6XHvYRGU149doi4jlgsW8,8092
34
+ anemoi/utils/mlflow/auth.py,sha256=Zgn2Ru7GE99_ZTBeskl5M1WnrlEGNU9XxsRVgVoUwmM,8376
35
35
  anemoi/utils/mlflow/client.py,sha256=Y34ceLcp1-H0XTt8h8-IhHKX9bApc_QJcgVrzZKtabY,2752
36
- anemoi/utils/mlflow/utils.py,sha256=2zwNqkS69TXKz-vUy8cdGckJA7jjtjbUZ6P7cj9U8OM,4940
36
+ anemoi/utils/mlflow/utils.py,sha256=0d-dp7YmDLJ59ikJWUcZooqBIdrNDxPShDQubcX-a3k,1310
37
37
  anemoi/utils/remote/__init__.py,sha256=7nHu-LRspYW2Fx9GNLjsxpytAUIvhIbOjb0Xmxb-33s,20988
38
38
  anemoi/utils/remote/s3.py,sha256=DxO_TjmetX_r3ZvGaHjpz40oqvcQYP3Vd_A4ojMGlSA,21379
39
39
  anemoi/utils/remote/ssh.py,sha256=xNtsawh8okytCKRehkRCVExbHZj-CRUQNormEHglfuw,8088
40
40
  anemoi/utils/schemas/__init__.py,sha256=nkinKlsPLPXEjfTYQT1mpKC4cvs-14w_zBkDRxakwxw,698
41
41
  anemoi/utils/schemas/errors.py,sha256=lgOXzVTYzAE0qWQf3OZ42vCWixv8lilSqLLhzARBmvI,1831
42
- anemoi_utils-0.4.28.dist-info/licenses/LICENSE,sha256=8HznKF1Vi2IvfLsKNE5A2iVyiri3pRjRPvPC9kxs6qk,11354
43
- anemoi_utils-0.4.28.dist-info/METADATA,sha256=CxRjsdH4Audnqnun7v-FSgCNxxSVFtwIuSQqgnoIHUk,15656
44
- anemoi_utils-0.4.28.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
45
- anemoi_utils-0.4.28.dist-info/entry_points.txt,sha256=LENOkn88xzFQo-V59AKoA_F_cfYQTJYtrNTtf37YgHY,60
46
- anemoi_utils-0.4.28.dist-info/top_level.txt,sha256=DYn8VPs-fNwr7fNH9XIBqeXIwiYYd2E2k5-dUFFqUz0,7
47
- anemoi_utils-0.4.28.dist-info/RECORD,,
42
+ anemoi_utils-0.4.29.dist-info/licenses/LICENSE,sha256=8HznKF1Vi2IvfLsKNE5A2iVyiri3pRjRPvPC9kxs6qk,11354
43
+ anemoi_utils-0.4.29.dist-info/METADATA,sha256=v5sHgt4rZA9mZ2YW1pMO0S76mlvZ09hoB3Q-G7xp6-Y,15717
44
+ anemoi_utils-0.4.29.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
45
+ anemoi_utils-0.4.29.dist-info/entry_points.txt,sha256=LENOkn88xzFQo-V59AKoA_F_cfYQTJYtrNTtf37YgHY,60
46
+ anemoi_utils-0.4.29.dist-info/top_level.txt,sha256=DYn8VPs-fNwr7fNH9XIBqeXIwiYYd2E2k5-dUFFqUz0,7
47
+ anemoi_utils-0.4.29.dist-info/RECORD,,