anemoi-utils 0.4.12__tar.gz → 0.4.13__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 (99) hide show
  1. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/.github/dependabot.yml +4 -4
  2. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/.github/labeler.yml +1 -0
  3. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/.github/workflows/downstream-ci-hpc.yml +14 -8
  4. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/.github/workflows/pr-label-conventional-commits.yml +4 -1
  5. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/.github/workflows/pr-label-public.yml +1 -1
  6. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/.github/workflows/python-publish.yml +1 -1
  7. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/.github/workflows/python-pull-request.yml +2 -2
  8. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/.gitignore +2 -0
  9. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/.pre-commit-config.yaml +8 -2
  10. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/.readthedocs.yaml +1 -1
  11. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/.release-please-config.json +3 -2
  12. anemoi_utils-0.4.13/.release-please-manifest.json +3 -0
  13. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/CHANGELOG.md +20 -0
  14. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/PKG-INFO +6 -3
  15. anemoi_utils-0.4.13/docs/_static/logo.png +0 -0
  16. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/pyproject.toml +18 -2
  17. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/__init__.py +1 -0
  18. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/__main__.py +12 -2
  19. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/_version.py +9 -4
  20. anemoi_utils-0.4.13/src/anemoi/utils/caching.py +249 -0
  21. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/checkpoints.py +81 -13
  22. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/cli.py +83 -7
  23. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/commands/__init__.py +4 -0
  24. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/commands/config.py +19 -2
  25. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/commands/requests.py +18 -2
  26. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/compatibility.py +6 -5
  27. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/config.py +254 -23
  28. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/dates.py +204 -50
  29. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/devtools.py +68 -7
  30. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/grib.py +30 -9
  31. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/grids.py +85 -8
  32. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/hindcasts.py +25 -8
  33. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/humanize.py +357 -52
  34. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/logs.py +31 -3
  35. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/mars/__init__.py +46 -12
  36. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/mars/requests.py +15 -1
  37. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/provenance.py +185 -28
  38. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/registry.py +122 -13
  39. anemoi_utils-0.4.13/src/anemoi/utils/remote/__init__.py +680 -0
  40. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/remote/s3.py +252 -29
  41. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/remote/ssh.py +140 -8
  42. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/s3.py +77 -4
  43. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/sanitise.py +52 -7
  44. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/text.py +218 -54
  45. anemoi_utils-0.4.13/src/anemoi/utils/timer.py +150 -0
  46. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi_utils.egg-info/PKG-INFO +6 -3
  47. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi_utils.egg-info/SOURCES.txt +1 -1
  48. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi_utils.egg-info/requires.txt +6 -2
  49. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/tests/test_caching.py +94 -15
  50. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/tests/test_compatibility.py +3 -0
  51. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/tests/test_dates.py +21 -5
  52. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/tests/test_frequency.py +4 -2
  53. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/tests/test_grids.py +2 -1
  54. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/tests/test_provenance.py +2 -2
  55. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/tests/test_remote.py +92 -10
  56. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/tests/test_sanetise.py +4 -2
  57. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/tests/test_utils.py +31 -4
  58. anemoi_utils-0.4.12/.release-please-manifest.json +0 -3
  59. anemoi_utils-0.4.12/docs/_static/logo.png +0 -0
  60. anemoi_utils-0.4.12/src/anemoi/utils/caching.py +0 -124
  61. anemoi_utils-0.4.12/src/anemoi/utils/remote/__init__.py +0 -332
  62. anemoi_utils-0.4.12/src/anemoi/utils/timer.py +0 -74
  63. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/.gitattributes +0 -0
  64. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/.github/CODEOWNERS +0 -0
  65. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/.github/ci-hpc-config.yml +0 -0
  66. {anemoi_utils-0.4.12/.github/PULL_REQUEST_TEMPLATE → anemoi_utils-0.4.13/.github}/pull_request_template.md +0 -0
  67. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/.github/release.yml +0 -0
  68. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/.github/workflows/pr-conventional-commit.yml +0 -0
  69. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/.github/workflows/pr-label-file-based.yml +0 -0
  70. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/.github/workflows/readthedocs-pr-update.yml +0 -0
  71. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/.github/workflows/release-please.yml +0 -0
  72. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/CONTRIBUTORS.md +0 -0
  73. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/LICENSE +0 -0
  74. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/README.md +0 -0
  75. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/docs/Makefile +0 -0
  76. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/docs/_static/style.css +0 -0
  77. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/docs/_templates/.gitkeep +0 -0
  78. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/docs/conf.py +0 -0
  79. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/docs/index.rst +0 -0
  80. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/docs/installing.rst +0 -0
  81. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/docs/modules/checkpoints.rst +0 -0
  82. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/docs/modules/config.rst +0 -0
  83. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/docs/modules/dates.rst +0 -0
  84. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/docs/modules/grib.rst +0 -0
  85. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/docs/modules/humanize.rst +0 -0
  86. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/docs/modules/provenance.rst +0 -0
  87. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/docs/modules/s3.rst +0 -0
  88. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/docs/modules/text.rst +0 -0
  89. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/setup.cfg +0 -0
  90. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/mars/mars.yaml +0 -0
  91. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi/utils/sanitize.py +0 -0
  92. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi_utils.egg-info/dependency_links.txt +0 -0
  93. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi_utils.egg-info/entry_points.txt +0 -0
  94. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/src/anemoi_utils.egg-info/top_level.txt +0 -0
  95. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/tests/test-transfer-data/directory/b/c/x +0 -0
  96. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/tests/test-transfer-data/directory/b/y +0 -0
  97. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/tests/test-transfer-data/directory/exotic filename ;^/"'[=.,#]()/303/252/303/274/303/247/303/262/342/234/205.txt" +0 -0
  98. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/tests/test-transfer-data/directory/z +0 -0
  99. {anemoi_utils-0.4.12 → anemoi_utils-0.4.13}/tests/test-transfer-data/file +0 -0
