anemoi-utils 0.4.25__tar.gz → 0.4.27__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.27/.release-please-manifest.json +3 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/CHANGELOG.md +15 -0
- {anemoi_utils-0.4.25/src/anemoi_utils.egg-info → anemoi_utils-0.4.27}/PKG-INFO +1 -1
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/_version.py +2 -2
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/checkpoints.py +0 -1
- anemoi_utils-0.4.27/src/anemoi/utils/commands/metadata.py +391 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/commands/transfer.py +1 -1
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/remote/s3.py +82 -19
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/testing.py +49 -73
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27/src/anemoi_utils.egg-info}/PKG-INFO +1 -1
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi_utils.egg-info/SOURCES.txt +1 -0
- anemoi_utils-0.4.25/.release-please-manifest.json +0 -3
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/.gitattributes +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/.github/CODEOWNERS +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/.github/ci-hpc-config.yml +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/.github/dependabot.yml +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/.github/labeler.yml +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/.github/pull_request_template.md +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/.github/workflows/downstream-ci-hpc.yml +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/.github/workflows/pr-conventional-commit.yml +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/.github/workflows/pr-label-conventional-commits.yml +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/.github/workflows/pr-label-file-based.yml +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/.github/workflows/pr-label-public.yml +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/.github/workflows/python-publish.yml +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/.github/workflows/python-pull-request.yml +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/.github/workflows/readthedocs-pr-update.yml +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/.github/workflows/release-please.yml +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/.gitignore +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/.pre-commit-config.yaml +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/.readthedocs.yaml +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/.release-please-config.json +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/CONTRIBUTORS.md +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/LICENSE +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/README.md +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/docs/Makefile +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/docs/_static/logo.png +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/docs/_static/style.css +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/docs/_templates/.gitkeep +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/docs/_templates/apidoc/package.rst.jinja +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/docs/conf.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/docs/index.rst +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/docs/installing.rst +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/docs/modules/checkpoints.rst +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/docs/modules/config.rst +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/docs/modules/dates.rst +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/docs/modules/grib.rst +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/docs/modules/humanize.rst +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/docs/modules/provenance.rst +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/docs/modules/s3.rst +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/docs/modules/testing.rst +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/docs/modules/text.rst +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/docs/scripts/api_build.sh +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/pyproject.toml +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/setup.cfg +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/__init__.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/__main__.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/caching.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/cli.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/commands/__init__.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/commands/config.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/commands/requests.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/compatibility.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/config.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/dates.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/devtools.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/grib.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/grids.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/hindcasts.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/humanize.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/logs.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/mars/__init__.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/mars/mars.yaml +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/mars/requests.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/provenance.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/registry.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/remote/__init__.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/remote/ssh.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/rules.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/s3.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/sanitise.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/sanitize.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/schemas/__init__.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/schemas/errors.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/text.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi/utils/timer.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi_utils.egg-info/dependency_links.txt +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi_utils.egg-info/entry_points.txt +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi_utils.egg-info/requires.txt +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/src/anemoi_utils.egg-info/top_level.txt +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/tests/test-transfer-data/directory/b/c/x +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/tests/test-transfer-data/directory/b/y +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/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.25 → anemoi_utils-0.4.27}/tests/test-transfer-data/directory/z +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/tests/test-transfer-data/file +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/tests/test_caching.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/tests/test_compatibility.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/tests/test_dates.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/tests/test_frequency.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/tests/test_provenance.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/tests/test_remote.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/tests/test_sanetise.py +0 -0
- {anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/tests/test_utils.py +0 -0
|
@@ -8,6 +8,21 @@ 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.27](https://github.com/ecmwf/anemoi-utils/compare/0.4.26...0.4.27) (2025-06-27)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
* Split s3 config from s3 client code ([#170](https://github.com/ecmwf/anemoi-utils/issues/170)) ([56dacb1](https://github.com/ecmwf/anemoi-utils/commit/56dacb19efa0979acd72edb72a95f058b69d612a))
|
|
17
|
+
|
|
18
|
+
## [0.4.26](https://github.com/ecmwf/anemoi-utils/compare/0.4.25...0.4.26) (2025-06-25)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Features
|
|
22
|
+
|
|
23
|
+
* 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))
|
|
24
|
+
* 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))
|
|
25
|
+
|
|
11
26
|
## [0.4.25](https://github.com/ecmwf/anemoi-utils/compare/0.4.24...0.4.25) (2025-06-24)
|
|
12
27
|
|
|
13
28
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: anemoi-utils
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.27
|
|
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
|
|
@@ -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)
|
|
@@ -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
|
|
@@ -17,7 +17,7 @@ from . import Command
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class Transfer(Command):
|
|
20
|
-
"""
|
|
20
|
+
"""Transfer files or folders from the source to the target location."""
|
|
21
21
|
|
|
22
22
|
def add_arguments(self, command_parser: ArgumentParser) -> None:
|
|
23
23
|
"""Add arguments to the command parser.
|
|
@@ -47,8 +47,8 @@ SECRETS = ["aws_access_key_id", "aws_secret_access_key"]
|
|
|
47
47
|
thread_local = threading.local()
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
def
|
|
51
|
-
"""Get an S3 client for the specified bucket and region.
|
|
50
|
+
def _s3_config(bucket: str, *, region: str = None) -> Any:
|
|
51
|
+
"""Get an S3 client config for the specified bucket and region.
|
|
52
52
|
|
|
53
53
|
Parameters
|
|
54
54
|
----------
|
|
@@ -56,31 +56,15 @@ def s3_client(bucket: str, *, region: str = None, service: str = "s3") -> Any:
|
|
|
56
56
|
The name of the S3 bucket.
|
|
57
57
|
region : str, optional
|
|
58
58
|
The AWS region of the S3 bucket.
|
|
59
|
-
service : str, optional
|
|
60
|
-
The AWS service to use, default is "s3".
|
|
61
59
|
|
|
62
60
|
Returns
|
|
63
61
|
-------
|
|
64
62
|
Any
|
|
65
63
|
The S3 client.
|
|
66
64
|
"""
|
|
67
|
-
import boto3
|
|
68
65
|
from botocore import UNSIGNED
|
|
69
|
-
from botocore.client import Config
|
|
70
66
|
|
|
71
|
-
|
|
72
|
-
thread_local.s3_clients = {}
|
|
73
|
-
|
|
74
|
-
key = f"{bucket}-{region}-{service}"
|
|
75
|
-
|
|
76
|
-
if key in thread_local.s3_clients:
|
|
77
|
-
return thread_local.s3_clients[key]
|
|
78
|
-
|
|
79
|
-
boto3_config = dict(
|
|
80
|
-
max_pool_connections=25,
|
|
81
|
-
request_checksum_calculation="when_required",
|
|
82
|
-
response_checksum_validation="when_required",
|
|
83
|
-
)
|
|
67
|
+
boto3_config = {}
|
|
84
68
|
|
|
85
69
|
if region:
|
|
86
70
|
# This is using AWS
|
|
@@ -129,6 +113,85 @@ def s3_client(bucket: str, *, region: str = None, service: str = "s3") -> Any:
|
|
|
129
113
|
boto3_config.update(options["config"])
|
|
130
114
|
del options["config"]
|
|
131
115
|
|
|
116
|
+
def _(options):
|
|
117
|
+
|
|
118
|
+
def __(k, v):
|
|
119
|
+
if k in SECRETS:
|
|
120
|
+
return "***"
|
|
121
|
+
return v
|
|
122
|
+
|
|
123
|
+
if isinstance(options, dict):
|
|
124
|
+
return {k: __(k, v) for k, v in options.items()}
|
|
125
|
+
|
|
126
|
+
if isinstance(options, list):
|
|
127
|
+
return [_(o) for o in options]
|
|
128
|
+
|
|
129
|
+
return options
|
|
130
|
+
|
|
131
|
+
LOG.debug(f"Using S3 options: {_(options)}")
|
|
132
|
+
|
|
133
|
+
return boto3_config, options
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def s3_options(bucket: str, *, region: str = None, service: str = "s3") -> dict:
|
|
137
|
+
"""Get the S3 configuration for the specified bucket and region.
|
|
138
|
+
|
|
139
|
+
Parameters
|
|
140
|
+
----------
|
|
141
|
+
bucket : str
|
|
142
|
+
The name of the S3 bucket.
|
|
143
|
+
region : str, optional
|
|
144
|
+
The AWS region of the S3 bucket.
|
|
145
|
+
service : str, optional
|
|
146
|
+
The AWS service to use, default is "s3".
|
|
147
|
+
|
|
148
|
+
Returns
|
|
149
|
+
-------
|
|
150
|
+
dict
|
|
151
|
+
The S3 configuration.
|
|
152
|
+
"""
|
|
153
|
+
_, options = _s3_config(bucket, region=region)
|
|
154
|
+
return options
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def s3_client(bucket: str, *, region: str = None, service: str = "s3") -> Any:
|
|
158
|
+
"""Get an S3 client for the specified bucket and region.
|
|
159
|
+
|
|
160
|
+
Parameters
|
|
161
|
+
----------
|
|
162
|
+
bucket : str
|
|
163
|
+
The name of the S3 bucket.
|
|
164
|
+
region : str, optional
|
|
165
|
+
The AWS region of the S3 bucket.
|
|
166
|
+
service : str, optional
|
|
167
|
+
The AWS service to use, default is "s3".
|
|
168
|
+
|
|
169
|
+
Returns
|
|
170
|
+
-------
|
|
171
|
+
Any
|
|
172
|
+
The S3 client.
|
|
173
|
+
"""
|
|
174
|
+
import boto3
|
|
175
|
+
from botocore.client import Config
|
|
176
|
+
|
|
177
|
+
if not hasattr(thread_local, "s3_clients"):
|
|
178
|
+
thread_local.s3_clients = {}
|
|
179
|
+
|
|
180
|
+
key = f"{bucket}-{region}-{service}"
|
|
181
|
+
|
|
182
|
+
if key in thread_local.s3_clients:
|
|
183
|
+
return thread_local.s3_clients[key]
|
|
184
|
+
|
|
185
|
+
boto3_config, options = _s3_config(bucket, region=region)
|
|
186
|
+
|
|
187
|
+
boto3_config.update(
|
|
188
|
+
dict(
|
|
189
|
+
max_pool_connections=25,
|
|
190
|
+
request_checksum_calculation="when_required",
|
|
191
|
+
response_checksum_validation="when_required",
|
|
192
|
+
)
|
|
193
|
+
)
|
|
194
|
+
|
|
132
195
|
options["config"] = Config(**boto3_config)
|
|
133
196
|
|
|
134
197
|
def _(options):
|
|
@@ -7,14 +7,12 @@
|
|
|
7
7
|
# granted to it by virtue of its status as an intergovernmental organisation
|
|
8
8
|
# nor does it submit to any jurisdiction.
|
|
9
9
|
|
|
10
|
-
import atexit
|
|
11
10
|
import logging
|
|
12
11
|
import os
|
|
13
12
|
import shutil
|
|
14
|
-
import tempfile
|
|
15
|
-
import threading
|
|
16
13
|
import warnings
|
|
17
14
|
from functools import lru_cache
|
|
15
|
+
from pathlib import Path
|
|
18
16
|
|
|
19
17
|
import pytest
|
|
20
18
|
from multiurl import download
|
|
@@ -25,30 +23,6 @@ LOG = logging.getLogger(__name__)
|
|
|
25
23
|
|
|
26
24
|
TEST_DATA_URL = "https://object-store.os-api.cci1.ecmwf.int/ml-tests/test-data/samples/"
|
|
27
25
|
|
|
28
|
-
lock = threading.RLock()
|
|
29
|
-
TEMPORARY_DIRECTORY = None
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def _temporary_directory() -> str:
|
|
33
|
-
"""Return a temporary directory in which to download test data.
|
|
34
|
-
|
|
35
|
-
Returns
|
|
36
|
-
-------
|
|
37
|
-
str
|
|
38
|
-
The path to the temporary directory.
|
|
39
|
-
"""
|
|
40
|
-
global TEMPORARY_DIRECTORY
|
|
41
|
-
with lock:
|
|
42
|
-
if TEMPORARY_DIRECTORY is not None:
|
|
43
|
-
return TEMPORARY_DIRECTORY
|
|
44
|
-
|
|
45
|
-
TEMPORARY_DIRECTORY = tempfile.mkdtemp()
|
|
46
|
-
|
|
47
|
-
# Register a cleanup function to remove the directory at exit
|
|
48
|
-
atexit.register(shutil.rmtree, TEMPORARY_DIRECTORY)
|
|
49
|
-
|
|
50
|
-
return TEMPORARY_DIRECTORY
|
|
51
|
-
|
|
52
26
|
|
|
53
27
|
def _check_path(path: str) -> None:
|
|
54
28
|
"""Check if the given path is normalized, not absolute, and does not start with a dot.
|
|
@@ -68,21 +42,17 @@ def _check_path(path: str) -> None:
|
|
|
68
42
|
assert not path.startswith("."), f"Path '{path}' should not start with '.'"
|
|
69
43
|
|
|
70
44
|
|
|
71
|
-
|
|
72
|
-
|
|
45
|
+
@pytest.fixture(scope="session")
|
|
46
|
+
def temporary_directory_for_test_data(tmp_path_factory) -> callable:
|
|
47
|
+
base_dir = tmp_path_factory.mktemp("test_data_base")
|
|
73
48
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
49
|
+
def _temporary_directory_for_test_data(path: str = "", archive: bool = False) -> str:
|
|
50
|
+
if path == "":
|
|
51
|
+
return str(base_dir)
|
|
52
|
+
_check_path(path)
|
|
53
|
+
return str(base_dir.joinpath(*Path(path).parts)) + (".extracted" if archive else "")
|
|
78
54
|
|
|
79
|
-
|
|
80
|
-
-------
|
|
81
|
-
str
|
|
82
|
-
The path to the temporary directory.
|
|
83
|
-
"""
|
|
84
|
-
_check_path(path)
|
|
85
|
-
return os.path.normpath(os.path.join(_temporary_directory(), path))
|
|
55
|
+
return _temporary_directory_for_test_data
|
|
86
56
|
|
|
87
57
|
|
|
88
58
|
def url_for_test_data(path: str) -> str:
|
|
@@ -103,27 +73,29 @@ def url_for_test_data(path: str) -> str:
|
|
|
103
73
|
return f"{TEST_DATA_URL}{path}"
|
|
104
74
|
|
|
105
75
|
|
|
106
|
-
|
|
107
|
-
|
|
76
|
+
@pytest.fixture()
|
|
77
|
+
def get_test_data(temporary_directory_for_test_data):
|
|
78
|
+
def _get_test_data(path: str, gzipped=False) -> callable:
|
|
79
|
+
"""Download the test data to a temporary directory and return the local path.
|
|
108
80
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
path : str
|
|
84
|
+
The relative path to the test data.
|
|
85
|
+
gzipped : bool, optional
|
|
86
|
+
Flag indicating if the remote file is gzipped, by default False. The local file will be gunzipped.
|
|
115
87
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
str
|
|
91
|
+
The local path to the downloaded test data.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
if _offline():
|
|
95
|
+
raise RuntimeError("Offline mode: cannot download test data, add @pytest.mark.skipif(not offline(),...)")
|
|
121
96
|
|
|
122
|
-
|
|
123
|
-
raise RuntimeError("Offline mode: cannot download test data, add @pytest.mark.skipif(not offline(),...)")
|
|
97
|
+
target = temporary_directory_for_test_data(path)
|
|
124
98
|
|
|
125
|
-
target = _temporary_directory_for_test_data(path)
|
|
126
|
-
with lock:
|
|
127
99
|
if os.path.exists(target):
|
|
128
100
|
return target
|
|
129
101
|
|
|
@@ -149,27 +121,29 @@ def get_test_data(path: str, gzipped=False) -> str:
|
|
|
149
121
|
|
|
150
122
|
return target
|
|
151
123
|
|
|
124
|
+
return _get_test_data
|
|
152
125
|
|
|
153
|
-
def get_test_archive(path: str, extension=".extracted") -> str:
|
|
154
|
-
"""Download an archive file (.zip, .tar, .tar.gz, .tar.bz2, .tar.xz) to a temporary directory
|
|
155
|
-
unpack it, and return the local path to the directory containing the extracted files.
|
|
156
126
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
path
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
The extension to add to the extracted directory, by default '.extracted'
|
|
127
|
+
@pytest.fixture()
|
|
128
|
+
def get_test_archive(temporary_directory_for_test_data, get_test_data) -> callable:
|
|
129
|
+
def _get_test_archive(path: str) -> str:
|
|
130
|
+
"""Download an archive file (.zip, .tar, .tar.gz, .tar.bz2, .tar.xz) to a temporary directory
|
|
131
|
+
unpack it, and return the local path to the directory containing the extracted files.
|
|
163
132
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
path : str
|
|
136
|
+
The relative path to the test data.
|
|
137
|
+
extension : str, optional
|
|
138
|
+
The extension to add to the extracted directory, by default '.extracted'
|
|
169
139
|
|
|
170
|
-
|
|
140
|
+
Returns
|
|
141
|
+
-------
|
|
142
|
+
str
|
|
143
|
+
The local path to the downloaded test data.
|
|
144
|
+
"""
|
|
171
145
|
|
|
172
|
-
target =
|
|
146
|
+
target = Path(temporary_directory_for_test_data(path, archive=True))
|
|
173
147
|
|
|
174
148
|
if os.path.exists(target):
|
|
175
149
|
return target
|
|
@@ -183,6 +157,8 @@ def get_test_archive(path: str, extension=".extracted") -> str:
|
|
|
183
157
|
|
|
184
158
|
return target
|
|
185
159
|
|
|
160
|
+
return _get_test_archive
|
|
161
|
+
|
|
186
162
|
|
|
187
163
|
def packages_installed(*names: str) -> bool:
|
|
188
164
|
"""Check if all the given packages are installed.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: anemoi-utils
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.27
|
|
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
|
|
@@ -67,6 +67,7 @@ src/anemoi/utils/text.py
|
|
|
67
67
|
src/anemoi/utils/timer.py
|
|
68
68
|
src/anemoi/utils/commands/__init__.py
|
|
69
69
|
src/anemoi/utils/commands/config.py
|
|
70
|
+
src/anemoi/utils/commands/metadata.py
|
|
70
71
|
src/anemoi/utils/commands/requests.py
|
|
71
72
|
src/anemoi/utils/commands/transfer.py
|
|
72
73
|
src/anemoi/utils/mars/__init__.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{anemoi_utils-0.4.25 → anemoi_utils-0.4.27}/.github/workflows/pr-label-conventional-commits.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|