anemoi-utils 0.3.15__tar.gz → 0.3.17__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 (66) hide show
  1. anemoi_utils-0.3.17/.gitattributes +1 -0
  2. anemoi_utils-0.3.17/.github/CODEOWNERS +6 -0
  3. anemoi_utils-0.3.17/.github/ci-hpc-config.yml +8 -0
  4. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/.github/workflows/changelog-pr-update.yml +3 -0
  5. anemoi_utils-0.3.17/.github/workflows/changelog-release-update.yml +35 -0
  6. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/.github/workflows/ci.yml +4 -1
  7. anemoi_utils-0.3.17/.github/workflows/python-publish.yml +27 -0
  8. anemoi_utils-0.3.17/.github/workflows/python-pull-request.yml +23 -0
  9. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/.gitignore +1 -0
  10. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/.pre-commit-config.yaml +26 -9
  11. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/CHANGELOG.md +9 -0
  12. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/PKG-INFO +5 -16
  13. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/pyproject.toml +14 -48
  14. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi/utils/_version.py +2 -2
  15. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi/utils/caching.py +1 -0
  16. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi/utils/checkpoints.py +6 -4
  17. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi/utils/cli.py +2 -0
  18. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi/utils/config.py +20 -6
  19. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi/utils/dates.py +62 -9
  20. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi/utils/grib.py +3 -3
  21. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi/utils/humanize.py +24 -20
  22. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi/utils/provenance.py +6 -1
  23. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi/utils/s3.py +12 -8
  24. anemoi_utils-0.3.17/src/anemoi/utils/sanetize.py +10 -0
  25. anemoi_utils-0.3.17/src/anemoi/utils/sanitise.py +115 -0
  26. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi/utils/text.py +3 -1
  27. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi_utils.egg-info/PKG-INFO +5 -16
  28. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi_utils.egg-info/SOURCES.txt +8 -0
  29. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi_utils.egg-info/requires.txt +4 -15
  30. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/tests/test_dates.py +11 -7
  31. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/tests/test_frequency.py +4 -2
  32. anemoi_utils-0.3.17/tests/test_sanetise.py +69 -0
  33. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/tests/test_utils.py +4 -1
  34. anemoi_utils-0.3.15/.github/workflows/python-publish.yml +0 -54
  35. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/.github/workflows/label-public-pr.yml +0 -0
  36. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/.github/workflows/readthedocs-pr-update.yml +0 -0
  37. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/.readthedocs.yaml +0 -0
  38. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/LICENSE +0 -0
  39. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/README.md +0 -0
  40. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/docs/Makefile +0 -0
  41. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/docs/_static/logo.png +0 -0
  42. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/docs/_static/style.css +0 -0
  43. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/docs/_templates/.gitkeep +0 -0
  44. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/docs/conf.py +0 -0
  45. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/docs/index.rst +0 -0
  46. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/docs/installing.rst +0 -0
  47. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/docs/modules/checkpoints.rst +0 -0
  48. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/docs/modules/config.rst +0 -0
  49. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/docs/modules/dates.rst +0 -0
  50. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/docs/modules/grib.rst +0 -0
  51. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/docs/modules/humanize.rst +0 -0
  52. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/docs/modules/provenance.rst +0 -0
  53. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/docs/modules/s3.rst +0 -0
  54. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/docs/modules/text.rst +0 -0
  55. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/setup.cfg +0 -0
  56. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi/utils/__init__.py +0 -0
  57. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi/utils/__main__.py +0 -0
  58. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi/utils/commands/__init__.py +0 -0
  59. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi/utils/commands/config.py +0 -0
  60. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi/utils/hindcasts.py +0 -0
  61. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi/utils/mars/__init__.py +0 -0
  62. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi/utils/mars/mars.yaml +0 -0
  63. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi/utils/timer.py +0 -0
  64. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi_utils.egg-info/dependency_links.txt +0 -0
  65. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi_utils.egg-info/entry_points.txt +0 -0
  66. {anemoi_utils-0.3.15 → anemoi_utils-0.3.17}/src/anemoi_utils.egg-info/top_level.txt +0 -0
