anemoi-utils 0.4.17__tar.gz → 0.4.19__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.19/.readthedocs.yaml +19 -0
- anemoi_utils-0.4.19/.release-please-manifest.json +3 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/CHANGELOG.md +19 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/PKG-INFO +2 -1
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/docs/Makefile +1 -0
- anemoi_utils-0.4.19/docs/_templates/apidoc/package.rst.jinja +55 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/docs/index.rst +4 -4
- anemoi_utils-0.4.19/docs/scripts/api_build.sh +13 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/pyproject.toml +1 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/_version.py +2 -2
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/registry.py +7 -3
- anemoi_utils-0.4.19/src/anemoi/utils/rules.py +218 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/testing.py +81 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi_utils.egg-info/PKG-INFO +2 -1
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi_utils.egg-info/SOURCES.txt +3 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi_utils.egg-info/requires.txt +1 -0
- anemoi_utils-0.4.17/.readthedocs.yaml +0 -16
- anemoi_utils-0.4.17/.release-please-manifest.json +0 -3
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/.gitattributes +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/.github/CODEOWNERS +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/.github/ci-hpc-config.yml +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/.github/dependabot.yml +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/.github/labeler.yml +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/.github/pull_request_template.md +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/.github/release.yml +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/.github/workflows/downstream-ci-hpc.yml +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/.github/workflows/pr-conventional-commit.yml +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/.github/workflows/pr-label-conventional-commits.yml +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/.github/workflows/pr-label-file-based.yml +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/.github/workflows/pr-label-public.yml +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/.github/workflows/python-publish.yml +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/.github/workflows/python-pull-request.yml +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/.github/workflows/readthedocs-pr-update.yml +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/.github/workflows/release-please.yml +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/.gitignore +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/.pre-commit-config.yaml +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/.release-please-config.json +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/CONTRIBUTORS.md +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/LICENSE +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/README.md +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/docs/_static/logo.png +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/docs/_static/style.css +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/docs/_templates/.gitkeep +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/docs/conf.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/docs/installing.rst +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/docs/modules/checkpoints.rst +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/docs/modules/config.rst +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/docs/modules/dates.rst +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/docs/modules/grib.rst +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/docs/modules/humanize.rst +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/docs/modules/provenance.rst +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/docs/modules/s3.rst +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/docs/modules/testing.rst +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/docs/modules/text.rst +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/setup.cfg +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/__init__.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/__main__.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/caching.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/checkpoints.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/cli.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/commands/__init__.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/commands/config.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/commands/requests.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/compatibility.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/config.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/dates.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/devtools.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/grib.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/grids.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/hindcasts.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/humanize.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/logs.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/mars/__init__.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/mars/mars.yaml +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/mars/requests.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/provenance.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/remote/__init__.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/remote/s3.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/remote/ssh.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/s3.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/sanitise.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/sanitize.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/text.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi/utils/timer.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi_utils.egg-info/dependency_links.txt +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi_utils.egg-info/entry_points.txt +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/src/anemoi_utils.egg-info/top_level.txt +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/tests/test-transfer-data/directory/b/c/x +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/tests/test-transfer-data/directory/b/y +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/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.17 → anemoi_utils-0.4.19}/tests/test-transfer-data/directory/z +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/tests/test-transfer-data/file +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/tests/test_caching.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/tests/test_compatibility.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/tests/test_dates.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/tests/test_frequency.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/tests/test_grids.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/tests/test_provenance.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/tests/test_remote.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/tests/test_sanetise.py +0 -0
- {anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/tests/test_utils.py +0 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
|
|
3
|
+
build:
|
|
4
|
+
os: ubuntu-22.04
|
|
5
|
+
tools:
|
|
6
|
+
python: "3.11"
|
|
7
|
+
jobs:
|
|
8
|
+
pre_build:
|
|
9
|
+
- bash docs/scripts/api_build.sh
|
|
10
|
+
|
|
11
|
+
sphinx:
|
|
12
|
+
configuration: docs/conf.py
|
|
13
|
+
|
|
14
|
+
python:
|
|
15
|
+
install:
|
|
16
|
+
- method: pip
|
|
17
|
+
path: .
|
|
18
|
+
extra_requirements:
|
|
19
|
+
- docs
|
|
@@ -8,6 +8,25 @@ 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.19](https://github.com/ecmwf/anemoi-utils/compare/0.4.18...0.4.19) (2025-04-04)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
* more testing support functions ([#136](https://github.com/ecmwf/anemoi-utils/issues/136)) ([5687b87](https://github.com/ecmwf/anemoi-utils/commit/5687b87ed17748412340d00f0724249f59b4e3f2))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Documentation
|
|
20
|
+
|
|
21
|
+
* add api ([#133](https://github.com/ecmwf/anemoi-utils/issues/133)) ([16af518](https://github.com/ecmwf/anemoi-utils/commit/16af5184eafbfc29cc3f0217a35675f2aa32847e))
|
|
22
|
+
|
|
23
|
+
## [0.4.18](https://github.com/ecmwf/anemoi-utils/compare/0.4.17...0.4.18) (2025-03-31)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Features
|
|
27
|
+
|
|
28
|
+
* add matching rules ([#132](https://github.com/ecmwf/anemoi-utils/issues/132)) ([2382980](https://github.com/ecmwf/anemoi-utils/commit/2382980f4f53909a73fa0a5c8cfab108625f3c55))
|
|
29
|
+
|
|
11
30
|
## [0.4.17](https://github.com/ecmwf/anemoi-utils/compare/0.4.16...0.4.17) (2025-03-27)
|
|
12
31
|
|
|
13
32
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: anemoi-utils
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.19
|
|
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
|
|
@@ -237,6 +237,7 @@ Requires-Dist: anemoi-utils[grib,provenance,s3,text]; extra == "all"
|
|
|
237
237
|
Provides-Extra: dev
|
|
238
238
|
Requires-Dist: anemoi-utils[all,docs,tests]; extra == "dev"
|
|
239
239
|
Provides-Extra: docs
|
|
240
|
+
Requires-Dist: anemoi-utils[all]; extra == "docs"
|
|
240
241
|
Requires-Dist: nbsphinx; extra == "docs"
|
|
241
242
|
Requires-Dist: pandoc; extra == "docs"
|
|
242
243
|
Requires-Dist: requests; extra == "docs"
|
|
@@ -19,4 +19,5 @@ help:
|
|
|
19
19
|
# Catch-all target: route all unknown targets to Sphinx using the new
|
|
20
20
|
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
|
21
21
|
%: Makefile
|
|
22
|
+
bash $(SOURCEDIR)/scripts/api_build.sh
|
|
22
23
|
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{%- macro automodule(modname, options) -%}
|
|
2
|
+
.. automodule:: {{ modname }}
|
|
3
|
+
{%- for option in options %}
|
|
4
|
+
:{{ option }}:
|
|
5
|
+
{%- endfor %}
|
|
6
|
+
{%- endmacro %}
|
|
7
|
+
|
|
8
|
+
{%- macro toctree(docnames) -%}
|
|
9
|
+
.. toctree::
|
|
10
|
+
:maxdepth: {{ maxdepth }}
|
|
11
|
+
{% for docname in docnames %}
|
|
12
|
+
{{ docname }}
|
|
13
|
+
{%- endfor %}
|
|
14
|
+
{%- endmacro %}
|
|
15
|
+
|
|
16
|
+
{%- if is_namespace %}
|
|
17
|
+
{{- pkgname.split(".")[1:] | join(".") | e | heading }}
|
|
18
|
+
{% else %}
|
|
19
|
+
{{- pkgname.split(".")[1:] | join(" ") | e | heading }}
|
|
20
|
+
{% endif %}
|
|
21
|
+
|
|
22
|
+
{%- if is_namespace %}
|
|
23
|
+
.. py:module:: {{ pkgname }}
|
|
24
|
+
{% endif %}
|
|
25
|
+
|
|
26
|
+
{%- if modulefirst and not is_namespace %}
|
|
27
|
+
{{ automodule(["anemoi", pkgname] | join("."), [""]) }}
|
|
28
|
+
{% endif %}
|
|
29
|
+
|
|
30
|
+
{%- if subpackages %}
|
|
31
|
+
Subpackages
|
|
32
|
+
-----------
|
|
33
|
+
|
|
34
|
+
{{ toctree(subpackages) }}
|
|
35
|
+
{% endif %}
|
|
36
|
+
|
|
37
|
+
{%- if submodules %}
|
|
38
|
+
{% if separatemodules %}
|
|
39
|
+
{{ toctree(submodules) }}
|
|
40
|
+
{% else %}
|
|
41
|
+
{%- for submodule in submodules %}
|
|
42
|
+
{% if show_headings %}
|
|
43
|
+
{{- submodule.split(".")[2:] | join(".") | e | heading(2) }}
|
|
44
|
+
{% endif %}
|
|
45
|
+
{{ automodule(["anemoi", submodule] | join("."), automodule_options) }}
|
|
46
|
+
{% endfor %}
|
|
47
|
+
{%- endif %}
|
|
48
|
+
{%- endif %}
|
|
49
|
+
|
|
50
|
+
{%- if not modulefirst and not is_namespace %}
|
|
51
|
+
Module contents
|
|
52
|
+
---------------
|
|
53
|
+
|
|
54
|
+
{{ automodule(pkgname, automodule_options) }}
|
|
55
|
+
{% endif %}
|
|
@@ -30,15 +30,15 @@ of the *Anemoi* packages.
|
|
|
30
30
|
|
|
31
31
|
installing
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
*****
|
|
34
|
+
API
|
|
35
|
+
*****
|
|
36
36
|
|
|
37
37
|
.. toctree::
|
|
38
38
|
:maxdepth: 1
|
|
39
39
|
:glob:
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
_api/*
|
|
42
42
|
|
|
43
43
|
***********************
|
|
44
44
|
Other Anemoi packages
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
script_dir=$(dirname "${BASH_SOURCE[0]}")
|
|
6
|
+
docs_dir="$script_dir/.."
|
|
7
|
+
source_dir="$script_dir/../../src/"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
trap 'rm -f $source_dir/anemoi/__init__.py' EXIT
|
|
11
|
+
|
|
12
|
+
touch "$source_dir/anemoi/__init__.py"
|
|
13
|
+
sphinx-apidoc -M -f -o "$docs_dir/_api" "$source_dir/anemoi" #-t "$docs_dir/_templates/apidoc"
|
|
@@ -17,8 +17,10 @@ from functools import cached_property
|
|
|
17
17
|
from typing import Any
|
|
18
18
|
from typing import Callable
|
|
19
19
|
from typing import Dict
|
|
20
|
+
from typing import Generic
|
|
20
21
|
from typing import List
|
|
21
22
|
from typing import Optional
|
|
23
|
+
from typing import TypeVar
|
|
22
24
|
from typing import Union
|
|
23
25
|
|
|
24
26
|
import entrypoints
|
|
@@ -78,8 +80,10 @@ class Error:
|
|
|
78
80
|
|
|
79
81
|
_BY_KIND = {}
|
|
80
82
|
|
|
83
|
+
T = TypeVar("T")
|
|
81
84
|
|
|
82
|
-
|
|
85
|
+
|
|
86
|
+
class Registry(Generic[T]):
|
|
83
87
|
"""A registry of factories.
|
|
84
88
|
|
|
85
89
|
Parameters
|
|
@@ -287,7 +291,7 @@ class Registry:
|
|
|
287
291
|
|
|
288
292
|
return sorted(self.factories.keys())
|
|
289
293
|
|
|
290
|
-
def create(self, name: str, *args: Any, **kwargs: Any) ->
|
|
294
|
+
def create(self, name: str, *args: Any, **kwargs: Any) -> T:
|
|
291
295
|
"""Create an instance using a factory.
|
|
292
296
|
|
|
293
297
|
Parameters
|
|
@@ -310,7 +314,7 @@ class Registry:
|
|
|
310
314
|
factory = self.lookup(name)
|
|
311
315
|
return factory(*args, **kwargs)
|
|
312
316
|
|
|
313
|
-
def from_config(self, config: Union[str, Dict[str, Any]], *args: Any, **kwargs: Any) ->
|
|
317
|
+
def from_config(self, config: Union[str, Dict[str, Any]], *args: Any, **kwargs: Any) -> T:
|
|
314
318
|
"""Create an instance from a configuration.
|
|
315
319
|
|
|
316
320
|
Parameters
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# (C) Copyright 2025 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
|
+
from typing import Any
|
|
11
|
+
from typing import Dict
|
|
12
|
+
from typing import List
|
|
13
|
+
from typing import Mapping
|
|
14
|
+
from typing import Optional
|
|
15
|
+
from typing import Union
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Rule:
|
|
19
|
+
|
|
20
|
+
def __init__(self, match: Dict[str, Any], result: Any):
|
|
21
|
+
"""Initialize a Rule object.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
match : Dict[str, Any]
|
|
26
|
+
A dictionary defining the conditions for the rule to match.
|
|
27
|
+
result : Any
|
|
28
|
+
The result to return if the rule matches.
|
|
29
|
+
"""
|
|
30
|
+
self._match = match
|
|
31
|
+
self._result = result
|
|
32
|
+
|
|
33
|
+
def match(self, obj: Mapping[str, Any]) -> bool:
|
|
34
|
+
"""Check if the rule matches the given object.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
obj : Mapping[str, Any]
|
|
39
|
+
The object to check against the rule's conditions.
|
|
40
|
+
|
|
41
|
+
Returns
|
|
42
|
+
-------
|
|
43
|
+
bool
|
|
44
|
+
True if the rule matches, False otherwise.
|
|
45
|
+
"""
|
|
46
|
+
for key, value in self._match.items():
|
|
47
|
+
if key not in obj or obj[key] != value:
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
return True
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def result(self) -> Any:
|
|
54
|
+
return self._result
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def condition(self) -> Dict[str, Any]:
|
|
58
|
+
return self._match
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class RuleSet:
|
|
62
|
+
|
|
63
|
+
def __init__(self, rules: List[Union[Rule, Dict[str, Any], List[Any]]]):
|
|
64
|
+
"""Initialize a RuleSet object.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
rules : List[Union[Rule, Dict[str, Any], List[Any]]]
|
|
69
|
+
A list of rules, where each rule can be a Rule object, a dictionary with
|
|
70
|
+
'match' and 'result' keys, or a list with two elements (match and result).
|
|
71
|
+
"""
|
|
72
|
+
assert isinstance(rules, list), "rules must be a list"
|
|
73
|
+
|
|
74
|
+
self.rules: List[Rule] = []
|
|
75
|
+
|
|
76
|
+
for rule in rules:
|
|
77
|
+
if isinstance(rule, Rule):
|
|
78
|
+
self.rules.append(rule)
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
if isinstance(rule, dict):
|
|
82
|
+
|
|
83
|
+
assert len(rule) == 2, "Rule dictionary must contain exactly two key-value pair."
|
|
84
|
+
|
|
85
|
+
match = rule.get("match")
|
|
86
|
+
if match is None:
|
|
87
|
+
raise ValueError("Rule dictionary must contain a 'match' key.")
|
|
88
|
+
|
|
89
|
+
result = rule.get("result")
|
|
90
|
+
if result is None:
|
|
91
|
+
raise ValueError("Rule dictionary must contain a 'result' key.")
|
|
92
|
+
|
|
93
|
+
self.rules.append(Rule(match, result))
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
if isinstance(rule, list):
|
|
97
|
+
assert len(rule) == 2, "Rule list must contain exactly two elements."
|
|
98
|
+
match = rule[0]
|
|
99
|
+
result = rule[1]
|
|
100
|
+
self.rules.append(Rule(match, result))
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
raise ValueError(
|
|
104
|
+
"Rule must be either a Rule object, a dictionary with 'match' and 'result' keys, or a list with two elements."
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def from_list(cls, rules: List[Any]) -> "RuleSet":
|
|
109
|
+
"""Create a RuleSet from a list of rules.
|
|
110
|
+
|
|
111
|
+
Parameters
|
|
112
|
+
----------
|
|
113
|
+
rules : List[Any]
|
|
114
|
+
A list of rules to initialize the RuleSet.
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
-------
|
|
118
|
+
RuleSet
|
|
119
|
+
A new RuleSet object.
|
|
120
|
+
"""
|
|
121
|
+
return cls(rules)
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def from_files(cls, path: str) -> "RuleSet":
|
|
125
|
+
"""Create a RuleSet from a file.
|
|
126
|
+
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
path : str
|
|
130
|
+
The path to the file containing the rules. Supported formats are .json and .yaml/.yml.
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
RuleSet
|
|
135
|
+
A new RuleSet object.
|
|
136
|
+
|
|
137
|
+
Raises
|
|
138
|
+
------
|
|
139
|
+
ValueError
|
|
140
|
+
If the file format is unsupported.
|
|
141
|
+
"""
|
|
142
|
+
if path.endswith(".json"):
|
|
143
|
+
import json
|
|
144
|
+
|
|
145
|
+
with open(path, "r") as f:
|
|
146
|
+
return cls.from_list(json.load(f))
|
|
147
|
+
|
|
148
|
+
if path.endswith(".yaml") or path.endswith(".yml"):
|
|
149
|
+
import yaml
|
|
150
|
+
|
|
151
|
+
with open(path, "r") as f:
|
|
152
|
+
return cls.from_list(yaml.safe_load(f))
|
|
153
|
+
|
|
154
|
+
raise ValueError("Unsupported file format. Supported formats are .json and .yaml/.yml.")
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
def from_any(cls, rules: Union[str, List[Any]]) -> "RuleSet":
|
|
158
|
+
"""Create a RuleSet from a list or a file path.
|
|
159
|
+
|
|
160
|
+
Parameters
|
|
161
|
+
----------
|
|
162
|
+
rules : Union[str, List[Any]]
|
|
163
|
+
The rules to initialize the RuleSet, either as a list or a file path.
|
|
164
|
+
|
|
165
|
+
Returns
|
|
166
|
+
-------
|
|
167
|
+
RuleSet
|
|
168
|
+
A new RuleSet object.
|
|
169
|
+
|
|
170
|
+
Raises
|
|
171
|
+
------
|
|
172
|
+
ValueError
|
|
173
|
+
If the rules format is unsupported.
|
|
174
|
+
"""
|
|
175
|
+
if isinstance(rules, str):
|
|
176
|
+
return cls.from_files(rules)
|
|
177
|
+
|
|
178
|
+
if isinstance(rules, list):
|
|
179
|
+
return cls.from_list(rules)
|
|
180
|
+
|
|
181
|
+
raise ValueError("Unsupported rules format. Must be a list or a file path.")
|
|
182
|
+
|
|
183
|
+
def match(self, obj: Mapping[str, Any], strategy: str = "first-match") -> Optional[Rule]:
|
|
184
|
+
"""Match an object against the rules in the RuleSet.
|
|
185
|
+
|
|
186
|
+
Parameters
|
|
187
|
+
----------
|
|
188
|
+
obj : Mapping[str, Any]
|
|
189
|
+
The object to match against the rules.
|
|
190
|
+
strategy : str, optional
|
|
191
|
+
The matching strategy to use. Currently, only 'first-match' is supported.
|
|
192
|
+
|
|
193
|
+
Returns
|
|
194
|
+
-------
|
|
195
|
+
Optional[Rule]
|
|
196
|
+
The first matching rule, or None if no match is found.
|
|
197
|
+
|
|
198
|
+
Raises
|
|
199
|
+
------
|
|
200
|
+
AssertionError
|
|
201
|
+
If an unsupported strategy is provided.
|
|
202
|
+
"""
|
|
203
|
+
assert strategy == "first-match", "Only 'first-match' strategy is supported for now."
|
|
204
|
+
for rule in self.rules:
|
|
205
|
+
if rule.match(obj):
|
|
206
|
+
return rule
|
|
207
|
+
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
def __iter__(self) -> iter:
|
|
211
|
+
"""Return an iterator over the rules in the RuleSet.
|
|
212
|
+
|
|
213
|
+
Returns
|
|
214
|
+
-------
|
|
215
|
+
iter
|
|
216
|
+
An iterator over the Rule objects in the RuleSet.
|
|
217
|
+
"""
|
|
218
|
+
return iter(self.rules)
|
|
@@ -13,7 +13,10 @@ import os
|
|
|
13
13
|
import shutil
|
|
14
14
|
import tempfile
|
|
15
15
|
import threading
|
|
16
|
+
import warnings
|
|
17
|
+
from functools import lru_cache
|
|
16
18
|
|
|
19
|
+
import pytest
|
|
17
20
|
from multiurl import download
|
|
18
21
|
|
|
19
22
|
LOG = logging.getLogger(__name__)
|
|
@@ -98,6 +101,9 @@ def get_test_data(path: str, gzipped=False) -> str:
|
|
|
98
101
|
"""
|
|
99
102
|
_check_path(path)
|
|
100
103
|
|
|
104
|
+
if _offline():
|
|
105
|
+
raise RuntimeError("Offline mode: cannot download test data, add @pytest.mark.skipif(not offline(),...)")
|
|
106
|
+
|
|
101
107
|
target = os.path.normpath(os.path.join(_temporary_directory(), path))
|
|
102
108
|
with lock:
|
|
103
109
|
if os.path.exists(target):
|
|
@@ -174,9 +180,84 @@ def packages_installed(*names) -> bool:
|
|
|
174
180
|
Flag indicating if all the packages are installed."
|
|
175
181
|
"""
|
|
176
182
|
|
|
183
|
+
warnings.warn(
|
|
184
|
+
"The 'packages_installed' function is deprecated. Use '@skip_if_missing' instead.",
|
|
185
|
+
DeprecationWarning,
|
|
186
|
+
stacklevel=2,
|
|
187
|
+
)
|
|
188
|
+
|
|
177
189
|
for name in names:
|
|
178
190
|
try:
|
|
179
191
|
__import__(name)
|
|
180
192
|
except ImportError:
|
|
181
193
|
return False
|
|
182
194
|
return True
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _missing_packages(*names) -> list[str]:
|
|
198
|
+
"""Check if the given packages are missing.
|
|
199
|
+
|
|
200
|
+
Use this function to check if the required packages are missing before running tests.
|
|
201
|
+
|
|
202
|
+
>>> @pytest.mark.skipif(missing_packages("foo", "bar"), reason="Packages 'foo' and 'bar' are not installed")
|
|
203
|
+
>>> def test_foo_bar() -> None:
|
|
204
|
+
>>> ...
|
|
205
|
+
|
|
206
|
+
Parameters
|
|
207
|
+
----------
|
|
208
|
+
names : str
|
|
209
|
+
The names of the packages to check.
|
|
210
|
+
|
|
211
|
+
Returns
|
|
212
|
+
-------
|
|
213
|
+
list[str]:
|
|
214
|
+
List of missing packages.
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
missing = []
|
|
218
|
+
for name in names:
|
|
219
|
+
try:
|
|
220
|
+
__import__(name)
|
|
221
|
+
except ImportError:
|
|
222
|
+
missing.append(name)
|
|
223
|
+
return missing
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _run_slow_tests() -> bool:
|
|
227
|
+
"""Check if the SLOW_TESTS environment variable is set.
|
|
228
|
+
|
|
229
|
+
Returns
|
|
230
|
+
-------
|
|
231
|
+
bool
|
|
232
|
+
True if the SLOW_TESTS environment variable is set, False otherwise.
|
|
233
|
+
"""
|
|
234
|
+
return int(os.environ.get("SLOW_TESTS", 0))
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@lru_cache(maxsize=None)
|
|
238
|
+
def _offline() -> bool:
|
|
239
|
+
"""Check if we are offline."""
|
|
240
|
+
|
|
241
|
+
import socket
|
|
242
|
+
|
|
243
|
+
try:
|
|
244
|
+
socket.create_connection(("anemoi.ecmwf.int", 443), timeout=5)
|
|
245
|
+
except OSError:
|
|
246
|
+
return True
|
|
247
|
+
|
|
248
|
+
return False
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
skip_if_offline = pytest.mark.skipif(_offline(), reason="No internet connection")
|
|
252
|
+
skip_slow_tests = pytest.mark.skipif(not _run_slow_tests(), reason="Skipping slow tests")
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def skip_missing_packages(*names):
|
|
256
|
+
missing = _missing_packages(*names)
|
|
257
|
+
if len(missing) == 0:
|
|
258
|
+
return lambda f: f
|
|
259
|
+
|
|
260
|
+
if len(missing) == 1:
|
|
261
|
+
return pytest.mark.skipif(True, reason=f"Package {missing[0]} is not installed")
|
|
262
|
+
|
|
263
|
+
return pytest.mark.skipif(True, reason=f"Packages {', '.join(missing)} are not installed")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: anemoi-utils
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.19
|
|
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
|
|
@@ -237,6 +237,7 @@ Requires-Dist: anemoi-utils[grib,provenance,s3,text]; extra == "all"
|
|
|
237
237
|
Provides-Extra: dev
|
|
238
238
|
Requires-Dist: anemoi-utils[all,docs,tests]; extra == "dev"
|
|
239
239
|
Provides-Extra: docs
|
|
240
|
+
Requires-Dist: anemoi-utils[all]; extra == "docs"
|
|
240
241
|
Requires-Dist: nbsphinx; extra == "docs"
|
|
241
242
|
Requires-Dist: pandoc; extra == "docs"
|
|
242
243
|
Requires-Dist: requests; extra == "docs"
|
|
@@ -31,6 +31,7 @@ docs/installing.rst
|
|
|
31
31
|
docs/_static/logo.png
|
|
32
32
|
docs/_static/style.css
|
|
33
33
|
docs/_templates/.gitkeep
|
|
34
|
+
docs/_templates/apidoc/package.rst.jinja
|
|
34
35
|
docs/modules/checkpoints.rst
|
|
35
36
|
docs/modules/config.rst
|
|
36
37
|
docs/modules/dates.rst
|
|
@@ -40,6 +41,7 @@ docs/modules/provenance.rst
|
|
|
40
41
|
docs/modules/s3.rst
|
|
41
42
|
docs/modules/testing.rst
|
|
42
43
|
docs/modules/text.rst
|
|
44
|
+
docs/scripts/api_build.sh
|
|
43
45
|
src/anemoi/utils/__init__.py
|
|
44
46
|
src/anemoi/utils/__main__.py
|
|
45
47
|
src/anemoi/utils/_version.py
|
|
@@ -57,6 +59,7 @@ src/anemoi/utils/humanize.py
|
|
|
57
59
|
src/anemoi/utils/logs.py
|
|
58
60
|
src/anemoi/utils/provenance.py
|
|
59
61
|
src/anemoi/utils/registry.py
|
|
62
|
+
src/anemoi/utils/rules.py
|
|
60
63
|
src/anemoi/utils/s3.py
|
|
61
64
|
src/anemoi/utils/sanitise.py
|
|
62
65
|
src/anemoi/utils/sanitize.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
|
|
File without changes
|
{anemoi_utils-0.4.17 → anemoi_utils-0.4.19}/.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
|