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.

Files changed (103) hide show
  1. anemoi_utils-0.4.26/.release-please-manifest.json +3 -0
  2. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/CHANGELOG.md +16 -0
  3. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/PKG-INFO +3 -1
  4. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/pyproject.toml +2 -0
  5. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/_version.py +2 -2
  6. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/checkpoints.py +0 -1
  7. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/cli.py +14 -4
  8. anemoi_utils-0.4.26/src/anemoi/utils/commands/metadata.py +391 -0
  9. anemoi_utils-0.4.26/src/anemoi/utils/commands/transfer.py +74 -0
  10. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/grids.py +27 -0
  11. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/remote/__init__.py +8 -3
  12. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/remote/s3.py +1 -1
  13. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/testing.py +150 -73
  14. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi_utils.egg-info/PKG-INFO +3 -1
  15. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi_utils.egg-info/SOURCES.txt +2 -1
  16. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi_utils.egg-info/requires.txt +2 -0
  17. anemoi_utils-0.4.24/.release-please-manifest.json +0 -3
  18. anemoi_utils-0.4.24/tests/test_grids.py +0 -29
  19. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.gitattributes +0 -0
  20. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/CODEOWNERS +0 -0
  21. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/ci-hpc-config.yml +0 -0
  22. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/dependabot.yml +0 -0
  23. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/labeler.yml +0 -0
  24. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/pull_request_template.md +0 -0
  25. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/workflows/downstream-ci-hpc.yml +0 -0
  26. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/workflows/pr-conventional-commit.yml +0 -0
  27. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/workflows/pr-label-conventional-commits.yml +0 -0
  28. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/workflows/pr-label-file-based.yml +0 -0
  29. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/workflows/pr-label-public.yml +0 -0
  30. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/workflows/python-publish.yml +0 -0
  31. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/workflows/python-pull-request.yml +0 -0
  32. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/workflows/readthedocs-pr-update.yml +0 -0
  33. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.github/workflows/release-please.yml +0 -0
  34. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.gitignore +0 -0
  35. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.pre-commit-config.yaml +0 -0
  36. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.readthedocs.yaml +0 -0
  37. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/.release-please-config.json +0 -0
  38. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/CONTRIBUTORS.md +0 -0
  39. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/LICENSE +0 -0
  40. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/README.md +0 -0
  41. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/Makefile +0 -0
  42. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/_static/logo.png +0 -0
  43. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/_static/style.css +0 -0
  44. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/_templates/.gitkeep +0 -0
  45. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/_templates/apidoc/package.rst.jinja +0 -0
  46. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/conf.py +0 -0
  47. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/index.rst +0 -0
  48. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/installing.rst +0 -0
  49. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/modules/checkpoints.rst +0 -0
  50. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/modules/config.rst +0 -0
  51. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/modules/dates.rst +0 -0
  52. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/modules/grib.rst +0 -0
  53. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/modules/humanize.rst +0 -0
  54. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/modules/provenance.rst +0 -0
  55. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/modules/s3.rst +0 -0
  56. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/modules/testing.rst +0 -0
  57. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/modules/text.rst +0 -0
  58. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/docs/scripts/api_build.sh +0 -0
  59. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/setup.cfg +0 -0
  60. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/__init__.py +0 -0
  61. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/__main__.py +0 -0
  62. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/caching.py +0 -0
  63. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/commands/__init__.py +0 -0
  64. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/commands/config.py +0 -0
  65. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/commands/requests.py +0 -0
  66. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/compatibility.py +0 -0
  67. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/config.py +0 -0
  68. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/dates.py +0 -0
  69. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/devtools.py +0 -0
  70. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/grib.py +0 -0
  71. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/hindcasts.py +0 -0
  72. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/humanize.py +0 -0
  73. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/logs.py +0 -0
  74. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/mars/__init__.py +0 -0
  75. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/mars/mars.yaml +0 -0
  76. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/mars/requests.py +0 -0
  77. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/provenance.py +0 -0
  78. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/registry.py +0 -0
  79. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/remote/ssh.py +0 -0
  80. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/rules.py +0 -0
  81. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/s3.py +0 -0
  82. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/sanitise.py +0 -0
  83. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/sanitize.py +0 -0
  84. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/schemas/__init__.py +0 -0
  85. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/schemas/errors.py +0 -0
  86. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/text.py +0 -0
  87. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi/utils/timer.py +0 -0
  88. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi_utils.egg-info/dependency_links.txt +0 -0
  89. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi_utils.egg-info/entry_points.txt +0 -0
  90. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/src/anemoi_utils.egg-info/top_level.txt +0 -0
  91. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test-transfer-data/directory/b/c/x +0 -0
  92. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test-transfer-data/directory/b/y +0 -0
  93. {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
  94. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test-transfer-data/directory/z +0 -0
  95. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test-transfer-data/file +0 -0
  96. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test_caching.py +0 -0
  97. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test_compatibility.py +0 -0
  98. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test_dates.py +0 -0
  99. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test_frequency.py +0 -0
  100. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test_provenance.py +0 -0
  101. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test_remote.py +0 -0
  102. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test_sanetise.py +0 -0
  103. {anemoi_utils-0.4.24 → anemoi_utils-0.4.26}/tests/test_utils.py +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.4.26"
3
+ }
@@ -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.24
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
  ]
@@ -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.24'
21
- __version_tuple__ = version_tuple = (0, 4, 24)
20
+ __version__ = version = '0.4.26'
21
+ __version_tuple__ = version_tuple = (0, 4, 26)
@@ -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(version: str, description: str, commands: dict[str, Command]) -> None:
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
- sys.exit(0)
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