@@ -0,0 +1 @@
1
+ CHANGELOG.md merge=union
@@ -0,0 +1,6 @@
1
+ # CODEOWNERS file
2
+
3
+ # Protect workflow files
4
+ /.github/ @theissenhelen @jesperdramsch @gmertes
5
+ /.pre-commit-config.yaml @theissenhelen @jesperdramsch @gmertes
6
+ /pyproject.toml @theissenhelen @jesperdramsch @gmertes
@@ -0,0 +1,8 @@
1
+ build:
2
+ python: '3.10'
3
+ modules:
4
+ - ninja
5
+ parallel: 64
6
+
7
+ pytest_cmd: |
8
+ python -m pytest -vv -m 'not notebook and not no_cache_init' --cov=. --cov-report=xml
@@ -5,6 +5,9 @@ on:
5
5
  branches:
6
6
  - main
7
7
  - develop
8
+ paths-ignore:
9
+ - .pre-commit-config.yaml
10
+ - .readthedocs.yaml
8
11
  jobs:
9
12
  Check-Changelog:
10
13
  name: Check Changelog Action
@@ -0,0 +1,35 @@
1
+ # .github/workflows/update-changelog.yaml
2
+ name: "Update Changelog"
3
+
4
+ on:
5
+ release:
6
+ types: [released]
7
+ workflow_dispatch: ~
8
+
9
+ permissions:
10
+ pull-requests: write
11
+ contents: write
12
+
13
+ jobs:
14
+ update:
15
+ runs-on: ubuntu-latest
16
+
17
+ steps:
18
+ - name: Checkout code
19
+ uses: actions/checkout@v4
20
+ with:
21
+ ref: ${{ github.event.release.target_commitish }}
22
+
23
+ - name: Update Changelog
24
+ uses: stefanzweifel/changelog-updater-action@v1
25
+ with:
26
+ latest-version: ${{ github.event.release.tag_name }}
27
+ heading-text: ${{ github.event.release.name }}
28
+
29
+ - name: Create Pull Request
30
+ uses: peter-evans/create-pull-request@v6
31
+ with:
32
+ branch: docs/changelog-update-${{ github.event.release.tag_name }}
33
+ title: '[Changelog] Update to ${{ github.event.release.tag_name }}'
34
+ add-paths: |
35
+ CHANGELOG.md
@@ -8,6 +8,9 @@ on:
8
8
  - 'develop'
9
9
  tags-ignore:
10
10
  - '**'
11
+ paths:
12
+ - "src/**"
13
+ - "tests/**"
11
14
 
12
15
  # Trigger the workflow on pull request
13
16
  pull_request: ~
@@ -34,7 +37,7 @@ jobs:
34
37
  downstream-ci-hpc:
35
38
  name: downstream-ci-hpc
36
39
  if: ${{ !github.event.pull_request.head.repo.fork && github.event.action != 'labeled' || github.event.label.name == 'approved-for-ci' }}
37
- uses: ecmwf-actions/downstream-ci/.github/workflows/downstream-ci.yml@main
40
+ uses: ecmwf-actions/downstream-ci/.github/workflows/downstream-ci-hpc.yml@main
38
41
  with:
39
42
  anemoi-utils: ecmwf/anemoi-utils@${{ github.event.pull_request.head.sha || github.sha }}
40
43
  secrets: inherit
@@ -0,0 +1,27 @@
1
+ # This workflow will upload a Python Package using Twine when a release is created
2
+ # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
3
+
4
+ name: Upload Python Package
5
+
6
+ on:
7
+ release:
8
+ types: [created]
9
+
10
+ jobs:
11
+ quality:
12
+ uses: ecmwf-actions/reusable-workflows/.github/workflows/qa-precommit-run.yml@v2
13
+ with:
14
+ skip-hooks: "no-commit-to-branch"
15
+
16
+ checks:
17
+ strategy:
18
+ matrix:
19
+ python-version: ["3.9", "3.10"]
20
+ uses: ecmwf-actions/reusable-workflows/.github/workflows/qa-pytest-pyproject.yml@v2
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ deploy:
25
+ needs: [checks, quality]
26
+ uses: ecmwf-actions/reusable-workflows/.github/workflows/cd-pypi.yml@v2
27
+ secrets: inherit
@@ -0,0 +1,23 @@
1
+ # This workflow will upload a Python Package using Twine when a release is created
2
+ # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
3
+
4
+ name: Code Quality checks for PRs
5
+
6
+ on:
7
+ push:
8
+ pull_request:
9
+ types: [opened, synchronize, reopened]
10
+
11
+ jobs:
12
+ quality:
13
+ uses: ecmwf-actions/reusable-workflows/.github/workflows/qa-precommit-run.yml@v2
14
+ with:
15
+ skip-hooks: "no-commit-to-branch"
16
+
17
+ checks:
18
+ strategy:
19
+ matrix:
20
+ python-version: ["3.9", "3.10"]
21
+ uses: ecmwf-actions/reusable-workflows/.github/workflows/qa-pytest-pyproject.yml@v2
22
+ with:
23
+ python-version: ${{ matrix.python-version }}
@@ -121,6 +121,7 @@ celerybeat.pid
121
121
 
