anemoi-utils 0.4.35__tar.gz → 0.4.37__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.37/.github/workflows/pr-label-ats.yml +66 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/.pre-commit-config.yaml +2 -2
- anemoi_utils-0.4.37/.release-please-manifest.json +3 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/CHANGELOG.md +30 -0
- {anemoi_utils-0.4.35/src/anemoi_utils.egg-info → anemoi_utils-0.4.37}/PKG-INFO +3 -2
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/pyproject.toml +2 -1
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/_environment.py +3 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/_version.py +3 -3
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/checkpoints.py +47 -32
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/cli.py +38 -5
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/commands/metadata.py +6 -4
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/commands/transfer.py +6 -2
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/config.py +0 -6
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/logs.py +34 -6
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/mlflow/auth.py +154 -18
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/registry.py +55 -1
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/remote/__init__.py +1 -3
- anemoi_utils-0.4.37/src/anemoi/utils/remote/s3.py +735 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37/src/anemoi_utils.egg-info}/PKG-INFO +3 -2
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi_utils.egg-info/SOURCES.txt +4 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi_utils.egg-info/requires.txt +2 -1
- anemoi_utils-0.4.37/tests/test_checkpoints.py +220 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/tests/test_mlflow_auth.py +130 -4
- anemoi_utils-0.4.37/tests/test_registry.py +64 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/tests/test_remote.py +45 -23
- anemoi_utils-0.4.37/tests/test_s3.py +65 -0
- anemoi_utils-0.4.35/.release-please-manifest.json +0 -3
- anemoi_utils-0.4.35/src/anemoi/utils/remote/s3.py +0 -789
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/.gitattributes +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/.github/CODEOWNERS +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/.github/ci-hpc-config.yml +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/.github/dependabot.yml +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/.github/labeler.yml +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/.github/pull_request_template.md +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/.github/workflows/downstream-ci-hpc.yml +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/.github/workflows/pr-conventional-commit.yml +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/.github/workflows/pr-label-conventional-commits.yml +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/.github/workflows/pr-label-file-based.yml +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/.github/workflows/pr-label-public.yml +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/.github/workflows/python-publish.yml +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/.github/workflows/python-pull-request.yml +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/.github/workflows/readthedocs-pr-update.yml +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/.github/workflows/release-please.yml +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/.gitignore +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/.readthedocs.yaml +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/.release-please-config.json +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/CONTRIBUTORS.md +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/LICENSE +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/README.md +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/docs/Makefile +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/docs/_static/logo.png +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/docs/_static/style.css +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/docs/_templates/.gitkeep +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/docs/_templates/apidoc/package.rst.jinja +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/docs/conf.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/docs/index.rst +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/docs/installing.rst +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/docs/modules/checkpoints.rst +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/docs/modules/config.rst +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/docs/modules/dates.rst +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/docs/modules/grib.rst +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/docs/modules/humanize.rst +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/docs/modules/provenance.rst +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/docs/modules/s3.rst +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/docs/modules/testing.rst +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/docs/modules/text.rst +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/docs/scripts/api_build.sh +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/setup.cfg +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/__init__.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/__main__.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/caching.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/commands/__init__.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/commands/config.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/commands/requests.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/compatibility.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/dates.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/devtools.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/grib.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/grids.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/hindcasts.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/humanize.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/mars/__init__.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/mars/mars.yaml +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/mars/requests.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/mlflow/__init__.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/mlflow/client.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/mlflow/utils.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/provenance.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/remote/ssh.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/rules.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/s3.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/sanitise.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/sanitize.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/schemas/__init__.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/schemas/errors.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/testing.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/text.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi/utils/timer.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi_utils.egg-info/dependency_links.txt +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi_utils.egg-info/entry_points.txt +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/src/anemoi_utils.egg-info/top_level.txt +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/tests/test-transfer-data/directory/b/c/x +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/tests/test-transfer-data/directory/b/y +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/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.35 → anemoi_utils-0.4.37}/tests/test-transfer-data/directory/z +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/tests/test-transfer-data/file +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/tests/test_caching.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/tests/test_compatibility.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/tests/test_dates.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/tests/test_frequency.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/tests/test_mlflow_client.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/tests/test_provenance.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/tests/test_sanitise.py +0 -0
- {anemoi_utils-0.4.35 → anemoi_utils-0.4.37}/tests/test_utils.py +0 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# This workflow checks that the appropriate ATS labels are applied to a pull request:
|
|
2
|
+
# - "ATS Approval Not Needed": pass
|
|
3
|
+
# - "ATS Approved": pass
|
|
4
|
+
# - "ATS Approval Needed": fail
|
|
5
|
+
# - Missing ATS label: fail
|
|
6
|
+
|
|
7
|
+
name: "[PR] ATS labels"
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
pull_request:
|
|
11
|
+
types:
|
|
12
|
+
- opened
|
|
13
|
+
- edited
|
|
14
|
+
- reopened
|
|
15
|
+
- labeled
|
|
16
|
+
- unlabeled
|
|
17
|
+
- synchronize
|
|
18
|
+
|
|
19
|
+
permissions:
|
|
20
|
+
pull-requests: read
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
check:
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
steps:
|
|
26
|
+
- name: Get PR details
|
|
27
|
+
uses: actions/github-script@v7
|
|
28
|
+
id: check-pr
|
|
29
|
+
with:
|
|
30
|
+
script: |
|
|
31
|
+
const pr = await github.rest.pulls.get({
|
|
32
|
+
owner: context.repo.owner,
|
|
33
|
+
repo: context.repo.repo,
|
|
34
|
+
pull_number: context.payload.pull_request.number,
|
|
35
|
+
});
|
|
36
|
+
const labels = pr.data.labels.map(label => label.name);
|
|
37
|
+
core.setOutput('labels', JSON.stringify(labels));
|
|
38
|
+
core.setOutput('author', context.payload.pull_request.user.login);
|
|
39
|
+
|
|
40
|
+
- name: Evaluate ATS labels
|
|
41
|
+
run: |
|
|
42
|
+
AUTHOR='${{ steps.check-pr.outputs.author }}'
|
|
43
|
+
if [ "$AUTHOR" == "DeployDuck" ] || [ "$AUTHOR" == "pre-commit-ci[bot]" ]; then
|
|
44
|
+
echo "Bot PR, skipping."
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
47
|
+
LABELS=$(echo '${{ steps.check-pr.outputs.labels }}' | jq -r '.[]')
|
|
48
|
+
echo "Labels found:"
|
|
49
|
+
echo -e "$LABELS\n"
|
|
50
|
+
echo "Result:"
|
|
51
|
+
if echo "$LABELS" | grep -qi "ATS approval not needed"; then
|
|
52
|
+
echo "ATS approval not needed. Passing."
|
|
53
|
+
EXIT_CODE=0
|
|
54
|
+
elif echo "$LABELS" | grep -qi "ATS approved"; then
|
|
55
|
+
echo "ATS Approved. Passing."
|
|
56
|
+
EXIT_CODE=0
|
|
57
|
+
elif echo "$LABELS" | grep -qi "ATS approval needed"; then
|
|
58
|
+
echo "ATS Approval Needed. Failing."
|
|
59
|
+
EXIT_CODE=1
|
|
60
|
+
else
|
|
61
|
+
echo "No ATS approval labels found. Please assign the appropriate ATS label. Failing."
|
|
62
|
+
EXIT_CODE=1
|
|
63
|
+
fi
|
|
64
|
+
echo -e "\nFor more information on ATS labels, see:"
|
|
65
|
+
echo "https://anemoi.readthedocs.io/en/latest/contributing/guidelines.html#labelling-guidelines"
|
|
66
|
+
exit $EXIT_CODE
|
|
@@ -10,7 +10,7 @@ repos:
|
|
|
10
10
|
entry: jupyter nbconvert --ClearOutputPreprocessor.enabled=True --inplace
|
|
11
11
|
additional_dependencies: [jupyter]
|
|
12
12
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
13
|
-
rev:
|
|
13
|
+
rev: v6.0.0
|
|
14
14
|
hooks:
|
|
15
15
|
- id: check-yaml # Check YAML files for syntax errors only
|
|
16
16
|
args: [--unsafe, --allow-multiple-documents]
|
|
@@ -41,7 +41,7 @@ repos:
|
|
|
41
41
|
- --profile black
|
|
42
42
|
- --project anemoi
|
|
43
43
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
44
|
-
rev: v0.12.
|
|
44
|
+
rev: v0.12.11
|
|
45
45
|
hooks:
|
|
46
46
|
- id: ruff
|
|
47
47
|
args:
|
|
@@ -8,6 +8,36 @@ 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.37](https://github.com/ecmwf/anemoi-utils/compare/0.4.36...0.4.37) (2025-09-30)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
* **mlflow auth:** Support for multiple servers ([#217](https://github.com/ecmwf/anemoi-utils/issues/217)) ([8ccfb1a](https://github.com/ecmwf/anemoi-utils/commit/8ccfb1ab063cccfec5852c386580036286b097c6))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Bug Fixes
|
|
20
|
+
|
|
21
|
+
* Update s3 chunk size to 10 MB ([#220](https://github.com/ecmwf/anemoi-utils/issues/220)) ([aa20fa8](https://github.com/ecmwf/anemoi-utils/commit/aa20fa8b0b572fb6fa510b2f28c2b8b8a2f76d7c))
|
|
22
|
+
* Use `yaml` and `json` flag in metadata get command ([#222](https://github.com/ecmwf/anemoi-utils/issues/222)) ([6af46c4](https://github.com/ecmwf/anemoi-utils/commit/6af46c4e715fc55aca374d2112976aa7d1bac589))
|
|
23
|
+
|
|
24
|
+
## [0.4.36](https://github.com/ecmwf/anemoi-utils/compare/0.4.35...0.4.36) (2025-09-22)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Features
|
|
28
|
+
|
|
29
|
+
* Add aliases to registry ([#219](https://github.com/ecmwf/anemoi-utils/issues/219)) ([37267b5](https://github.com/ecmwf/anemoi-utils/commit/37267b548556a796a01b43abb908011eeec85454))
|
|
30
|
+
* Debug imports ([#182](https://github.com/ecmwf/anemoi-utils/issues/182)) ([1eaa615](https://github.com/ecmwf/anemoi-utils/commit/1eaa61540dc9ac3d5fe82f2c91b7fc98c8bb10af))
|
|
31
|
+
* NoAuth for AML mlflow Logging ([#200](https://github.com/ecmwf/anemoi-utils/issues/200)) ([732182e](https://github.com/ecmwf/anemoi-utils/commit/732182ea5d255ba69ea2ed0a23b307d6f64aaf84))
|
|
32
|
+
* Rich logging ([#209](https://github.com/ecmwf/anemoi-utils/issues/209)) ([3c762a5](https://github.com/ecmwf/anemoi-utils/commit/3c762a593ba2dc734becc54b92984d6dc62967ac))
|
|
33
|
+
* Speedup checkpoint editing - remove compression ([#218](https://github.com/ecmwf/anemoi-utils/issues/218)) ([b49120f](https://github.com/ecmwf/anemoi-utils/commit/b49120f763b0b6ee10c805bab2aa7b973047f963))
|
|
34
|
+
* Use obstore to access s3 buckets ([#210](https://github.com/ecmwf/anemoi-utils/issues/210)) ([da380be](https://github.com/ecmwf/anemoi-utils/commit/da380be71d78274d72bd0a3859ef00b1c80e9469))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
### Bug Fixes
|
|
38
|
+
|
|
39
|
+
* Add missing s3 function used by datasets ([#212](https://github.com/ecmwf/anemoi-utils/issues/212)) ([30589e8](https://github.com/ecmwf/anemoi-utils/commit/30589e891fbdb1cff205f0350c63e93a725c7242))
|
|
40
|
+
|
|
11
41
|
## [0.4.35](https://github.com/ecmwf/anemoi-utils/compare/0.4.34...0.4.35) (2025-08-12)
|
|
12
42
|
|
|
13
43
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: anemoi-utils
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.37
|
|
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,6 +225,7 @@ Requires-Python: >=3.10
|
|
|
225
225
|
License-File: LICENSE
|
|
226
226
|
Requires-Dist: aniso8601
|
|
227
227
|
Requires-Dist: deprecation
|
|
228
|
+
Requires-Dist: entrypoints
|
|
228
229
|
Requires-Dist: importlib-metadata; python_version < "3.10"
|
|
229
230
|
Requires-Dist: multiurl
|
|
230
231
|
Requires-Dist: numpy
|
|
@@ -256,7 +257,7 @@ Provides-Extra: provenance
|
|
|
256
257
|
Requires-Dist: gitpython; extra == "provenance"
|
|
257
258
|
Requires-Dist: nvsmi; extra == "provenance"
|
|
258
259
|
Provides-Extra: s3
|
|
259
|
-
Requires-Dist:
|
|
260
|
+
Requires-Dist: obstore; extra == "s3"
|
|
260
261
|
Provides-Extra: tests
|
|
261
262
|
Requires-Dist: anemoi-utils[mlflow]; extra == "tests"
|
|
262
263
|
Requires-Dist: pytest; extra == "tests"
|
|
@@ -41,6 +41,7 @@ dynamic = [ "version" ]
|
|
|
41
41
|
dependencies = [
|
|
42
42
|
"aniso8601",
|
|
43
43
|
"deprecation",
|
|
44
|
+
"entrypoints",
|
|
44
45
|
"importlib-metadata; python_version<'3.10'",
|
|
45
46
|
"multiurl",
|
|
46
47
|
"numpy",
|
|
@@ -73,7 +74,7 @@ optional-dependencies.mlflow = [ "mlflow-skinny>=2.11.1", "requests" ]
|
|
|
73
74
|
optional-dependencies.provenance = [ "gitpython", "nvsmi" ]
|
|
74
75
|
|
|
75
76
|
optional-dependencies.s3 = [
|
|
76
|
-
"
|
|
77
|
+
"obstore",
|
|
77
78
|
]
|
|
78
79
|
|
|
79
80
|
optional-dependencies.tests = [ "anemoi-utils[mlflow]", "pytest", "pytest-mock>=3" ]
|
|
@@ -17,6 +17,9 @@ class Environment:
|
|
|
17
17
|
ANEMOI_CONFIG_OVERRIDE_PATH: str
|
|
18
18
|
"""Path to the configuration override file for Anemoi."""
|
|
19
19
|
|
|
20
|
+
ANEMOI_DEBUG_IMPORTS: bool
|
|
21
|
+
"""Enable debug imports to trace module loading."""
|
|
22
|
+
|
|
20
23
|
def __setattr__(self, name: str, value: Any) -> None:
|
|
21
24
|
raise AttributeError("Cannot set attributes on Environment class. Use environment variables instead.")
|
|
22
25
|
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.4.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 4,
|
|
31
|
+
__version__ = version = '0.4.37'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 4, 37)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'gceecf1ec8'
|
|
@@ -214,7 +214,7 @@ def save_metadata(
|
|
|
214
214
|
zipf.writestr(entry["path"], value.tobytes())
|
|
215
215
|
|
|
216
216
|
|
|
217
|
-
def _edit_metadata(path: str, name: str, callback: Callable, supporting_arrays: dict = None) -> None:
|
|
217
|
+
def _edit_metadata(path: str, name: str, callback: Callable, supporting_arrays: dict | None = None) -> None:
|
|
218
218
|
"""Edit metadata in a checkpoint file.
|
|
219
219
|
|
|
220
220
|
Parameters
|
|
@@ -230,41 +230,56 @@ def _edit_metadata(path: str, name: str, callback: Callable, supporting_arrays:
|
|
|
230
230
|
"""
|
|
231
231
|
new_path = f"{path}.anemoi-edit-{time.time()}-{os.getpid()}.tmp"
|
|
232
232
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
for
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
directory = os.path.dirname(full)
|
|
247
|
-
|
|
248
|
-
if not found:
|
|
233
|
+
with zipfile.ZipFile(path, "r") as source_zip:
|
|
234
|
+
file_list = source_zip.namelist()
|
|
235
|
+
|
|
236
|
+
# Find the target file and its directory
|
|
237
|
+
target_file = None
|
|
238
|
+
directory = None
|
|
239
|
+
for file_path in file_list:
|
|
240
|
+
if os.path.basename(file_path) == name:
|
|
241
|
+
target_file = file_path
|
|
242
|
+
directory = os.path.dirname(file_path)
|
|
243
|
+
break
|
|
244
|
+
|
|
245
|
+
if target_file is None:
|
|
249
246
|
raise ValueError(f"Could not find '{name}' in {path}")
|
|
250
247
|
|
|
248
|
+
# Calculate total files for progress bar
|
|
249
|
+
total_files = len(file_list)
|
|
251
250
|
if supporting_arrays is not None:
|
|
251
|
+
total_files += len(supporting_arrays)
|
|
252
|
+
|
|
253
|
+
with zipfile.ZipFile(new_path, "w", zipfile.ZIP_STORED) as new_zip:
|
|
254
|
+
with tqdm.tqdm(total=total_files, desc="Rebuilding checkpoint") as pbar:
|
|
255
|
+
|
|
256
|
+
# Copy all files except the target file
|
|
257
|
+
for file_path in file_list:
|
|
258
|
+
if file_path != target_file:
|
|
259
|
+
with source_zip.open(file_path) as source_file:
|
|
260
|
+
data = source_file.read()
|
|
261
|
+
new_zip.writestr(file_path, data)
|
|
262
|
+
pbar.update(1)
|
|
252
263
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
264
|
+
# Handle the target file with callback
|
|
265
|
+
with TemporaryDirectory() as temp_dir:
|
|
266
|
+
# Extract only the target file
|
|
267
|
+
source_zip.extract(target_file, temp_dir)
|
|
268
|
+
target_full_path = os.path.join(temp_dir, target_file)
|
|
269
|
+
|
|
270
|
+
# Apply the callback
|
|
271
|
+
callback(target_full_path)
|
|
272
|
+
|
|
273
|
+
# Add the modified file to the new zip (if it still exists)
|
|
274
|
+
if os.path.exists(target_full_path):
|
|
275
|
+
new_zip.write(target_full_path, target_file)
|
|
276
|
+
pbar.update(1)
|
|
277
|
+
|
|
278
|
+
# Add supporting arrays if provided
|
|
279
|
+
if supporting_arrays is not None:
|
|
280
|
+
for key, entry in supporting_arrays.items():
|
|
281
|
+
array_path = os.path.join(directory, f"{key}.numpy") if directory else f"{key}.numpy"
|
|
282
|
+
new_zip.writestr(array_path, entry.tobytes())
|
|
268
283
|
pbar.update(1)
|
|
269
284
|
|
|
270
285
|
os.rename(new_path, path)
|
|
@@ -16,11 +16,26 @@ import sys
|
|
|
16
16
|
import traceback
|
|
17
17
|
from collections.abc import Callable
|
|
18
18
|
|
|
19
|
+
from anemoi.utils import ENV
|
|
20
|
+
|
|
19
21
|
try:
|
|
20
22
|
import argcomplete
|
|
21
23
|
except ImportError:
|
|
22
24
|
argcomplete = None
|
|
23
25
|
|
|
26
|
+
|
|
27
|
+
if ENV.ANEMOI_DEBUG_IMPORTS:
|
|
28
|
+
from datetime import datetime
|
|
29
|
+
from importlib.abc import MetaPathFinder
|
|
30
|
+
|
|
31
|
+
class ImportTracer(MetaPathFinder):
|
|
32
|
+
def find_spec(self, fullname, path, target=None):
|
|
33
|
+
now = datetime.now().isoformat(timespec="milliseconds")
|
|
34
|
+
print(f"[{now}] Importing {fullname} from {path}")
|
|
35
|
+
return None # allow normal import processing to continue
|
|
36
|
+
|
|
37
|
+
sys.meta_path.insert(0, ImportTracer())
|
|
38
|
+
|
|
24
39
|
LOG = logging.getLogger(__name__)
|
|
25
40
|
|
|
26
41
|
|
|
@@ -29,6 +44,10 @@ class Command:
|
|
|
29
44
|
|
|
30
45
|
accept_unknown_args = False
|
|
31
46
|
|
|
47
|
+
def check(self, parser: argparse.ArgumentParser, args: argparse.Namespace) -> None:
|
|
48
|
+
"""Check the command arguments."""
|
|
49
|
+
pass
|
|
50
|
+
|
|
32
51
|
def run(self, args: argparse.Namespace) -> None:
|
|
33
52
|
"""Run the command.
|
|
34
53
|
|
|
@@ -72,6 +91,11 @@ def make_parser(description: str, commands: dict[str, Command]) -> argparse.Argu
|
|
|
72
91
|
action="store_true",
|
|
73
92
|
help="Debug mode",
|
|
74
93
|
)
|
|
94
|
+
parser.add_argument(
|
|
95
|
+
"--rich",
|
|
96
|
+
action="store_true",
|
|
97
|
+
help="Use rich for logging",
|
|
98
|
+
)
|
|
75
99
|
|
|
76
100
|
subparsers = parser.add_subparsers(help="commands:", dest="command")
|
|
77
101
|
for name, command in commands.items():
|
|
@@ -216,16 +240,25 @@ def cli_main(
|
|
|
216
240
|
|
|
217
241
|
cmd = commands[args.command]
|
|
218
242
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
243
|
+
if args.rich:
|
|
244
|
+
from .logs import get_rich_handler
|
|
245
|
+
|
|
246
|
+
logging.basicConfig(
|
|
247
|
+
format="%(message)s", level=logging.DEBUG if args.debug else logging.INFO, handlers=[get_rich_handler()]
|
|
248
|
+
)
|
|
249
|
+
else:
|
|
250
|
+
logging.basicConfig(
|
|
251
|
+
format="%(asctime)s %(levelname)s %(message)s",
|
|
252
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
253
|
+
level=logging.DEBUG if args.debug else logging.INFO,
|
|
254
|
+
)
|
|
224
255
|
|
|
225
256
|
if unknown and not cmd.accept_unknown_args:
|
|
226
257
|
# This should trigger an error
|
|
227
258
|
parser.parse_args(test_arguments)
|
|
228
259
|
|
|
260
|
+
cmd.check(parser, args)
|
|
261
|
+
|
|
229
262
|
try:
|
|
230
263
|
if unknown:
|
|
231
264
|
cmd.run(args, unknown)
|
|
@@ -139,13 +139,13 @@ class Metadata(Command):
|
|
|
139
139
|
command_parser.add_argument(
|
|
140
140
|
"--json",
|
|
141
141
|
action="store_true",
|
|
142
|
-
help="Use the JSON format with ``--dump``, ``--view`` and ``--edit``.",
|
|
142
|
+
help="Use the JSON format with ``--dump``, ``--view``, ``--get`` and ``--edit``.",
|
|
143
143
|
)
|
|
144
144
|
|
|
145
145
|
command_parser.add_argument(
|
|
146
146
|
"--yaml",
|
|
147
147
|
action="store_true",
|
|
148
|
-
help="Use the YAML format with ``--dump``, ``--view`` and ``--edit``.",
|
|
148
|
+
help="Use the YAML format with ``--dump``, ``--view``, ``--get`` and ``--edit``.",
|
|
149
149
|
)
|
|
150
150
|
|
|
151
151
|
def run(self, args: Namespace) -> None:
|
|
@@ -315,7 +315,6 @@ class Metadata(Command):
|
|
|
315
315
|
args : Namespace
|
|
316
316
|
The arguments passed to the command.
|
|
317
317
|
"""
|
|
318
|
-
from pprint import pprint
|
|
319
318
|
|
|
320
319
|
from anemoi.utils.checkpoints import load_metadata
|
|
321
320
|
|
|
@@ -335,7 +334,10 @@ class Metadata(Command):
|
|
|
335
334
|
|
|
336
335
|
print(f"Metadata values for {args.get}: ", end="\n" if isinstance(metadata, (dict, list)) else "")
|
|
337
336
|
if isinstance(metadata, dict):
|
|
338
|
-
|
|
337
|
+
if args.yaml:
|
|
338
|
+
print(yaml.dump(metadata, indent=2, sort_keys=True))
|
|
339
|
+
return
|
|
340
|
+
print(json.dumps(metadata, indent=2, sort_keys=True))
|
|
339
341
|
else:
|
|
340
342
|
print(metadata)
|
|
341
343
|
|
|
@@ -28,10 +28,14 @@ class Transfer(Command):
|
|
|
28
28
|
The argument parser to which the arguments will be added.
|
|
29
29
|
"""
|
|
30
30
|
command_parser.add_argument(
|
|
31
|
-
"--source",
|
|
31
|
+
"--source",
|
|
32
|
+
help="A path to a local file or folder or a URL to a file or a folder on S3.",
|
|
33
|
+
required=True,
|
|
32
34
|
)
|
|
33
35
|
command_parser.add_argument(
|
|
34
|
-
"--target",
|
|
36
|
+
"--target",
|
|
37
|
+
help="A path to a local file or folder or a URL to a file or a folder on S3 or a remote folder.",
|
|
38
|
+
required=True,
|
|
35
39
|
)
|
|
36
40
|
command_parser.add_argument(
|
|
37
41
|
"--overwrite",
|
|
@@ -15,7 +15,6 @@ import json
|
|
|
15
15
|
import logging
|
|
16
16
|
import os
|
|
17
17
|
import threading
|
|
18
|
-
import warnings
|
|
19
18
|
from typing import Any
|
|
20
19
|
|
|
21
20
|
import yaml
|
|
@@ -187,7 +186,6 @@ class DotDict(dict):
|
|
|
187
186
|
The attribute value.
|
|
188
187
|
"""
|
|
189
188
|
|
|
190
|
-
self.warn_on_mutation(attr)
|
|
191
189
|
value = self.convert_to_nested_dot_dict(value)
|
|
192
190
|
super().__setitem__(attr, value)
|
|
193
191
|
|
|
@@ -201,13 +199,9 @@ class DotDict(dict):
|
|
|
201
199
|
value : Any
|
|
202
200
|
The value to set.
|
|
203
201
|
"""
|
|
204
|
-
self.warn_on_mutation(key)
|
|
205
202
|
value = self.convert_to_nested_dot_dict(value)
|
|
206
203
|
super().__setitem__(key, value)
|
|
207
204
|
|
|
208
|
-
def warn_on_mutation(self, key):
|
|
209
|
-
warnings.warn("Modifying an instance of DotDict(). This class is intended to be immutable.")
|
|
210
|
-
|
|
211
205
|
def __repr__(self) -> str:
|
|
212
206
|
"""Return a string representation of the DotDict.
|
|
213
207
|
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
|
|
11
11
|
"""Logging utilities."""
|
|
12
12
|
|
|
13
|
+
import contextvars
|
|
13
14
|
import logging
|
|
14
|
-
import threading
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
LOGGING_NAME = contextvars.ContextVar("logging_name", default="main")
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
LOGGER = logging.getLogger(__name__)
|
|
@@ -27,7 +27,7 @@ def set_logging_name(name: str) -> None:
|
|
|
27
27
|
name : str
|
|
28
28
|
The name to set for logging.
|
|
29
29
|
"""
|
|
30
|
-
|
|
30
|
+
LOGGING_NAME.set(name)
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
class ThreadCustomFormatter(logging.Formatter):
|
|
@@ -46,7 +46,7 @@ class ThreadCustomFormatter(logging.Formatter):
|
|
|
46
46
|
str
|
|
47
47
|
The formatted log record.
|
|
48
48
|
"""
|
|
49
|
-
record.logging_name =
|
|
49
|
+
record.logging_name = LOGGING_NAME.get()
|
|
50
50
|
return super().format(record)
|
|
51
51
|
|
|
52
52
|
|
|
@@ -58,11 +58,39 @@ def enable_logging_name(name: str = "main") -> None:
|
|
|
58
58
|
name : str, optional
|
|
59
59
|
The default logging name to set, by default "main".
|
|
60
60
|
"""
|
|
61
|
-
thread_local.logging_name = name
|
|
62
61
|
|
|
63
|
-
|
|
62
|
+
logger = logging.getLogger()
|
|
63
|
+
is_rich = any(handler.__class__.__name__ == "CustomRichHandler" for handler in logger.handlers)
|
|
64
|
+
|
|
65
|
+
set_logging_name(name)
|
|
66
|
+
|
|
67
|
+
if is_rich:
|
|
68
|
+
formatter = ThreadCustomFormatter("%(message)s")
|
|
69
|
+
else:
|
|
70
|
+
formatter = ThreadCustomFormatter("%(asctime)s - [%(logging_name)s] - %(levelname)s - %(message)s")
|
|
64
71
|
|
|
65
72
|
logger = logging.getLogger()
|
|
66
73
|
|
|
67
74
|
for handler in logger.handlers:
|
|
68
75
|
handler.setFormatter(formatter)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_rich_handler() -> logging.Handler:
|
|
79
|
+
"""Return a RichHandler with custom formatting for logging."""
|
|
80
|
+
|
|
81
|
+
from rich.logging import RichHandler
|
|
82
|
+
from rich.text import Text
|
|
83
|
+
|
|
84
|
+
class CustomRichHandler(RichHandler):
|
|
85
|
+
def render_message(self, record, message):
|
|
86
|
+
global width
|
|
87
|
+
|
|
88
|
+
text = super().render_message(record, message)
|
|
89
|
+
|
|
90
|
+
if hasattr(record, "logging_name"):
|
|
91
|
+
name = record.logging_name
|
|
92
|
+
text = Text.assemble(f"[{name}]", (" → ", "dim"), text)
|
|
93
|
+
|
|
94
|
+
return text
|
|
95
|
+
|
|
96
|
+
return CustomRichHandler(log_time_format="[%X]")
|