anemoi-utils 0.4.24__tar.gz → 0.4.26__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.
- anemoi_utils-0.4.26/.release-please-manifest.json +3 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/CHANGELOG.md +16 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/PKG-INFO +3 -1
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/pyproject.toml +2 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/_version.py +2 -2
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/checkpoints.py +0 -1
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/cli.py +14 -4
- anemoi_utils-0.4.26/src/anemoi/utils/commands/metadata.py +391 -0
- anemoi_utils-0.4.26/src/anemoi/utils/commands/transfer.py +74 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/grids.py +27 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/remote/__init__.py +8 -3
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/remote/s3.py +1 -1
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/testing.py +150 -73
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi_utils.egg-info/PKG-INFO +3 -1
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi_utils.egg-info/SOURCES.txt +2 -1
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi_utils.egg-info/requires.txt +2 -0
- anemoi_utils-0.4.24/.release-please-manifest.json +0 -3
- anemoi_utils-0.4.24/tests/test_grids.py +0 -29
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.gitattributes +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/CODEOWNERS +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/ci-hpc-config.yml +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/dependabot.yml +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/labeler.yml +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/pull_request_template.md +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/workflows/downstream-ci-hpc.yml +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/workflows/pr-conventional-commit.yml +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/workflows/pr-label-conventional-commits.yml +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/workflows/pr-label-file-based.yml +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/workflows/pr-label-public.yml +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/workflows/python-publish.yml +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/workflows/python-pull-request.yml +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/workflows/readthedocs-pr-update.yml +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/workflows/release-please.yml +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.gitignore +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.pre-commit-config.yaml +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.readthedocs.yaml +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.release-please-config.json +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/CONTRIBUTORS.md +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/LICENSE +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/README.md +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/Makefile +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/_static/logo.png +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/_static/style.css +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/_templates/.gitkeep +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/_templates/apidoc/package.rst.jinja +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/conf.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/index.rst +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/installing.rst +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/modules/checkpoints.rst +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/modules/config.rst +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/modules/dates.rst +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/modules/grib.rst +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/modules/humanize.rst +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/modules/provenance.rst +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/modules/s3.rst +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/modules/testing.rst +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/modules/text.rst +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/scripts/api_build.sh +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/setup.cfg +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/__init__.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/__main__.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/caching.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/commands/__init__.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/commands/config.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/commands/requests.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/compatibility.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/config.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/dates.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/devtools.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/grib.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/hindcasts.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/humanize.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/logs.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/mars/__init__.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/mars/mars.yaml +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/mars/requests.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/provenance.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/registry.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/remote/ssh.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/rules.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/s3.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/sanitise.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/sanitize.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/schemas/__init__.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/schemas/errors.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/text.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/timer.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi_utils.egg-info/dependency_links.txt +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi_utils.egg-info/entry_points.txt +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi_utils.egg-info/top_level.txt +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test-transfer-data/directory/b/c/x +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test-transfer-data/directory/b/y +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test-transfer-data/directory/exotic filename ;^/"'[=.,#]()/303/252/303/274/303/247/303/262/342/234/205.txt" +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test-transfer-data/directory/z +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test-transfer-data/file +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test_caching.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test_compatibility.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test_dates.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test_frequency.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test_provenance.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test_remote.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test_sanetise.py +0 -0
- {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test_utils.py +0 -0
|
@@ -8,6 +8,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
Please add your functional changes to the appropriate section in the PR.
|
|
9
9
|
Keep it human-readable, your future self will thank you!
|
|
10
10
|
|
|
11
|
+
## [0.4.26](https://github.com/ecmwf/anemoi-utils/compare/0.4.25...0.4.26) (2025-06-25)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
* Fixtures for temp dir handling for test data ([#166](https://github.com/ecmwf/anemoi-utils/issues/166)) ([2b9677f](https://github.com/ecmwf/anemoi-utils/commit/2b9677fffc5eba84876f974001b87b73c7e542af))
|
|
17
|
+
* Move anemoi-inference metadata command to this package, add metadata removal options ([#167](https://github.com/ecmwf/anemoi-utils/issues/167)) ([cabb989](https://github.com/ecmwf/anemoi-utils/commit/cabb989bdd4154a0476acf48e1ac44099c91c6db))
|
|
18
|
+
|
|
19
|
+
## [0.4.25](https://github.com/ecmwf/anemoi-utils/compare/0.4.24...0.4.25) (2025-06-24)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
* Add a CLI to transfer data ([#164](https://github.com/ecmwf/anemoi-utils/issues/164)) ([3a845ca](https://github.com/ecmwf/anemoi-utils/commit/3a845ca0c31d115e6b3d0496d862a3eaee5fb236))
|
|
25
|
+
* Add function to test cli ([#168](https://github.com/ecmwf/anemoi-utils/issues/168)) ([9ac9b06](https://github.com/ecmwf/anemoi-utils/commit/9ac9b06b8fd0a62cad33ea5de6a6b482f0a13656))
|
|
26
|
+
|
|
11
27
|
## [0.4.24](https://github.com/ecmwf/anemoi-utils/compare/0.4.23...0.4.24) (2025-06-06)
|
|
12
28
|
|
|
13
29
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: anemoi-utils
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.26
|
|
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
|
|
@@ -225,12 +225,14 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
|
225
225
|
Requires-Python: >=3.9
|
|
226
226
|
License-File: LICENSE
|
|
227
227
|
Requires-Dist: aniso8601
|
|
228
|
+
Requires-Dist: deprecation
|
|
228
229
|
Requires-Dist: importlib-metadata; python_version < "3.10"
|
|
229
230
|
Requires-Dist: multiurl
|
|
230
231
|
Requires-Dist: numpy
|
|
231
232
|
Requires-Dist: pydantic>=2.9
|
|
232
233
|
Requires-Dist: python-dateutil
|
|
233
234
|
Requires-Dist: pyyaml
|
|
235
|
+
Requires-Dist: rich
|
|
234
236
|
Requires-Dist: tomli; python_version < "3.11"
|
|
235
237
|
Requires-Dist: tqdm
|
|
236
238
|
Provides-Extra: all
|
|
@@ -41,12 +41,14 @@ classifiers = [
|
|
|
41
41
|
dynamic = [ "version" ]
|
|
42
42
|
dependencies = [
|
|
43
43
|
"aniso8601",
|
|
44
|
+
"deprecation",
|
|
44
45
|
"importlib-metadata; python_version<'3.10'",
|
|
45
46
|
"multiurl",
|
|
46
47
|
"numpy",
|
|
47
48
|
"pydantic>=2.9",
|
|
48
49
|
"python-dateutil",
|
|
49
50
|
"pyyaml",
|
|
51
|
+
"rich",
|
|
50
52
|
"tomli; python_version<'3.11'",
|
|
51
53
|
"tqdm",
|
|
52
54
|
]
|
|
@@ -308,7 +308,6 @@ def remove_metadata(path: str, *, name: str = DEFAULT_NAME) -> None:
|
|
|
308
308
|
name : str, optional
|
|
309
309
|
The name of the metadata file in the zip archive
|
|
310
310
|
"""
|
|
311
|
-
LOG.info("Removing metadata '%s' from %s", name, path)
|
|
312
311
|
|
|
313
312
|
def callback(full):
|
|
314
313
|
os.remove(full)
|
|
@@ -15,6 +15,7 @@ import os
|
|
|
15
15
|
import sys
|
|
16
16
|
import traceback
|
|
17
17
|
from typing import Callable
|
|
18
|
+
from typing import Optional
|
|
18
19
|
|
|
19
20
|
try:
|
|
20
21
|
import argcomplete
|
|
@@ -185,7 +186,9 @@ def register_commands(here: str, package: str, select: Callable, fail: Callable
|
|
|
185
186
|
return result
|
|
186
187
|
|
|
187
188
|
|
|
188
|
-
def cli_main(
|
|
189
|
+
def cli_main(
|
|
190
|
+
version: str, description: str, commands: dict[str, Command], test_arguments: Optional[list[str]] = None
|
|
191
|
+
) -> None:
|
|
189
192
|
"""Main entry point for the CLI.
|
|
190
193
|
|
|
191
194
|
Parameters
|
|
@@ -196,9 +199,11 @@ def cli_main(version: str, description: str, commands: dict[str, Command]) -> No
|
|
|
196
199
|
The description of the CLI
|
|
197
200
|
commands : dict[str, Command]
|
|
198
201
|
A dictionary of command names to Command instances
|
|
202
|
+
test_arguments : list[str], optional
|
|
203
|
+
The command line arguments to parse, used for testing purposes, by default None
|
|
199
204
|
"""
|
|
200
205
|
parser = make_parser(description, commands)
|
|
201
|
-
args, unknown = parser.parse_known_args()
|
|
206
|
+
args, unknown = parser.parse_known_args(test_arguments)
|
|
202
207
|
if argcomplete:
|
|
203
208
|
argcomplete.autocomplete(parser)
|
|
204
209
|
|
|
@@ -220,7 +225,7 @@ def cli_main(version: str, description: str, commands: dict[str, Command]) -> No
|
|
|
220
225
|
|
|
221
226
|
if unknown and not cmd.accept_unknown_args:
|
|
222
227
|
# This should trigger an error
|
|
223
|
-
parser.parse_args()
|
|
228
|
+
parser.parse_args(test_arguments)
|
|
224
229
|
|
|
225
230
|
try:
|
|
226
231
|
if unknown:
|
|
@@ -228,9 +233,14 @@ def cli_main(version: str, description: str, commands: dict[str, Command]) -> No
|
|
|
228
233
|
else:
|
|
229
234
|
cmd.run(args)
|
|
230
235
|
except ValueError as e:
|
|
236
|
+
|
|
237
|
+
if test_arguments:
|
|
238
|
+
raise
|
|
239
|
+
|
|
231
240
|
traceback.print_exc()
|
|
232
241
|
LOG.error("\n💣 %s", str(e).lstrip())
|
|
233
242
|
LOG.error("💣 Exiting")
|
|
234
243
|
sys.exit(1)
|
|
235
244
|
|
|
236
|
-
|
|
245
|
+
if not test_arguments:
|
|
246
|
+
sys.exit(0)
|
|
@@ -0,0 +1,391 @@
|
|
|
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 json
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
import shutil
|
|
15
|
+
import subprocess
|
|
16
|
+
from argparse import ArgumentParser
|
|
17
|
+
from argparse import Namespace
|
|
18
|
+
from tempfile import TemporaryDirectory
|
|
19
|
+
from typing import Any
|
|
20
|
+
from typing import Dict
|
|
21
|
+
|
|
22
|
+
import yaml
|
|
23
|
+
|
|
24
|
+
from . import Command
|
|
25
|
+
|
|
26
|
+
LOG = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
EDITOR_OPTIONS = {"code": ["--wait"]}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Metadata(Command):
|
|
32
|
+
"""Edit, remove, dump or load metadata from a checkpoint file."""
|
|
33
|
+
|
|
34
|
+
def add_arguments(self, command_parser: ArgumentParser) -> None:
|
|
35
|
+
"""Add command line arguments to the parser.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
command_parser : ArgumentParser
|
|
40
|
+
The argument parser to which the arguments will be added.
|
|
41
|
+
"""
|
|
42
|
+
from anemoi.utils.checkpoints import DEFAULT_NAME
|
|
43
|
+
|
|
44
|
+
command_parser.add_argument("path", help="Path to the checkpoint.")
|
|
45
|
+
|
|
46
|
+
group = command_parser.add_mutually_exclusive_group(required=True)
|
|
47
|
+
|
|
48
|
+
group.add_argument(
|
|
49
|
+
"--dump",
|
|
50
|
+
action="store_true",
|
|
51
|
+
help=(
|
|
52
|
+
"Extract the metadata from the checkpoint and print it to the standard output"
|
|
53
|
+
" or the file specified by ``--output``, in JSON or YAML format."
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
group.add_argument(
|
|
57
|
+
"--load",
|
|
58
|
+
action="store_true",
|
|
59
|
+
help=(
|
|
60
|
+
"Set the metadata in the checkpoint from the content"
|
|
61
|
+
" of a file specified by the ``--input`` argument."
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
group.add_argument(
|
|
66
|
+
"--edit",
|
|
67
|
+
action="store_true",
|
|
68
|
+
help=(
|
|
69
|
+
"Edit the metadata in place, using the specified editor."
|
|
70
|
+
" See the ``--editor`` argument for more information."
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
group.add_argument(
|
|
75
|
+
"--view",
|
|
76
|
+
action="store_true",
|
|
77
|
+
help=(
|
|
78
|
+
"View the metadata in place, using the specified pager."
|
|
79
|
+
" See the ``--pager`` argument for more information."
|
|
80
|
+
),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
group.add_argument(
|
|
84
|
+
"--remove",
|
|
85
|
+
action="store_true",
|
|
86
|
+
help="Remove the metadata from the checkpoint.",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
group.add_argument(
|
|
90
|
+
"--supporting-arrays",
|
|
91
|
+
action="store_true",
|
|
92
|
+
help="Print the supporting arrays.",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
group.add_argument(
|
|
96
|
+
"--get",
|
|
97
|
+
help="Navigate the metadata via dot-separated path.",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
group.add_argument(
|
|
101
|
+
"--pytest",
|
|
102
|
+
action="store_true",
|
|
103
|
+
help=("Extract the metadata from the checkpoint so it can be added to the test suite."),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
command_parser.add_argument(
|
|
107
|
+
"--name",
|
|
108
|
+
default=DEFAULT_NAME,
|
|
109
|
+
help="Name of metadata record to be used with the actions above.",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
command_parser.add_argument(
|
|
113
|
+
"--input",
|
|
114
|
+
help="The output file name to be used by the ``--load`` option.",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
command_parser.add_argument(
|
|
118
|
+
"--output",
|
|
119
|
+
help="The output file name to be used by the ``--dump`` option.",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
command_parser.add_argument(
|
|
123
|
+
"--inplace",
|
|
124
|
+
action="store_true",
|
|
125
|
+
help="If set, update the source file in place instead of writing to a separate target.",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
command_parser.add_argument(
|
|
129
|
+
"--editor",
|
|
130
|
+
help="Editor to use for the ``--edit`` option. Default to ``$EDITOR`` if defined, else ``vi``.",
|
|
131
|
+
default=os.environ.get("EDITOR", "vi"),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
command_parser.add_argument(
|
|
135
|
+
"--pager",
|
|
136
|
+
help="Editor to use for the ``--view`` option. Default to ``$PAGER`` if defined, else ``less``.",
|
|
137
|
+
default=os.environ.get("PAGER", "less"),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
command_parser.add_argument(
|
|
141
|
+
"--json",
|
|
142
|
+
action="store_true",
|
|
143
|
+
help="Use the JSON format with ``--dump``, ``--view`` and ``--edit``.",
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
command_parser.add_argument(
|
|
147
|
+
"--yaml",
|
|
148
|
+
action="store_true",
|
|
149
|
+
help="Use the YAML format with ``--dump``, ``--view`` and ``--edit``.",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
def run(self, args: Namespace) -> None:
|
|
153
|
+
"""Execute the command based on the provided arguments.
|
|
154
|
+
|
|
155
|
+
Parameters
|
|
156
|
+
----------
|
|
157
|
+
args : Namespace
|
|
158
|
+
The arguments passed to the command.
|
|
159
|
+
"""
|
|
160
|
+
if args.edit:
|
|
161
|
+
return self.edit(args)
|
|
162
|
+
|
|
163
|
+
if args.view:
|
|
164
|
+
return self.view(args)
|
|
165
|
+
|
|
166
|
+
if args.get:
|
|
167
|
+
return self.get(args)
|
|
168
|
+
|
|
169
|
+
if args.remove:
|
|
170
|
+
return self.remove(args)
|
|
171
|
+
|
|
172
|
+
if args.dump or args.pytest:
|
|
173
|
+
return self.dump(args)
|
|
174
|
+
|
|
175
|
+
if args.load:
|
|
176
|
+
return self.load(args)
|
|
177
|
+
|
|
178
|
+
if args.supporting_arrays:
|
|
179
|
+
return self.supporting_arrays(args)
|
|
180
|
+
|
|
181
|
+
def edit(self, args: Namespace) -> None:
|
|
182
|
+
"""Edit the metadata in place using the specified editor.
|
|
183
|
+
|
|
184
|
+
Parameters
|
|
185
|
+
----------
|
|
186
|
+
args : Namespace
|
|
187
|
+
The arguments passed to the command.
|
|
188
|
+
"""
|
|
189
|
+
return self._edit(args, view=False, cmd=args.editor)
|
|
190
|
+
|
|
191
|
+
def view(self, args: Namespace) -> None:
|
|
192
|
+
"""View the metadata in place using the specified pager.
|
|
193
|
+
|
|
194
|
+
Parameters
|
|
195
|
+
----------
|
|
196
|
+
args : Namespace
|
|
197
|
+
The arguments passed to the command.
|
|
198
|
+
"""
|
|
199
|
+
return self._edit(args, view=True, cmd=args.pager)
|
|
200
|
+
|
|
201
|
+
def _edit(self, args: Namespace, view: bool, cmd: str) -> None:
|
|
202
|
+
"""Internal method to edit or view the metadata.
|
|
203
|
+
|
|
204
|
+
Parameters
|
|
205
|
+
----------
|
|
206
|
+
args : Namespace
|
|
207
|
+
The arguments passed to the command.
|
|
208
|
+
view : bool
|
|
209
|
+
If True, view the metadata; otherwise, edit it.
|
|
210
|
+
cmd : str
|
|
211
|
+
The command to use for editing or viewing.
|
|
212
|
+
"""
|
|
213
|
+
from anemoi.utils.checkpoints import load_metadata
|
|
214
|
+
from anemoi.utils.checkpoints import replace_metadata
|
|
215
|
+
|
|
216
|
+
kwargs: Dict[str, Any] = {}
|
|
217
|
+
|
|
218
|
+
if args.json:
|
|
219
|
+
ext = "json"
|
|
220
|
+
dump = json.dump
|
|
221
|
+
load = json.load
|
|
222
|
+
if args.test:
|
|
223
|
+
kwargs = {"sort_keys": True}
|
|
224
|
+
else:
|
|
225
|
+
kwargs = {"indent": 4, "sort_keys": True}
|
|
226
|
+
else:
|
|
227
|
+
ext = "yaml"
|
|
228
|
+
dump = yaml.dump
|
|
229
|
+
load = yaml.safe_load
|
|
230
|
+
kwargs = {"default_flow_style": False}
|
|
231
|
+
|
|
232
|
+
with TemporaryDirectory() as temp_dir:
|
|
233
|
+
|
|
234
|
+
path = os.path.join(temp_dir, f"checkpoint.{ext}")
|
|
235
|
+
metadata = load_metadata(args.path)
|
|
236
|
+
|
|
237
|
+
with open(path, "w") as f:
|
|
238
|
+
dump(metadata, f, **kwargs)
|
|
239
|
+
|
|
240
|
+
subprocess.check_call([cmd, *EDITOR_OPTIONS.get(cmd, []), path])
|
|
241
|
+
|
|
242
|
+
if not view:
|
|
243
|
+
with open(path) as f:
|
|
244
|
+
edited = load(f)
|
|
245
|
+
|
|
246
|
+
if edited != metadata:
|
|
247
|
+
replace_metadata(args.path, edited)
|
|
248
|
+
else:
|
|
249
|
+
LOG.info("No changes made.")
|
|
250
|
+
|
|
251
|
+
def remove(self, args: Namespace) -> None:
|
|
252
|
+
"""Remove the metadata from the checkpoint.
|
|
253
|
+
|
|
254
|
+
Parameters
|
|
255
|
+
----------
|
|
256
|
+
args : Namespace
|
|
257
|
+
The arguments passed to the command.
|
|
258
|
+
"""
|
|
259
|
+
from anemoi.utils.checkpoints import remove_metadata
|
|
260
|
+
|
|
261
|
+
if args.inplace and args.output:
|
|
262
|
+
raise ValueError("Only choose one of --inplace and --output")
|
|
263
|
+
|
|
264
|
+
LOG.info("Removing metadata from %s", args.path)
|
|
265
|
+
|
|
266
|
+
if args.inplace:
|
|
267
|
+
output = args.path
|
|
268
|
+
else:
|
|
269
|
+
if not args.output:
|
|
270
|
+
raise ValueError("Argument --output is required unless --inplace is set")
|
|
271
|
+
|
|
272
|
+
shutil.copy2(args.path, args.output)
|
|
273
|
+
output = args.output
|
|
274
|
+
|
|
275
|
+
LOG.info("Writing checkpoint at %s", output)
|
|
276
|
+
remove_metadata(output)
|
|
277
|
+
|
|
278
|
+
def dump(self, args: Namespace) -> None:
|
|
279
|
+
"""Dump the metadata from the checkpoint to a file or standard output.
|
|
280
|
+
|
|
281
|
+
Parameters
|
|
282
|
+
----------
|
|
283
|
+
args : Namespace
|
|
284
|
+
The arguments passed to the command.
|
|
285
|
+
"""
|
|
286
|
+
from anemoi.utils.checkpoints import load_metadata
|
|
287
|
+
|
|
288
|
+
if args.output:
|
|
289
|
+
file = open(args.output, "w")
|
|
290
|
+
else:
|
|
291
|
+
file = None
|
|
292
|
+
|
|
293
|
+
metadata = load_metadata(args.path)
|
|
294
|
+
if args.pytest:
|
|
295
|
+
from anemoi.inference.testing.mock_checkpoint import minimum_mock_checkpoint
|
|
296
|
+
|
|
297
|
+
# We remove all unessential metadata for testing purposes
|
|
298
|
+
metadata = minimum_mock_checkpoint(metadata)
|
|
299
|
+
|
|
300
|
+
if args.yaml:
|
|
301
|
+
print(yaml.dump(metadata, indent=2, sort_keys=True), file=file)
|
|
302
|
+
return
|
|
303
|
+
|
|
304
|
+
if args.json or True:
|
|
305
|
+
if args.pytest:
|
|
306
|
+
print(json.dumps(metadata, sort_keys=True), file=file)
|
|
307
|
+
else:
|
|
308
|
+
print(json.dumps(metadata, indent=4, sort_keys=True), file=file)
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
def get(self, args: Namespace) -> None:
|
|
312
|
+
"""Navigate and print the metadata via a dot-separated path.
|
|
313
|
+
|
|
314
|
+
Parameters
|
|
315
|
+
----------
|
|
316
|
+
args : Namespace
|
|
317
|
+
The arguments passed to the command.
|
|
318
|
+
"""
|
|
319
|
+
from pprint import pprint
|
|
320
|
+
|
|
321
|
+
from anemoi.utils.checkpoints import load_metadata
|
|
322
|
+
|
|
323
|
+
metadata = load_metadata(args.path, name=args.name)
|
|
324
|
+
|
|
325
|
+
if args.get == ".":
|
|
326
|
+
print("Metadata from root: ", list(metadata.keys()))
|
|
327
|
+
return
|
|
328
|
+
|
|
329
|
+
for key in args.get.split("."):
|
|
330
|
+
if key == "":
|
|
331
|
+
keys = list(metadata.keys())
|
|
332
|
+
print(f"Metadata keys from {args.get[:-1]}: ", keys)
|
|
333
|
+
return
|
|
334
|
+
else:
|
|
335
|
+
metadata = metadata[key]
|
|
336
|
+
|
|
337
|
+
print(f"Metadata values for {args.get}: ", end="\n" if isinstance(metadata, (dict, list)) else "")
|
|
338
|
+
if isinstance(metadata, dict):
|
|
339
|
+
pprint(metadata, indent=2, compact=True)
|
|
340
|
+
else:
|
|
341
|
+
print(metadata)
|
|
342
|
+
|
|
343
|
+
def load(self, args: Namespace) -> None:
|
|
344
|
+
"""Load metadata into the checkpoint from a specified file.
|
|
345
|
+
|
|
346
|
+
Parameters
|
|
347
|
+
----------
|
|
348
|
+
args : Namespace
|
|
349
|
+
The arguments passed to the command.
|
|
350
|
+
"""
|
|
351
|
+
from anemoi.utils.checkpoints import has_metadata
|
|
352
|
+
from anemoi.utils.checkpoints import replace_metadata
|
|
353
|
+
from anemoi.utils.checkpoints import save_metadata
|
|
354
|
+
|
|
355
|
+
if args.input is None:
|
|
356
|
+
raise ValueError("Please specify a value for --input")
|
|
357
|
+
|
|
358
|
+
_, ext = os.path.splitext(args.input)
|
|
359
|
+
if ext == ".json" or args.json:
|
|
360
|
+
with open(args.input) as f:
|
|
361
|
+
metadata = json.load(f)
|
|
362
|
+
|
|
363
|
+
elif ext in (".yaml", ".yml") or args.yaml:
|
|
364
|
+
with open(args.input) as f:
|
|
365
|
+
metadata = yaml.safe_load(f)
|
|
366
|
+
|
|
367
|
+
else:
|
|
368
|
+
raise ValueError(f"Unknown file extension {ext}. Please specify --json or --yaml")
|
|
369
|
+
|
|
370
|
+
if has_metadata(args.path, name=args.name):
|
|
371
|
+
replace_metadata(args.path, metadata)
|
|
372
|
+
else:
|
|
373
|
+
save_metadata(args.path, metadata, name=args.name)
|
|
374
|
+
|
|
375
|
+
def supporting_arrays(self, args: Namespace) -> None:
|
|
376
|
+
"""Print the supporting arrays from the metadata.
|
|
377
|
+
|
|
378
|
+
Parameters
|
|
379
|
+
----------
|
|
380
|
+
args : Namespace
|
|
381
|
+
The arguments passed to the command.
|
|
382
|
+
"""
|
|
383
|
+
from anemoi.utils.checkpoints import load_metadata
|
|
384
|
+
|
|
385
|
+
_, supporting_arrays = load_metadata(args.path, supporting_arrays=True)
|
|
386
|
+
|
|
387
|
+
for name, array in supporting_arrays.items():
|
|
388
|
+
print(f"{name}: shape={array.shape} dtype={array.dtype}")
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
command = Metadata
|
|
@@ -0,0 +1,74 @@
|
|
|
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
|
+
from argparse import ArgumentParser
|
|
12
|
+
from argparse import Namespace
|
|
13
|
+
|
|
14
|
+
from anemoi.utils.remote import transfer
|
|
15
|
+
|
|
16
|
+
from . import Command
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Transfer(Command):
|
|
20
|
+
"""Transfer files or folders from the source to the target location."""
|
|
21
|
+
|
|
22
|
+
def add_arguments(self, command_parser: ArgumentParser) -> None:
|
|
23
|
+
"""Add arguments to the command parser.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
command_parser : ArgumentParser
|
|
28
|
+
The argument parser to which the arguments will be added.
|
|
29
|
+
"""
|
|
30
|
+
command_parser.add_argument(
|
|
31
|
+
"--source", help="A path to a local file or folder or a URL to a file or a folder on S3."
|
|
32
|
+
)
|
|
33
|
+
command_parser.add_argument(
|
|
34
|
+
"--target", help="A path to a local file or folder or a URL to a file or a folder on S3 or a remote folder."
|
|
35
|
+
)
|
|
36
|
+
command_parser.add_argument(
|
|
37
|
+
"--overwrite",
|
|
38
|
+
action="store_true",
|
|
39
|
+
help="If the data is already on in the target location it will be overwritten..",
|
|
40
|
+
)
|
|
41
|
+
command_parser.add_argument(
|
|
42
|
+
"--resume",
|
|
43
|
+
action="store_true",
|
|
44
|
+
help="If the data is already on S3 it will not be uploaded, unless the remote file has a different size.",
|
|
45
|
+
)
|
|
46
|
+
command_parser.add_argument("--verbosity", default=1, help="The level of verbosity, by default 1.")
|
|
47
|
+
command_parser.add_argument(
|
|
48
|
+
"--progress", default=None, help="A callable that will be called with the number of files."
|
|
49
|
+
)
|
|
50
|
+
command_parser.add_argument(
|
|
51
|
+
"--threads", default=1, help="The number of threads to use when uploading a directory, by default 1."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def run(self, args: Namespace) -> None:
|
|
55
|
+
"""Execute the command with the provided arguments.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
args : Namespace
|
|
60
|
+
The arguments passed to the command.
|
|
61
|
+
"""
|
|
62
|
+
transfer(
|
|
63
|
+
source=args.source,
|
|
64
|
+
target=args.target,
|
|
65
|
+
overwrite=args.overwrite,
|
|
66
|
+
resume=args.resume,
|
|
67
|
+
verbosity=args.verbosity,
|
|
68
|
+
progress=args.progress,
|
|
69
|
+
threads=args.threads,
|
|
70
|
+
temporary_target=False,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
command = Transfer
|
|
@@ -17,9 +17,12 @@ from typing import List
|
|
|
17
17
|
from typing import Tuple
|
|
18
18
|
from typing import Union
|
|
19
19
|
|
|
20
|
+
import deprecation
|
|
20
21
|
import numpy as np
|
|
21
22
|
import requests
|
|
22
23
|
|
|
24
|
+
from anemoi.utils._version import __version__
|
|
25
|
+
|
|
23
26
|
from .caching import cached
|
|
24
27
|
|
|
25
28
|
LOG = logging.getLogger(__name__)
|
|
@@ -28,6 +31,12 @@ LOG = logging.getLogger(__name__)
|
|
|
28
31
|
GRIDS_URL_PATTERN = "https://get.ecmwf.int/repository/anemoi/grids/grid-{name}.npz"
|
|
29
32
|
|
|
30
33
|
|
|
34
|
+
@deprecation.deprecated(
|
|
35
|
+
deprecated_in="0.4.25",
|
|
36
|
+
removed_in="0.5.0",
|
|
37
|
+
current_version=__version__,
|
|
38
|
+
details="Use anemoi.transform.spatial.xyz_to_latlon instead.",
|
|
39
|
+
)
|
|
31
40
|
def xyz_to_latlon(x: np.ndarray, y: np.ndarray, z: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
|
32
41
|
"""Convert Cartesian coordinates to latitude and longitude.
|
|
33
42
|
|
|
@@ -51,6 +60,12 @@ def xyz_to_latlon(x: np.ndarray, y: np.ndarray, z: np.ndarray) -> tuple[np.ndarr
|
|
|
51
60
|
)
|
|
52
61
|
|
|
53
62
|
|
|
63
|
+
@deprecation.deprecated(
|
|
64
|
+
deprecated_in="0.4.25",
|
|
65
|
+
removed_in="0.5.0",
|
|
66
|
+
current_version=__version__,
|
|
67
|
+
details="Use anemoi.transform.spatial.xyz_to_latlon instead.",
|
|
68
|
+
)
|
|
54
69
|
def latlon_to_xyz(lat: np.ndarray, lon: np.ndarray, radius: float = 1.0) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
55
70
|
"""Convert latitude and longitude to Cartesian coordinates.
|
|
56
71
|
|
|
@@ -87,6 +102,12 @@ def latlon_to_xyz(lat: np.ndarray, lon: np.ndarray, radius: float = 1.0) -> tupl
|
|
|
87
102
|
return x, y, z
|
|
88
103
|
|
|
89
104
|
|
|
105
|
+
@deprecation.deprecated(
|
|
106
|
+
deprecated_in="0.4.25",
|
|
107
|
+
removed_in="0.5.0",
|
|
108
|
+
current_version=__version__,
|
|
109
|
+
details="Use anemoi.transform.spatial.nearest_grid_points instead.",
|
|
110
|
+
)
|
|
90
111
|
def nearest_grid_points(
|
|
91
112
|
source_latitudes: np.ndarray,
|
|
92
113
|
source_longitudes: np.ndarray,
|
|
@@ -169,6 +190,12 @@ def _grids(name: Union[str, List[float], Tuple[float, ...]]) -> bytes:
|
|
|
169
190
|
return response.content
|
|
170
191
|
|
|
171
192
|
|
|
193
|
+
@deprecation.deprecated(
|
|
194
|
+
deprecated_in="0.4.25",
|
|
195
|
+
removed_in="0.5.0",
|
|
196
|
+
current_version=__version__,
|
|
197
|
+
details="Use anemoi.transform.grids.named.lookup instead.",
|
|
198
|
+
)
|
|
172
199
|
def grids(name: Union[str, List[float], Tuple[float, ...]]) -> dict:
|
|
173
200
|
"""Load grid data by name.
|
|
174
201
|
|