122
122
  # Environments
123
123
  .env
124
+ .envrc
124
125
  .venv
125
126
  env/
126
127
  venv/
@@ -20,8 +20,14 @@ repos:
20
20
  - id: no-commit-to-branch # Prevent committing to main / master
21
21
  - id: check-added-large-files # Check for large files added to git
22
22
  - id: check-merge-conflict # Check for files that contain merge conflict
23
+ - repo: https://github.com/pre-commit/pygrep-hooks
24
+ rev: v1.10.0 # Use the ref you want to point at
25
+ hooks:
26
+ - id: python-use-type-annotations # Check for missing type annotations
27
+ - id: python-check-blanket-noqa # Check for # noqa: all
28
+ - id: python-no-log-warn # Check for log.warn
23
29
  - repo: https://github.com/psf/black-pre-commit-mirror
24
- rev: 24.4.2
30
+ rev: 24.8.0
25
31
  hooks:
26
32
  - id: black
27
33
  args: [--line-length=120]
@@ -34,7 +40,7 @@ repos:
34
40
  - --force-single-line-imports
35
41
  - --profile black
36
42
  - repo: https://github.com/astral-sh/ruff-pre-commit
37
- rev: v0.4.6
43
+ rev: v0.6.4
38
44
  hooks:
39
45
  - id: ruff
40
46
  # Next line if for documenation cod snippets
@@ -45,7 +51,7 @@ repos:
45
51
  - --exit-non-zero-on-fix
46
52
  - --preview
47
53
  - repo: https://github.com/sphinx-contrib/sphinx-lint
48
- rev: v0.9.1
54
+ rev: v1.0.0
49
55
  hooks:
50
56
  - id: sphinx-lint
51
57
  # For now, we use it. But it does not support a lot of sphinx features
@@ -59,12 +65,23 @@ repos:
59
65
  hooks:
60
66
  - id: docconvert
61
67
  args: ["numpy"]
62
- - repo: https://github.com/b8raoult/optional-dependencies-all
63
- rev: "0.0.6"
64
- hooks:
65
- - id: optional-dependencies-all
66
- args: ["--inplace", "--exclude-keys=dev,docs,tests", "--group=dev=all,docs,tests"]
67
68
  - repo: https://github.com/tox-dev/pyproject-fmt
68
- rev: "2.1.3"
69
+ rev: "2.2.3"
69
70
  hooks:
70
71
  - id: pyproject-fmt
72
+ - repo: https://github.com/jshwi/docsig # Check docstrings against function sig
73
+ rev: v0.60.1
74
+ hooks:
75
+ - id: docsig
76
+ args:
77
+ - --ignore-no-params # Allow docstrings without parameters
78
+ - --check-dunders # Check dunder methods
79
+ - --check-overridden # Check overridden methods
80
+ - --check-protected # Check protected methods
81
+ - --check-class # Check class docstrings
82
+ - --disable=E113 # Disable empty docstrings
83
+ - --summary # Print a summary
84
+ ci:
85
+ autoupdate_schedule: monthly
86
+ ci:
87
+ autoupdate_schedule: monthly
@@ -11,8 +11,17 @@ Keep it human-readable, your future self will thank you!
11
11
  ## [Unreleased]
12
12
 
13
13
  ### Added
14
+ - Codeowners file
15
+ - Pygrep precommit hooks
16
+ - Docsig precommit hooks
17
+ - Changelog merge strategy- Codeowners file
18
+ - Create dependency on wcwidth. MIT licence.
19
+ - Add anonimize() function.
14
20
 
15
21
  ### Changed
22
+ - downstream-ci should only runs for changes in src and tests
23
+ - bugfixes for CI
24
+ - python3.9 support
16
25
 
17
26
  ### Removed
18
27
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: anemoi-utils
3
- Version: 0.3.15
3
+ Version: 0.3.17
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
@@ -223,26 +223,14 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
223
223
  Classifier: Programming Language :: Python :: Implementation :: PyPy
