anemoi-utils 0.4.3__py3-none-any.whl → 0.4.4__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/__init__.py +3 -1
- anemoi/utils/_version.py +2 -2
- anemoi/utils/checkpoints.py +74 -9
- anemoi/utils/cli.py +14 -2
- anemoi/utils/config.py +3 -2
- anemoi/utils/dates.py +7 -2
- anemoi/utils/registry.py +98 -0
- {anemoi_utils-0.4.3.dist-info → anemoi_utils-0.4.4.dist-info}/METADATA +1 -1
- {anemoi_utils-0.4.3.dist-info → anemoi_utils-0.4.4.dist-info}/RECORD +13 -12
- {anemoi_utils-0.4.3.dist-info → anemoi_utils-0.4.4.dist-info}/WHEEL +1 -1
- {anemoi_utils-0.4.3.dist-info → anemoi_utils-0.4.4.dist-info}/LICENSE +0 -0
- {anemoi_utils-0.4.3.dist-info → anemoi_utils-0.4.4.dist-info}/entry_points.txt +0 -0
- {anemoi_utils-0.4.3.dist-info → anemoi_utils-0.4.4.dist-info}/top_level.txt +0 -0
anemoi/utils/__init__.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
# (C) Copyright 2024
|
|
1
|
+
# (C) Copyright 2024 Anemoi contributors.
|
|
2
|
+
#
|
|
2
3
|
# This software is licensed under the terms of the Apache Licence Version 2.0
|
|
3
4
|
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
|
|
5
|
+
#
|
|
4
6
|
# In applying this licence, ECMWF does not waive the privileges and immunities
|
|
5
7
|
# granted to it by virtue of its status as an intergovernmental organisation
|
|
6
8
|
# nor does it submit to any jurisdiction.
|
anemoi/utils/_version.py
CHANGED
anemoi/utils/checkpoints.py
CHANGED
|
@@ -27,7 +27,7 @@ DEFAULT_NAME = "ai-models.json"
|
|
|
27
27
|
DEFAULT_FOLDER = "anemoi-metadata"
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
def has_metadata(path: str, name: str = DEFAULT_NAME) -> bool:
|
|
30
|
+
def has_metadata(path: str, *, name: str = DEFAULT_NAME) -> bool:
|
|
31
31
|
"""Check if a checkpoint file has a metadata file
|
|
32
32
|
|
|
33
33
|
Parameters
|
|
@@ -49,13 +49,26 @@ def has_metadata(path: str, name: str = DEFAULT_NAME) -> bool:
|
|
|
49
49
|
return False
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
def
|
|
52
|
+
def metadata_root(path: str, *, name: str = DEFAULT_NAME) -> bool:
|
|
53
|
+
|
|
54
|
+
with zipfile.ZipFile(path, "r") as f:
|
|
55
|
+
for b in f.namelist():
|
|
56
|
+
if os.path.basename(b) == name:
|
|
57
|
+
return os.path.dirname(b)
|
|
58
|
+
raise ValueError(f"Could not find '{name}' in {path}.")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def load_metadata(path: str, *, supporting_arrays=False, name: str = DEFAULT_NAME) -> dict:
|
|
53
62
|
"""Load metadata from a checkpoint file
|
|
54
63
|
|
|
55
64
|
Parameters
|
|
56
65
|
----------
|
|
57
66
|
path : str
|
|
58
67
|
The path to the checkpoint file
|
|
68
|
+
|
|
69
|
+
supporting_arrays: bool, optional
|
|
70
|
+
If True, the function will return a dictionary with the supporting arrays
|
|
71
|
+
|
|
59
72
|
name : str, optional
|
|
60
73
|
The name of the metadata file in the zip archive
|
|
61
74
|
|
|
@@ -79,12 +92,29 @@ def load_metadata(path: str, name: str = DEFAULT_NAME) -> dict:
|
|
|
79
92
|
|
|
80
93
|
if metadata is not None:
|
|
81
94
|
with zipfile.ZipFile(path, "r") as f:
|
|
82
|
-
|
|
95
|
+
metadata = json.load(f.open(metadata, "r"))
|
|
96
|
+
if supporting_arrays:
|
|
97
|
+
metadata["supporting_arrays"] = load_supporting_arrays(f, metadata.get("supporting_arrays", {}))
|
|
98
|
+
return metadata, supporting_arrays
|
|
99
|
+
|
|
100
|
+
return metadata
|
|
83
101
|
else:
|
|
84
102
|
raise ValueError(f"Could not find '{name}' in {path}.")
|
|
85
103
|
|
|
86
104
|
|
|
87
|
-
def
|
|
105
|
+
def load_supporting_arrays(zipf, entries) -> dict:
|
|
106
|
+
import numpy as np
|
|
107
|
+
|
|
108
|
+
supporting_arrays = {}
|
|
109
|
+
for key, entry in entries.items():
|
|
110
|
+
supporting_arrays[key] = np.frombuffer(
|
|
111
|
+
zipf.read(entry["path"]),
|
|
112
|
+
dtype=entry["dtype"],
|
|
113
|
+
).reshape(entry["shape"])
|
|
114
|
+
return supporting_arrays
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def save_metadata(path, metadata, *, supporting_arrays=None, name=DEFAULT_NAME, folder=DEFAULT_FOLDER) -> None:
|
|
88
118
|
"""Save metadata to a checkpoint file
|
|
89
119
|
|
|
90
120
|
Parameters
|
|
@@ -93,6 +123,8 @@ def save_metadata(path, metadata, name=DEFAULT_NAME, folder=DEFAULT_FOLDER) -> N
|
|
|
93
123
|
The path to the checkpoint file
|
|
94
124
|
metadata : JSON
|
|
95
125
|
A JSON serializable object
|
|
126
|
+
supporting_arrays: dict, optional
|
|
127
|
+
A dictionary of supporting NumPy arrays
|
|
96
128
|
name : str, optional
|
|
97
129
|
The name of the metadata file in the zip archive
|
|
98
130
|
folder : str, optional
|
|
@@ -118,19 +150,41 @@ def save_metadata(path, metadata, name=DEFAULT_NAME, folder=DEFAULT_FOLDER) -> N
|
|
|
118
150
|
|
|
119
151
|
directory = list(directories)[0]
|
|
120
152
|
|
|
153
|
+
LOG.info("Adding extra information to checkpoint %s", path)
|
|
121
154
|
LOG.info("Saving metadata to %s/%s/%s", directory, folder, name)
|
|
122
155
|
|
|
156
|
+
metadata = metadata.copy()
|
|
157
|
+
if supporting_arrays is not None:
|
|
158
|
+
metadata["supporting_arrays_paths"] = {
|
|
159
|
+
key: dict(path=f"{directory}/{folder}/{key}.numpy", shape=value.shape, dtype=str(value.dtype))
|
|
160
|
+
for key, value in supporting_arrays.items()
|
|
161
|
+
}
|
|
162
|
+
else:
|
|
163
|
+
metadata["supporting_arrays_paths"] = {}
|
|
164
|
+
|
|
123
165
|
zipf.writestr(
|
|
124
166
|
f"{directory}/{folder}/{name}",
|
|
125
167
|
json.dumps(metadata),
|
|
126
168
|
)
|
|
127
169
|
|
|
170
|
+
for name, entry in metadata["supporting_arrays_paths"].items():
|
|
171
|
+
value = supporting_arrays[name]
|
|
172
|
+
LOG.info(
|
|
173
|
+
"Saving supporting array `%s` to %s (shape=%s, dtype=%s)",
|
|
174
|
+
name,
|
|
175
|
+
entry["path"],
|
|
176
|
+
entry["shape"],
|
|
177
|
+
entry["dtype"],
|
|
178
|
+
)
|
|
179
|
+
zipf.writestr(entry["path"], value.tobytes())
|
|
180
|
+
|
|
128
181
|
|
|
129
|
-
def _edit_metadata(path, name, callback):
|
|
182
|
+
def _edit_metadata(path, name, callback, supporting_arrays=None):
|
|
130
183
|
new_path = f"{path}.anemoi-edit-{time.time()}-{os.getpid()}.tmp"
|
|
131
184
|
|
|
132
185
|
found = False
|
|
133
186
|
|
|
187
|
+
directory = None
|
|
134
188
|
with TemporaryDirectory() as temp_dir:
|
|
135
189
|
zipfile.ZipFile(path, "r").extractall(temp_dir)
|
|
136
190
|
total = 0
|
|
@@ -141,10 +195,21 @@ def _edit_metadata(path, name, callback):
|
|
|
141
195
|
if f == name:
|
|
142
196
|
found = True
|
|
143
197
|
callback(full)
|
|
198
|
+
directory = os.path.dirname(full)
|
|
144
199
|
|
|
145
200
|
if not found:
|
|
146
201
|
raise ValueError(f"Could not find '{name}' in {path}")
|
|
147
202
|
|
|
203
|
+
if supporting_arrays is not None:
|
|
204
|
+
|
|
205
|
+
for key, entry in supporting_arrays.items():
|
|
206
|
+
value = entry.tobytes()
|
|
207
|
+
fname = os.path.join(directory, f"{key}.numpy")
|
|
208
|
+
os.makedirs(os.path.dirname(fname), exist_ok=True)
|
|
209
|
+
with open(fname, "wb") as f:
|
|
210
|
+
f.write(value)
|
|
211
|
+
total += 1
|
|
212
|
+
|
|
148
213
|
with zipfile.ZipFile(new_path, "w", zipfile.ZIP_DEFLATED) as zipf:
|
|
149
214
|
with tqdm.tqdm(total=total, desc="Rebuilding checkpoint") as pbar:
|
|
150
215
|
for root, dirs, files in os.walk(temp_dir):
|
|
@@ -158,7 +223,7 @@ def _edit_metadata(path, name, callback):
|
|
|
158
223
|
LOG.info("Updated metadata in %s", path)
|
|
159
224
|
|
|
160
225
|
|
|
161
|
-
def replace_metadata(path, metadata, name=DEFAULT_NAME):
|
|
226
|
+
def replace_metadata(path, metadata, supporting_arrays=None, *, name=DEFAULT_NAME):
|
|
162
227
|
|
|
163
228
|
if not isinstance(metadata, dict):
|
|
164
229
|
raise ValueError(f"metadata must be a dict, got {type(metadata)}")
|
|
@@ -170,14 +235,14 @@ def replace_metadata(path, metadata, name=DEFAULT_NAME):
|
|
|
170
235
|
with open(full, "w") as f:
|
|
171
236
|
json.dump(metadata, f)
|
|
172
237
|
|
|
173
|
-
_edit_metadata(path, name, callback)
|
|
238
|
+
return _edit_metadata(path, name, callback, supporting_arrays)
|
|
174
239
|
|
|
175
240
|
|
|
176
|
-
def remove_metadata(path, name=DEFAULT_NAME):
|
|
241
|
+
def remove_metadata(path, *, name=DEFAULT_NAME):
|
|
177
242
|
|
|
178
243
|
LOG.info("Removing metadata '%s' from %s", name, path)
|
|
179
244
|
|
|
180
245
|
def callback(full):
|
|
181
246
|
os.remove(full)
|
|
182
247
|
|
|
183
|
-
_edit_metadata(path, name, callback)
|
|
248
|
+
return _edit_metadata(path, name, callback)
|
anemoi/utils/cli.py
CHANGED
|
@@ -96,8 +96,20 @@ def register_commands(here, package, select, fail=None):
|
|
|
96
96
|
continue
|
|
97
97
|
|
|
98
98
|
obj = select(imported)
|
|
99
|
-
if obj is
|
|
100
|
-
|
|
99
|
+
if obj is None:
|
|
100
|
+
continue
|
|
101
|
+
|
|
102
|
+
if hasattr(obj, "command"):
|
|
103
|
+
name = obj.command
|
|
104
|
+
|
|
105
|
+
if name in result:
|
|
106
|
+
msg = f"Duplicate command '{name}', please choose a different command name for {type(obj)}"
|
|
107
|
+
raise ValueError(msg)
|
|
108
|
+
if " " in name:
|
|
109
|
+
msg = f"Commands cannot contain spaces: '{name}' in {type(obj)}"
|
|
110
|
+
raise ValueError(msg)
|
|
111
|
+
|
|
112
|
+
result[name] = obj
|
|
101
113
|
|
|
102
114
|
for name, e in not_available.items():
|
|
103
115
|
if fail is None:
|
anemoi/utils/config.py
CHANGED
|
@@ -358,7 +358,7 @@ def check_config_mode(name="settings.toml", secrets_name=None, secrets=None) ->
|
|
|
358
358
|
CHECKED[name] = True
|
|
359
359
|
|
|
360
360
|
|
|
361
|
-
def find(metadata, what, result=None):
|
|
361
|
+
def find(metadata, what, result=None, *, select: callable = None):
|
|
362
362
|
if result is None:
|
|
363
363
|
result = []
|
|
364
364
|
|
|
@@ -369,7 +369,8 @@ def find(metadata, what, result=None):
|
|
|
369
369
|
|
|
370
370
|
if isinstance(metadata, dict):
|
|
371
371
|
if what in metadata:
|
|
372
|
-
|
|
372
|
+
if select is None or select(metadata[what]):
|
|
373
|
+
result.append(metadata[what])
|
|
373
374
|
|
|
374
375
|
for k, v in metadata.items():
|
|
375
376
|
find(v, what, result)
|
anemoi/utils/dates.py
CHANGED
|
@@ -107,8 +107,8 @@ def as_datetime_list(date, default_increment=1):
|
|
|
107
107
|
return list(_as_datetime_list(date, default_increment))
|
|
108
108
|
|
|
109
109
|
|
|
110
|
-
def
|
|
111
|
-
"""Convert
|
|
110
|
+
def as_timedelta(frequency) -> datetime.timedelta:
|
|
111
|
+
"""Convert anything to a timedelta object.
|
|
112
112
|
|
|
113
113
|
Parameters
|
|
114
114
|
----------
|
|
@@ -171,6 +171,11 @@ def frequency_to_timedelta(frequency) -> datetime.timedelta:
|
|
|
171
171
|
raise ValueError(f"Cannot convert frequency {frequency} to timedelta")
|
|
172
172
|
|
|
173
173
|
|
|
174
|
+
def frequency_to_timedelta(frequency) -> datetime.timedelta:
|
|
175
|
+
"""Convert a frequency to a timedelta object."""
|
|
176
|
+
return as_timedelta(frequency)
|
|
177
|
+
|
|
178
|
+
|
|
174
179
|
def frequency_to_string(frequency) -> str:
|
|
175
180
|
"""Convert a frequency (i.e. a datetime.timedelta) to a string.
|
|
176
181
|
|
anemoi/utils/registry.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# (C) Copyright 2024 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
|
+
|
|
11
|
+
import importlib
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
import entrypoints
|
|
17
|
+
|
|
18
|
+
LOG = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Wrapper:
|
|
22
|
+
"""A wrapper for the registry"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, name, registry):
|
|
25
|
+
self.name = name
|
|
26
|
+
self.registry = registry
|
|
27
|
+
|
|
28
|
+
def __call__(self, factory):
|
|
29
|
+
self.registry.register(self.name, factory)
|
|
30
|
+
return factory
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Registry:
|
|
34
|
+
"""A registry of factories"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, package):
|
|
37
|
+
|
|
38
|
+
self.package = package
|
|
39
|
+
self.registered = {}
|
|
40
|
+
self.kind = package.split(".")[-1]
|
|
41
|
+
|
|
42
|
+
def register(self, name: str, factory: callable = None):
|
|
43
|
+
|
|
44
|
+
if factory is None:
|
|
45
|
+
return Wrapper(name, self)
|
|
46
|
+
|
|
47
|
+
self.registered[name] = factory
|
|
48
|
+
|
|
49
|
+
def _load(self, file):
|
|
50
|
+
name, _ = os.path.splitext(file)
|
|
51
|
+
try:
|
|
52
|
+
importlib.import_module(f".{name}", package=self.package)
|
|
53
|
+
except Exception:
|
|
54
|
+
LOG.warning(f"Error loading filter '{self.package}.{name}'", exc_info=True)
|
|
55
|
+
|
|
56
|
+
def lookup(self, name: str) -> callable:
|
|
57
|
+
if name in self.registered:
|
|
58
|
+
return self.registered[name]
|
|
59
|
+
|
|
60
|
+
directory = sys.modules[self.package].__path__[0]
|
|
61
|
+
|
|
62
|
+
for file in os.listdir(directory):
|
|
63
|
+
|
|
64
|
+
if file[0] == ".":
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
if file == "__init__.py":
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
full = os.path.join(directory, file)
|
|
71
|
+
if os.path.isdir(full):
|
|
72
|
+
if os.path.exists(os.path.join(full, "__init__.py")):
|
|
73
|
+
self._load(file)
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
if file.endswith(".py"):
|
|
77
|
+
self._load(file)
|
|
78
|
+
|
|
79
|
+
entrypoint_group = f"anemoi.{self.kind}"
|
|
80
|
+
for entry_point in entrypoints.get_group_all(entrypoint_group):
|
|
81
|
+
if entry_point.name == name:
|
|
82
|
+
if name in self.registered:
|
|
83
|
+
LOG.warning(
|
|
84
|
+
f"Overwriting builtin '{name}' from {self.package} with plugin '{entry_point.module_name}'"
|
|
85
|
+
)
|
|
86
|
+
self.registered[name] = entry_point.load()
|
|
87
|
+
|
|
88
|
+
if name not in self.registered:
|
|
89
|
+
raise ValueError(f"Cannot load '{name}' from {self.package}")
|
|
90
|
+
|
|
91
|
+
return self.registered[name]
|
|
92
|
+
|
|
93
|
+
def create(self, name: str, *args, **kwargs):
|
|
94
|
+
factory = self.lookup(name)
|
|
95
|
+
return factory(*args, **kwargs)
|
|
96
|
+
|
|
97
|
+
def __call__(self, name: str, *args, **kwargs):
|
|
98
|
+
return self.create(name, *args, **kwargs)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: anemoi-utils
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.4
|
|
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,15 +1,16 @@
|
|
|
1
|
-
anemoi/utils/__init__.py,sha256=
|
|
1
|
+
anemoi/utils/__init__.py,sha256=0u0eIdu5-H1frf6V4KHpNmlh_SS-bJnxjzIejlsLqdw,702
|
|
2
2
|
anemoi/utils/__main__.py,sha256=cLA2PidDTOUHaDGzd0_E5iioKYNe-PSTv567Y2fuwQk,723
|
|
3
|
-
anemoi/utils/_version.py,sha256=
|
|
3
|
+
anemoi/utils/_version.py,sha256=t0Mfy7vCENQWSYE4xRfluKBHgAYWK48fjSubsSGEQEI,411
|
|
4
4
|
anemoi/utils/caching.py,sha256=0cznpvaaox14NSVi-Q3PqumfuGtXo0YNcEFwDPxvMZw,1948
|
|
5
|
-
anemoi/utils/checkpoints.py,sha256=
|
|
6
|
-
anemoi/utils/cli.py,sha256=
|
|
7
|
-
anemoi/utils/config.py,sha256=
|
|
8
|
-
anemoi/utils/dates.py,sha256=
|
|
5
|
+
anemoi/utils/checkpoints.py,sha256=719HjvY8zyseQxwk-08rMB3X3vI_o26Sq_AiBFkZ8Fk,7802
|
|
6
|
+
anemoi/utils/cli.py,sha256=rmMP60VY3em99rQP6TCrKibMngWwVe5h_0GDcf16c5U,4117
|
|
7
|
+
anemoi/utils/config.py,sha256=Fpy4wzj3dahTwwO75Iet6zmQQFGmTvhXml6-EsTEvgk,9873
|
|
8
|
+
anemoi/utils/dates.py,sha256=wwYD5_QI7EWY_jhpENNYtL5O7fjwYkzmqHkNoayvmrY,12452
|
|
9
9
|
anemoi/utils/grib.py,sha256=zBICyOsYtR_9px1C5UDT6wL_D6kiIhUi_00kjFmas5c,3047
|
|
10
10
|
anemoi/utils/hindcasts.py,sha256=OUOY2nDa3LBnzJ3ncgANzJDapouh82KgVyofDAu7K_Q,1426
|
|
11
11
|
anemoi/utils/humanize.py,sha256=tSQkiUHiDj3VYk-DeruHp9P79sJO1b0whsPBphqy9qA,16627
|
|
12
12
|
anemoi/utils/provenance.py,sha256=SqOiNoY1y36Zec83Pjt7OhihbwxMyknscfmogHCuriA,10894
|
|
13
|
+
anemoi/utils/registry.py,sha256=m7jNJKTkMPOv_muUCn1RPeVW8D8DtggRIhY36RtcQfU,2847
|
|
13
14
|
anemoi/utils/s3.py,sha256=LMljA5OoaVcgZcg_rmH-_LOX4uicMZl1FY64Bx4uOO8,18694
|
|
14
15
|
anemoi/utils/sanitise.py,sha256=MqEMLwVZ1jSemLDBoQXuJyXKIfyR0gzYi7DoITBcir8,2866
|
|
15
16
|
anemoi/utils/sanitize.py,sha256=43ZKDcfVpeXSsJ9TFEc9aZnD6oe2cUh151XnDspM98M,462
|
|
@@ -19,9 +20,9 @@ anemoi/utils/commands/__init__.py,sha256=qAybFZPBBQs0dyx7dZ3X5JsLpE90pwrqt1vSV7c
|
|
|
19
20
|
anemoi/utils/commands/config.py,sha256=cAt6yYF3rN1shr57c8wXsgMEvQMRN08l_fCdA1Ux9gE,839
|
|
20
21
|
anemoi/utils/mars/__init__.py,sha256=RAeY8gJ7ZvsPlcIvrQ4fy9xVHs3SphTAPw_XJDtNIKo,1750
|
|
21
22
|
anemoi/utils/mars/mars.yaml,sha256=R0dujp75lLA4wCWhPeOQnzJ45WZAYLT8gpx509cBFlc,66
|
|
22
|
-
anemoi_utils-0.4.
|
|
23
|
-
anemoi_utils-0.4.
|
|
24
|
-
anemoi_utils-0.4.
|
|
25
|
-
anemoi_utils-0.4.
|
|
26
|
-
anemoi_utils-0.4.
|
|
27
|
-
anemoi_utils-0.4.
|
|
23
|
+
anemoi_utils-0.4.4.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
24
|
+
anemoi_utils-0.4.4.dist-info/METADATA,sha256=9Tnb0OBqtTnOcJPBg3yRRYKLEgPW0FByS_GkAke-mGg,15171
|
|
25
|
+
anemoi_utils-0.4.4.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
|
26
|
+
anemoi_utils-0.4.4.dist-info/entry_points.txt,sha256=LENOkn88xzFQo-V59AKoA_F_cfYQTJYtrNTtf37YgHY,60
|
|
27
|
+
anemoi_utils-0.4.4.dist-info/top_level.txt,sha256=DYn8VPs-fNwr7fNH9XIBqeXIwiYYd2E2k5-dUFFqUz0,7
|
|
28
|
+
anemoi_utils-0.4.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|