@@ -4,7 +4,7 @@ updates:
4
4
  directory: "/"
5
5
  schedule:
6
6
  interval: "weekly"
7
- - package-ecosystem: "github-actions"
8
- directory: "/"
9
- schedule:
10
- interval: "monthly"
7
+ # - package-ecosystem: "github-actions"
8
+ # directory: "/"
9
+ # schedule:
10
+ # interval: "monthly"
@@ -23,6 +23,7 @@ config:
23
23
  - changed-files:
24
24
  - any-glob-to-any-file:
25
25
  - "**/src/**/config/**/*"
26
+ - "**/src/anemoi/inference/config.py"
26
27
 
27
28
  CI/CD:
28
29
  - changed-files:
@@ -42,13 +42,19 @@ jobs:
42
42
  with:
43
43
  anemoi-utils: ecmwf/anemoi-utils@${{ github.event.pull_request.head.sha || github.sha }}
44
44
  codecov_upload: true
45
+ # Only run on fedora
46
+ skip_matrix_jobs: |
47
+ gnu@debian-11
48
+ gnu@rocky-8.6
49
+ clang@rocky-8.6
50
+ gnu@ubuntu-22.04
45
51
  secrets: inherit
46
52
 
47
- # Build downstream packages on HPC
48
- downstream-ci-hpc:
49
- name: downstream-ci-hpc
50
- if: ${{ !github.event.pull_request.head.repo.fork && github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci' }}
51
- uses: ecmwf-actions/downstream-ci/.github/workflows/downstream-ci-hpc.yml@main
52
- with:
53
- anemoi-utils: ecmwf/anemoi-utils@${{ github.event.pull_request.head.sha || github.sha }}
54
- secrets: inherit
53
+ # # Build downstream packages on HPC
54
+ # downstream-ci-hpc:
55
+ # name: downstream-ci-hpc
56
+ # if: ${{ !github.event.pull_request.head.repo.fork && github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci' }}
57
+ # uses: ecmwf-actions/downstream-ci/.github/workflows/downstream-ci-hpc.yml@main
58
+ # with:
59
+ # anemoi-utils: ecmwf/anemoi-utils@${{ github.event.pull_request.head.sha || github.sha }}
60
+ # secrets: inherit
@@ -8,6 +8,9 @@ on:
8
8
  types:
9
9
  [opened, reopened, labeled, unlabeled]
10
10
 
11
+ permissions:
12
+ pull-requests: write
13
+
11
14
  jobs:
12
15
  assign-labels:
13
16
  runs-on: ubuntu-latest
@@ -41,5 +44,5 @@ jobs:
41
44
  - type: 'config'
42
45
  nouns: ['config', 'conf', 'configuration']
43
46
  labels: ['config']
44
- maintain-labels-not-matched: false
47
+ maintain-labels-not-matched: true
45
48
  apply-changes: true
@@ -7,4 +7,4 @@ on:
7
7
 
8
8
  jobs:
9
9
  label:
10
- uses: ecmwf-actions/reusable-workflows/.github/workflows/label-pr.yml@v2
10
+ uses: ecmwf/reusable-workflows/.github/workflows/label-pr.yml@v2
@@ -9,5 +9,5 @@ on:
9
9
 
10
10
  jobs:
11
11
  deploy:
12
- uses: ecmwf-actions/reusable-workflows/.github/workflows/cd-pypi.yml@v2
12
+ uses: ecmwf/reusable-workflows/.github/workflows/cd-pypi.yml@v2
13
13
  secrets: inherit
@@ -12,7 +12,7 @@ on:
12
12
 
13
13
  jobs:
14
14
  quality:
15
- uses: ecmwf-actions/reusable-workflows/.github/workflows/qa-precommit-run.yml@v2
15
+ uses: ecmwf/reusable-workflows/.github/workflows/qa-precommit-run.yml@v2
16
16
  with:
17
17
  skip-hooks: "no-commit-to-branch"
18
18
 
@@ -20,6 +20,6 @@ jobs:
20
20
  strategy:
21
21
  matrix:
22
22
  python-version: ["3.9", "3.10", "3.11", "3.12"]
23
- uses: ecmwf-actions/reusable-workflows/.github/workflows/qa-pytest-pyproject.yml@v2
23
+ uses: ecmwf/reusable-workflows/.github/workflows/qa-pytest-pyproject.yml@v2
24
24
  with:
25
25
  python-version: ${{ matrix.python-version }}
@@ -78,6 +78,8 @@ dmypy.json
78
78
 
79
79
  # Data files
80
80
  *.grib
81
+ *.grib1
82
+ *.grib2
81
83
  *.onnx
82
84
  *.ckpt
83
85
  *.npy
@@ -58,7 +58,12 @@ repos:
58
58
  rev: v0.0.14
59
59
  hooks:
60
60
  - id: rstfmt
61
- exclude: 'cli/.*' # Because we use argparse
61
+ exclude: '(cli|schemas)/.*' # Because we use argparse and pydantic sphinx directives
62
+ - repo: https://github.com/b8raoult/pre-commit-docconvert
63
+ rev: "0.1.4"
64
+ hooks:
65
+ - id: docconvert
66
+ args: ["numpy"]
62
67
  - repo: https://github.com/tox-dev/pyproject-fmt
63
68
  rev: "v2.5.0"
64
69
  hooks:
@@ -73,6 +78,7 @@ repos:
73
78
  - --check-overridden # Check overridden methods
74
79
  - --check-protected # Check protected methods
75
80
  - --check-class # Check class docstrings
76
- - --disable=E113 # Disable empty docstrings
81
+ - --disable=SIG101,SIG102 # Disable empty docstrings
77
82
  ci:
78
83
  autoupdate_schedule: monthly
84
+ autoupdate_commit_msg: "chore(deps): pre-commit.ci autoupdate"
@@ -3,7 +3,7 @@ version: 2
3
3
  build:
4
4
  os: ubuntu-22.04
5
5
  tools:
6
- python: "3.11"
6
+ python: "3.10"
7
7
 
8
8
  sphinx:
9
9
  configuration: docs/conf.py
@@ -4,10 +4,11 @@
4
4
  "bump-patch-for-minor-pre-major": true,
5
5
  "separate-pull-requests": true,
6
6
  "always-update": true,
7
- "changelog-type": "github",
7
+ "changelog-type": "default",
8
8
  "include-component-in-tag": false,
9
9
  "include-v-in-tag": false,
10
- "pull-request-title-pattern": "chore${scope}: Preparing Next Release for ${component} ${version}",
10
+ "draft-pull-request": true,
11
+ "pull-request-title-pattern": "chore${scope}: Release${component} ${version}",
11
12
  "pull-request-header": ":robot: Automated Release PR\n\nThis PR was created by `release-please` to prepare the next release. Once merged:\n\n1. A new version tag will be created\n2. A GitHub release will be published\n3. The changelog will be updated\n\nChanges to be included in the next release:",
12
13
  "pull-request-footer": "> [!IMPORTANT]\n> :warning: Merging this PR will:\n> - Create a new release\n> - Trigger deployment pipelines\n> - Update package versions\n\n **Before merging:**\n - Ensure all tests pass\n - Review the changelog carefully\n - Get required approvals\n\n [Release-please documentation](https://github.com/googleapis/release-please)",
13
14
  "packages": {
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.4.13"
3
+ }
@@ -8,6 +8,26 @@ 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.13](https://github.com/ecmwf/anemoi-utils/compare/0.4.12...0.4.13) (2025-03-14)
12
+
13
+
14
+ ### Features
15
+
16
+ * add robust requests ([#112](https://github.com/ecmwf/anemoi-utils/issues/112)) ([5d87227](https://github.com/ecmwf/anemoi-utils/commit/5d87227e6f0b39f087f8a34f238806a2f73480f1))
17
+ * bugfix ([#100](https://github.com/ecmwf/anemoi-utils/issues/100)) ([c016cb4](https://github.com/ecmwf/anemoi-utils/commit/c016cb46c23b6a0575d9d843b06fd6b9f71b9f27))
18
+ * keep yaml formating in error messages ([#108](https://github.com/ecmwf/anemoi-utils/issues/108)) ([3bd6682](https://github.com/ecmwf/anemoi-utils/commit/3bd66828cf19d8e3d7d3fbed27533161b6285828))
19
+ * re-add default values in transfer function ([#101](https://github.com/ecmwf/anemoi-utils/issues/101)) ([6462205](https://github.com/ecmwf/anemoi-utils/commit/6462205ee25fa35a71af047b1fbb04bd3c4ca2c4))
20
+
21
+
22
+ ### Bug Fixes
23
+
24
+ * add optional dependency. boto3 <= 1.36 ([#105](https://github.com/ecmwf/anemoi-utils/issues/105)) ([c8c8393](https://github.com/ecmwf/anemoi-utils/commit/c8c8393ab1e886289541d3aa47a614afe5cd379b))
25
+
26
+
27
+ ### Documentation
28
+
29
+ * update logo ([#96](https://github.com/ecmwf/anemoi-utils/issues/96)) ([c297127](https://github.com/ecmwf/anemoi-utils/commit/c297127e066c92023ca065b3e7d36ac4ab62527e))
30
+
11
31
  ## 0.4.12 (2025-01-30)
12
32
 
13
33
  <!-- Release notes generated using configuration in .github/release.yml at main -->
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: anemoi-utils
3
- Version: 0.4.12
3
+ Version: 0.4.13
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
@@ -226,13 +226,14 @@ Requires-Python: >=3.9
226
226
  License-File: LICENSE
227
227
  Requires-Dist: aniso8601
228
228
  Requires-Dist: importlib-metadata; python_version < "3.10"
229
+ Requires-Dist: multiurl
229
230
  Requires-Dist: numpy
230
231
  Requires-Dist: python-dateutil
231
232
  Requires-Dist: pyyaml
232
233
  Requires-Dist: tomli; python_version < "3.11"
233
234
  Requires-Dist: tqdm
234
235
  Provides-Extra: all
235
- Requires-Dist: anemoi-utils[grib,provenance,text]; extra == "all"
236
+ Requires-Dist: anemoi-utils[grib,provenance,s3,text]; extra == "all"
236
237
  Provides-Extra: dev
237
238
  Requires-Dist: anemoi-utils[all,docs,tests]; extra == "dev"
238
239
  Provides-Extra: docs
@@ -240,7 +241,7 @@ Requires-Dist: nbsphinx; extra == "docs"
240
241
  Requires-Dist: pandoc; extra == "docs"
241
242
  Requires-Dist: requests; extra == "docs"
242
243
  Requires-Dist: sphinx; extra == "docs"
243
- Requires-Dist: sphinx-argparse<0.5; extra == "docs"
244
+ Requires-Dist: sphinx-argparse; extra == "docs"
244
245
  Requires-Dist: sphinx-rtd-theme; extra == "docs"
245
246
  Requires-Dist: termcolor; extra == "docs"
246
247
  Provides-Extra: grib
@@ -248,6 +249,8 @@ Requires-Dist: requests; extra == "grib"
248
249
  Provides-Extra: provenance
249
250
  Requires-Dist: gitpython; extra == "provenance"
250
251
  Requires-Dist: nvsmi; extra == "provenance"
252
+ Provides-Extra: s3
253
+ Requires-Dist: boto3<1.36; extra == "s3"
251
254
  Provides-Extra: tests
252
255
  Requires-Dist: pytest; extra == "tests"
253
256
  Provides-Extra: text
@@ -42,6 +42,7 @@ dynamic = [ "version" ]
42
42
  dependencies = [
43
43
  "aniso8601",
44
44
  "importlib-metadata; python_version<'3.10'",
45
+ "multiurl",
45
46
  "numpy",
46
47
  "python-dateutil",
47
48
  "pyyaml",
@@ -49,7 +50,7 @@ dependencies = [
49
50
  "tqdm",
50
51
  ]
51
52
 
52
- optional-dependencies.all = [ "anemoi-utils[grib,provenance,text]" ]
53
+ optional-dependencies.all = [ "anemoi-utils[grib,provenance,text,s3]" ]
53
54
  optional-dependencies.dev = [ "anemoi-utils[all,docs,tests]" ]
54
55
 
55
56
  optional-dependencies.docs = [
@@ -57,7 +58,7 @@ optional-dependencies.docs = [
57
58
  "pandoc",
58
59
  "requests",
59
60
  "sphinx",
60
- "sphinx-argparse<0.5",
61
+ "sphinx-argparse",
61
62
  "sphinx-rtd-theme",
62
63
  "termcolor",
63
64
  ]
@@ -66,6 +67,10 @@ optional-dependencies.grib = [ "requests" ]
66
67
 
67
68
  optional-dependencies.provenance = [ "gitpython", "nvsmi" ]
68
69
 
70
+ optional-dependencies.s3 = [
71
+ "boto3<1.36",
72
+ ]
73
+
69
74
  optional-dependencies.tests = [ "pytest" ]
70
75
 
71
76
  optional-dependencies.text = [ "termcolor", "wcwidth" ]
@@ -87,3 +92,14 @@ version_file = "src/anemoi/utils/_version.py"
87
92
  markers = [
88
93
  "skip_on_hpc: mark a test that should not be run on HPC",
89
94
  ]
95
+
96
+ [tool.mypy]
97
+ strict = false
98
+ exclude = [
99
+ "docs/**",
100
+ ]
101
+
102
+ [tool.pydocstringformatter]
103
+ write = true
104
+ exclude = "docs/**"
105
+ style = "numpydoc"
@@ -7,6 +7,7 @@
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
+ """Anemoi Utils package."""
10
11
 
11
12
  try:
12
13
  # NOTE: the `_version.py` file must not be present in the git repository
@@ -7,6 +7,8 @@
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
+ from typing import Any
11
+
10
12
  from anemoi.utils.cli import cli_main
11
13
  from anemoi.utils.cli import make_parser
12
14
 
@@ -15,11 +17,19 @@ from .commands import COMMANDS
15
17
 
16
18
 
17
19
  # For read-the-docs
18
- def create_parser():
20
+ def create_parser() -> Any:
21
+ """Create the argument parser for the CLI.
22
+
23
+ Returns
24
+ -------
25
+ Any
26
+ The argument parser
27
+ """
19
28
  return make_parser(__doc__, COMMANDS)
20
29
 
21
30
 
22
- def main():
31
+ def main() -> None:
32
+ """Main entry point for the CLI."""
23
33
  cli_main(__version__, __doc__, COMMANDS)
24
34
 
25
35
 
@@ -1,8 +1,13 @@
1
- # file generated by setuptools_scm
1
+ # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
+
4
+ __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
5
+
3
6
  TYPE_CHECKING = False
4
7
  if TYPE_CHECKING:
5
- from typing import Tuple, Union
8
+ from typing import Tuple
9
+ from typing import Union
10
+
6
11
  VERSION_TUPLE = Tuple[Union[int, str], ...]
7
12
  else:
8
13
  VERSION_TUPLE = object
@@ -12,5 +17,5 @@ __version__: str
12
17
  __version_tuple__: VERSION_TUPLE
13
18
  version_tuple: VERSION_TUPLE
14
19
 
15
- __version__ = version = '0.4.12'
16
- __version_tuple__ = version_tuple = (0, 4, 12)
20
+ __version__ = version = '0.4.13'
21
+ __version_tuple__ = version_tuple = (0, 4, 13)
@@ -0,0 +1,249 @@
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 hashlib
12
+ import json
13
+ import os
14
+ import time
15
+ from threading import Lock
16
+ from typing import Any
17
+ from typing import Callable
18
+ from typing import Optional
19
+
20
+ import numpy as np
21
+
22
+ LOCK = Lock()
23
+ CACHE = {}
24
+
25
+
26
+ def _get_cache_path(collection: str) -> str:
27
+ """Get the cache path for a collection.
28
+
29
+ Parameters
30
+ ----------
31
+ collection : str
32
+ The name of the collection
33
+
34
+ Returns
35
+ -------
36
+ str
37
+ The cache path
38
+ """
39
+ return os.path.join(os.path.expanduser("~"), ".cache", "anemoi", collection)
40
+
41
+
42
+ def clean_cache(collection: str = "default") -> None:
43
+ """Clean the cache for a collection.
44
+
45
+ Parameters
46
+ ----------
47
+ collection : str, optional
48
+ The name of the collection, by default "default"
49
+ """
50
+ global CACHE
51
+ CACHE = {}
52
+ path = _get_cache_path(collection)
53
+ if not os.path.exists(path):
54
+ return
55
+ for filename in os.listdir(path):
56
+ os.remove(os.path.join(path, filename))
57
+
58
+
59
+ class Cacher:
60
+ """This class implements a simple caching mechanism.
61
+ Private class, do not use directly.
62
+ """
63
+
64
+ def __init__(self, collection: str, expires: Optional[int]):
65
+ """Initialize the Cacher.
66
+
67
+ Parameters
68
+ ----------
69
+ collection : str
70
+ The name of the collection
71
+ expires : int, optional
72
+ The expiration time in seconds, or None for no expiration
73
+ """
74
+ self.collection = collection
75
+ self.expires = expires
76
+
77
+ def __call__(self, func: Callable) -> Callable:
78
+ """Wrap a function with caching.
79
+
80
+ Parameters
81
+ ----------
82
+ func : Callable
83
+ The function to wrap
84
+
85
+ Returns
86
+ -------
87
+ Callable
88
+ The wrapped function
89
+ """
90
+ full = f"{func.__module__}.{func.__name__}"
91
+
92
+ def wrapped(*args, **kwargs):
93
+ with LOCK:
94
+ return self.cache(
95
+ (full, args, kwargs),
96
+ lambda: func(*args, **kwargs),
97
+ )
98
+
99
+ return wrapped
100
+
101
+ def cache(self, key: tuple, proc: Callable) -> Any:
102
+ """Cache the result of a function.
103
+
104
+ Parameters
105
+ ----------
106
+ key : tuple
107
+ The cache key
108
+ proc : Callable
109
+ The function to call if the result is not cached
110
+
111
+ Returns
112
+ -------
113
+ Any
114
+ The cached result
115
+ """
116
+ key = json.dumps(key, sort_keys=True)
117
+ m = hashlib.md5()
118
+ m.update(key.encode("utf-8"))
119
+ m = m.hexdigest()
120
+
121
+ if m in CACHE:
122
+ return CACHE[m]
123
+
124
+ path = _get_cache_path(self.collection)
125
+
126
+ filename = os.path.join(path, m) + self.ext
127
+ if os.path.exists(filename):
128
+ data = self.load(filename)
129
+ if self.expires is None or data["expires"] > time.time():
130
+ if data["key"] == key:
131
+ return data["value"]
132
+
133
+ value = proc()
134
+ data = {"key": key, "value": value}
135
+ if self.expires is not None:
136
+ data["expires"] = time.time() + self.expires
137
+
138
+ os.makedirs(path, exist_ok=True)
139
+ temp_filename = self.save(filename, data)
140
+ os.rename(temp_filename, filename)
141
+
142
+ CACHE[m] = value
143
+ return value
144
+
145
+
146
+ class JsonCacher(Cacher):
147
+ """Cacher that uses JSON files."""
148
+
149
+ ext = ""
150
+
151
+ def save(self, path: str, data: dict) -> str:
152
+ """Save data to a JSON file.
153
+
154
+ Parameters
155
+ ----------
156
+ path : str
157
+ The path to the JSON file
158
+ data : dict
159
+ The data to save
160
+
161
+ Returns
162
+ -------
163
+ str
164
+ The temporary file path
165
+ """
166
+ temp_path = path + ".tmp"
167
+ with open(temp_path, "w") as f:
168
+ json.dump(data, f)
169
+ return temp_path
170
+
171
+ def load(self, path: str) -> dict:
172
+ """Load data from a JSON file.
173
+
174
+ Parameters
175
+ ----------
176
+ path : str
177
+ The path to the JSON file
178
+
179
+ Returns
180
+ -------
181
+ dict
182
+ The loaded data
183
+ """
184
+ with open(path, "r") as f:
185
+ return json.load(f)
186
+
187
+
188
+ class NpzCacher(Cacher):
189
+ """Cacher that uses NPZ files."""
190
+
191
+ ext = ".npz"
192
+
193
+ def save(self, path: str, data: dict) -> str:
194
+ """Save data to an NPZ file.
195
+
196
+ Parameters
197
+ ----------
198
+ path : str
199
+ The path to the NPZ file
200
+ data : dict
201
+ The data to save
202
+
203
+ Returns
204
+ -------
205
+ str
206
+ The temporary file path
207
+ """
208
+ temp_path = path + ".tmp.npz"
209
+ np.savez(temp_path, **data)
210
+ return temp_path
211
+
212
+ def load(self, path: str) -> dict:
213
+ """Load data from an NPZ file.
214
+
215
+ Parameters
216
+ ----------
217
+ path : str
218
+ The path to the NPZ file
219
+
220
+ Returns
221
+ -------
222
+ dict
223
+ The loaded data
224
+ """
225
+ return np.load(path, allow_pickle=True)
226
+
227
+
228
+ # This function is the main entry point for the caching mechanism for the other anemoi packages
229
+ def cached(collection: str = "default", expires: Optional[int] = None, encoding: str = "json") -> Callable:
230
+ """Decorator to cache the result of a function.
231
+
232
+ Default is to use a json file to store the cache, but you can also use npz files
233
+ to cache dict of numpy arrays.
234
+
235
+ Parameters
236
+ ----------
237
+ collection : str, optional
238
+ The name of the collection, by default "default"
239
+ expires : int, optional
240
+ The expiration time in seconds, or None for no expiration, by default None
241
+ encoding : str, optional
242
+ The encoding type, either "json" or "npz", by default "json"
243
+
244
+ Returns
245
+ -------
246
+ Callable
247
+ The decorated function
248
+ """
249
+ return dict(json=JsonCacher, npz=NpzCacher)[encoding](collection, expires)