224
224
  Requires-Python: >=3.9
225
225
  License-File: LICENSE
226
- Requires-Dist: isodate
226
+ Requires-Dist: aniso8601
227
227
  Requires-Dist: pyyaml
228
228
  Requires-Dist: tomli
229
229
  Requires-Dist: tqdm
230
230
  Provides-Extra: all
231
- Requires-Dist: gitpython; extra == "all"
232
- Requires-Dist: nvsmi; extra == "all"
233
- Requires-Dist: requests; extra == "all"
234
- Requires-Dist: termcolor; extra == "all"
231
+ Requires-Dist: anemoi-utils[grib,provenance,text]; extra == "all"
235
232
  Provides-Extra: dev
236
- Requires-Dist: gitpython; extra == "dev"
237
- Requires-Dist: nbsphinx; extra == "dev"
238
- Requires-Dist: nvsmi; extra == "dev"
239
- Requires-Dist: pandoc; extra == "dev"
240
- Requires-Dist: pytest; extra == "dev"
241
- Requires-Dist: requests; extra == "dev"
242
- Requires-Dist: sphinx; extra == "dev"
243
- Requires-Dist: sphinx-argparse<0.5; extra == "dev"
244
- Requires-Dist: sphinx-rtd-theme; extra == "dev"
245
- Requires-Dist: termcolor; extra == "dev"
233
+ Requires-Dist: anemoi-utils[all,docs,tests]; extra == "dev"
246
234
  Provides-Extra: docs
247
235
  Requires-Dist: nbsphinx; extra == "docs"
248
236
  Requires-Dist: pandoc; extra == "docs"
@@ -260,3 +248,4 @@ Provides-Extra: tests
260
248
  Requires-Dist: pytest; extra == "tests"
261
249
  Provides-Extra: text
262
250
  Requires-Dist: termcolor; extra == "text"
251
+ Requires-Dist: wcwidth; extra == "text"
@@ -10,19 +10,13 @@
10
10
  # https://packaging.python.org/en/latest/guides/writing-pyproject-toml/
11
11
 
12
12
  [build-system]
13
- requires = [
14
- "setuptools>=60",
15
- "setuptools-scm>=8",
16
- ]
13
+ requires = [ "setuptools>=60", "setuptools-scm>=8" ]
17
14
 
18
15
  [project]
19
16
  name = "anemoi-utils"
20
17
 
21
18
  description = "A package to hold various functions to support training of ML models on ECMWF data."
22
- keywords = [
23
- "ai",
24
- "tools",
25
- ]
19
+ keywords = [ "ai", "tools" ]
26
20
 
27
21
  license = { file = "LICENSE" }
28
22
  authors = [
@@ -45,35 +39,16 @@ classifiers = [
45
39
  "Programming Language :: Python :: Implementation :: PyPy",
46
40
  ]
47
41
 
48
- dynamic = [
49
- "version",
50
- ]
42
+ dynamic = [ "version" ]
51
43
  dependencies = [
52
- "isodate",
53
-
44
+ "aniso8601",
54
45
  "pyyaml",
55
- "tomli", # Only needed before 3.11
46
+ "tomli", # Only needed before 3.11
56
47
  "tqdm",
57
48
  ]
58
49
 
59
- optional-dependencies.all = [
60
- "gitpython",
61
- "nvsmi",
62
- "requests",
63
- "termcolor",
64
- ]
65
- optional-dependencies.dev = [
66
- "gitpython",
67
- "nbsphinx",
68
- "nvsmi",
69
- "pandoc",
70
- "pytest",
71
- "requests",
72
- "sphinx",
73
- "sphinx-argparse<0.5",
74
- "sphinx-rtd-theme",
75
- "termcolor",
76
- ]
50
+ optional-dependencies.all = [ "anemoi-utils[grib,provenance,text]" ]
51
+ optional-dependencies.dev = [ "anemoi-utils[all,docs,tests]" ]
77
52
 
78
53
  optional-dependencies.docs = [
79
54
  "nbsphinx",
@@ -85,21 +60,14 @@ optional-dependencies.docs = [
85
60
  "termcolor",
86
61
  ]
87
62
 
88
- optional-dependencies.grib = [
89
- "requests",
90
- ]
63
+ optional-dependencies.grib = [ "requests" ]
91
64
 
92
- optional-dependencies.provenance = [
93
- "gitpython",
94
- "nvsmi",
95
- ]
96
- optional-dependencies.tests = [
97
- "pytest",
98
- ]
65
+ optional-dependencies.provenance = [ "gitpython", "nvsmi" ]
66
+
67
+ optional-dependencies.tests = [ "pytest" ]
68
+
69
+ optional-dependencies.text = [ "termcolor", "wcwidth" ]
99
70
 
100
- optional-dependencies.text = [
101
- "termcolor",
102
- ]
103
71
  urls.Documentation = "https://anemoi-utils.readthedocs.io/"
104
72
  urls.Homepage = "https://github.com/ecmwf/anemoi-utils/"
105
73
  urls.Issues = "https://github.com/ecmwf/anemoi-utils/issues"
@@ -108,9 +76,7 @@ urls.Repository = "https://github.com/ecmwf/anemoi-utils/"
108
76
  scripts.anemoi-utils = "anemoi.utils.__main__:main"
109
77
 
110
78
  [tool.setuptools.package-data]
111
- "anemoi.utils.mars" = [
112
- "*.yaml",
113
- ]
79
+ "anemoi.utils.mars" = [ "*.yaml" ]
114
80
 
115
81
  [tool.setuptools_scm]
116
82
  version_file = "src/anemoi/utils/_version.py"
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.3.15'
16
- __version_tuple__ = version_tuple = (0, 3, 15)
15
+ __version__ = version = '0.3.17'
16
+ __version_tuple__ = version_tuple = (0, 3, 17)
@@ -49,6 +49,7 @@ def cache(key, proc, collection="default", expires=None):
49
49
 
50
50
 
51
51
  class cached:
52
+ """Decorator to cache the result of a function."""
52
53
 
53
54
  def __init__(self, collection="default", expires=None):
54
55
  self.collection = collection
@@ -47,7 +47,7 @@ def has_metadata(path: str, name: str = DEFAULT_NAME) -> bool:
47
47
  return False
48
48
 
49
49
 
50
- def load_metadata(path: str, name: str = DEFAULT_NAME):
50
+ def load_metadata(path: str, name: str = DEFAULT_NAME) -> dict:
51
51
  """Load metadata from a checkpoint file
52
52
 
53
53
  Parameters
@@ -59,8 +59,8 @@ def load_metadata(path: str, name: str = DEFAULT_NAME):
59
59
 
60
60
  Returns
61
61
  -------
62
- JSON
63
- The content of the metadata file
62
+ dict
63
+ The content of the metadata file from JSON
64
64
 
65
65
  Raises
66
66
  ------
@@ -82,7 +82,7 @@ def load_metadata(path: str, name: str = DEFAULT_NAME):
82
82
  raise ValueError(f"Could not find '{name}' in {path}.")
83
83
 
84
84
 
85
- def save_metadata(path, metadata, name=DEFAULT_NAME, folder=DEFAULT_FOLDER):
85
+ def save_metadata(path, metadata, name=DEFAULT_NAME, folder=DEFAULT_FOLDER) -> None:
86
86
  """Save metadata to a checkpoint file
87
87
 
88
88
  Parameters
@@ -93,6 +93,8 @@ def save_metadata(path, metadata, name=DEFAULT_NAME, folder=DEFAULT_FOLDER):
93
93
  A JSON serializable object
94
94
  name : str, optional
95
95
  The name of the metadata file in the zip archive
96
+ folder : str, optional
97
+ The folder where the metadata file will be saved
96
98
  """
97
99
  with zipfile.ZipFile(path, "a") as zipf:
98
100
 
@@ -55,6 +55,8 @@ def make_parser(description, commands):
55
55
 
56
56
 
57
57
  class Failed(Command):
58
+ """Command not available."""
59
+
58
60
  def __init__(self, name, error):
59
61
  self.name = name
60
62
  self.error = error
@@ -5,6 +5,7 @@
5
5
  # granted to it by virtue of its status as an intergovernmental organisation
6
6
  # nor does it submit to any jurisdiction.
7
7
 
8
+ from __future__ import annotations
8
9
 
9
10
  import json
10
11
  import logging
@@ -175,9 +176,14 @@ def config_path(name="settings.toml"):
175
176
  return full
176
177
 
177
178
 
178
- def load_any_dict_format(path):
179
+ def load_any_dict_format(path) -> dict:
179
180
  """Load a configuration file in any supported format: JSON, YAML and TOML.
180
181
 
182
+ Parameters
183
+ ----------
184
+ path : str
185
+ The path to the configuration file.
186
+
181
187
  Returns
182
188
  -------
183
189
  dict
@@ -243,7 +249,7 @@ def _load_config(name="settings.toml", secrets=None, defaults=None):
243
249
  return CONFIG[key]
244
250
 
245
251
 
246
- def _save_config(name, data):
252
+ def _save_config(name, data) -> None:
247
253
  CONFIG.pop(name, None)
248
254
 
249
255
  conf = config_path(name)
@@ -265,7 +271,7 @@ def _save_config(name, data):
265
271
  f.write(data)
266
272
 
267
273
 
268
- def save_config(name, data):
274
+ def save_config(name, data) -> None:
269
275
  """Save a configuration file.
270
276
 
271
277
  Parameters
@@ -281,13 +287,17 @@ def save_config(name, data):
281
287
  _save_config(name, data)
282
288
 
283
289
 
284
- def load_config(name="settings.toml", secrets=None, defaults=None):
290
+ def load_config(name="settings.toml", secrets=None, defaults=None) -> DotDict | str:
285
291
  """Read a configuration file.
286
292
 
287
293
  Parameters
288
294
  ----------
289
295
  name : str, optional
290
296
  The name of the config file to read, by default "settings.toml"
297
+ secrets : str or list, optional
298
+ The name of the secrets file, by default None
299
+ defaults : str or dict, optional
300
+ The name of the defaults file, by default None
291
301
 
292
302
  Returns
293
303
  -------
@@ -299,7 +309,7 @@ def load_config(name="settings.toml", secrets=None, defaults=None):
299
309
  return _load_config(name, secrets, defaults)
300
310
 
301
311
 
302
- def load_raw_config(name, default=None):
312
+ def load_raw_config(name, default=None) -> DotDict | str:
303
313
 
304
314
  path = config_path(name)
305
315
  if os.path.exists(path):
@@ -308,13 +318,17 @@ def load_raw_config(name, default=None):
308
318
  return default
309
319
 
310
320
 
311
- def check_config_mode(name="settings.toml", secrets_name=None, secrets=None):
321
+ def check_config_mode(name="settings.toml", secrets_name=None, secrets=None) -> None:
312
322
  """Check that a configuration file is secure.
313
323
 
314
324
  Parameters
315
325
  ----------
316
326
  name : str, optional
317
327
  The name of the configuration file, by default "settings.toml"
328
+ secrets_name : str, optional
329
+ The name of the secrets file, by default None
330
+ secrets : list, optional
331
+ The list of secrets to check, by default None
318
332
 
319
333
  Raises
320
334
  ------
@@ -10,12 +10,20 @@ import calendar
10
10
  import datetime
11
11
  import re
12
12
 
13
- import isodate
13
+ import aniso8601
14
14
 
15
- from .hindcasts import HindcastDatesTimes
15
+
16
+ def normalise_frequency(frequency):
17
+ if isinstance(frequency, int):
18
+ return frequency
19
+ assert isinstance(frequency, str), (type(frequency), frequency)
20
+
21
+ unit = frequency[-1].lower()
22
+ v = int(frequency[:-1])
23
+ return {"h": v, "d": v * 24}[unit]
16
24
 
17
25
 
18
- def _no_time_zone(date):
26
+ def _no_time_zone(date) -> datetime.datetime:
19
27
  """Remove time zone information from a date.
20
28
 
21
29
  Parameters
@@ -33,7 +41,7 @@ def _no_time_zone(date):
33
41
 
34
42
 
35
43
  # this function is use in anemoi-datasets
36
- def as_datetime(date, keep_time_zone=False):
44
+ def as_datetime(date, keep_time_zone=False) -> datetime.datetime:
37
45
  """Convert a date to a datetime object, removing any time zone information.
38
46
 
39
47
  Parameters
@@ -63,7 +71,41 @@ def as_datetime(date, keep_time_zone=False):
63
71
  raise ValueError(f"Invalid date type: {type(date)}")
64
72
 
65
73
 
66
- def frequency_to_timedelta(frequency):
74
+ def _as_datetime_list(date, default_increment):
75
+ if isinstance(date, (list, tuple)):
76
+ for d in date:
77
+ yield from _as_datetime_list(d, default_increment)
78
+
79
+ if isinstance(date, str):
80
+ # Check for ISO format
81
+ try:
82
+ start, end = aniso8601.parse_interval(date)
83
+ while start <= end:
84
+ yield as_datetime(start)
85
+ start += default_increment
86
+
87
+ return
88
+
89
+ except aniso8601.exceptions.ISOFormatError:
90
+ pass
91
+
92
+ try:
93
+ intervals = aniso8601.parse_repeating_interval(date)
94
+ for date in intervals:
95
+ yield as_datetime(date)
96
+ return
97
+ except aniso8601.exceptions.ISOFormatError:
98
+ pass
99
+
100
+ yield as_datetime(date)
101
+
102
+
103
+ def as_datetime_list(date, default_increment=1):
104
+ default_increment = frequency_to_timedelta(default_increment)
105
+ return list(_as_datetime_list(date, default_increment))
106
+
107
+
108
+ def frequency_to_timedelta(frequency) -> datetime.timedelta:
67
109
  """Convert a frequency to a timedelta object.
68
110
 
69
111
  Parameters
@@ -120,14 +162,14 @@ def frequency_to_timedelta(frequency):
120
162
 
121
163
  # ISO8601
122
164
  try:
123
- return isodate.parse_duration(frequency)
124
- except isodate.isoerror.ISO8601Error:
165
+ return aniso8601.parse_duration(frequency)
166
+ except aniso8601.exceptions.ISOFormatError:
125
167
  pass
126
168
 
127
169
  raise ValueError(f"Cannot convert frequency {frequency} to timedelta")
128
170
 
129
171
 
130
- def frequency_to_string(frequency):
172
+ def frequency_to_string(frequency) -> str:
131
173
  """Convert a frequency (i.e. a datetime.timedelta) to a string.
132
174
 
133
175
  Parameters
@@ -174,7 +216,7 @@ def frequency_to_string(frequency):
174
216
  return str(frequency)
175
217
 
176
218
 
177
- def frequency_to_seconds(frequency):
219
+ def frequency_to_seconds(frequency) -> int:
178
220
  """Convert a frequency to seconds.
179
221
 
180
222
  Parameters
@@ -362,6 +404,8 @@ class Autumn(DateTimes):
362
404
 
363
405
 
364
406
  class ConcatDateTimes:
407
+ """ConcatDateTimes is an iterator that generates datetime objects from a list of dates."""
408
+
365
409
  def __init__(self, *dates):
366
410
  if len(dates) == 1 and isinstance(dates[0], list):
367
411
  dates = dates[0]
@@ -374,6 +418,8 @@ class ConcatDateTimes:
374
418
 
375
419
 
376
420
  class EnumDateTimes:
421
+ """EnumDateTimes is an iterator that generates datetime objects from a list of dates."""
422
+
377
423
  def __init__(self, dates):
378
424
  self.dates = dates
379
425
 
@@ -393,6 +439,8 @@ def datetimes_factory(*args, **kwargs):
393
439
  name = kwargs.get("name")
394
440
 
395
441
  if name == "hindcast":
442
+ from .hindcasts import HindcastDatesTimes
443
+
396
444
  reference_dates = kwargs["reference_dates"]
397
445
  reference_dates = datetimes_factory(reference_dates)
398
446
  years = kwargs["years"]
@@ -416,3 +464,8 @@ def datetimes_factory(*args, **kwargs):
416
464
  return datetimes_factory(*a)
417
465
 
418
466
  return ConcatDateTimes(*[datetimes_factory(a) for a in args])
467
+
468
+
469
+ if __name__ == "__main__":
470
+ print(as_datetime_list("R10/2023-01-01T00:00:00Z/P1D"))
471
+ print(as_datetime_list("2007-03-01T13:00:00/2008-05-11T15:30:00", "200h"))
@@ -95,8 +95,8 @@ def units(param) -> str:
95
95
 
96
96
  Parameters
97
97
  ----------
98
- paramid : int or str
99
- Parameter id ir name.
98
+ param : int or str
99
+ Parameter id or name.
100
100
 
101
101
  Returns
102
102
  -------
@@ -112,7 +112,7 @@ def units(param) -> str:
112
112
  return _units()[unit_id]
113
113
 
114
114
 
115
- def must_be_positive(param):
115
+ def must_be_positive(param) -> bool:
116
116
  """Check if a parameter must be positive.
117
117
 
118
118
  Parameters