From aff87ca068b7500b4ae8a71fe4c0674aca1b5b1f Mon Sep 17 00:00:00 2001 From: Jared Lewis Date: Mon, 28 Oct 2024 12:55:17 +1100 Subject: [PATCH 01/14] feat: Beginning to flesh out the interface --- packages/ref-core/pyproject.toml | 1 + packages/ref-core/src/ref_core/providers.py | 36 +++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 packages/ref-core/src/ref_core/providers.py diff --git a/packages/ref-core/pyproject.toml b/packages/ref-core/pyproject.toml index 15592f5..1a51cb7 100644 --- a/packages/ref-core/pyproject.toml +++ b/packages/ref-core/pyproject.toml @@ -21,6 +21,7 @@ classifiers = [ "Topic :: Scientific/Engineering", ] dependencies = [ + "pydantic>=2.0" ] [tool.uv] diff --git a/packages/ref-core/src/ref_core/providers.py b/packages/ref-core/src/ref_core/providers.py new file mode 100644 index 0000000..3298de3 --- /dev/null +++ b/packages/ref-core/src/ref_core/providers.py @@ -0,0 +1,36 @@ +""" +Interfaces for metrics providers. + +This defines how metrics packages interoperate with the REF framework. +""" + +from typing import Protocol + + +class MetricsProvider(Protocol): + """ + Interface for that a metrics provider must implement. + + This provides a consistent interface to multiple different metrics packages. + """ + + name: str + + +def run_metric(metric: MetricInformation, configuration: Configuration) -> MetricResult: + """ + Run a metric on a configuration. + + Parameters + ---------- + metric : MetricInformation + The metric to run. + configuration : Configuration + The configuration to run the metric on. + + Returns + ------- + MetricResult + The result of running the metric. + """ + pass From 07d1410d9f993b9d44de87c62565335bda2e3878 Mon Sep 17 00:00:00 2001 From: Jared Lewis Date: Mon, 28 Oct 2024 14:47:39 +1100 Subject: [PATCH 02/14] chore: Add a simple example --- packages/ref-core/src/ref_core/providers.py | 68 +++++++++++++++---- .../src/ref_metrics_example/example.py | 32 +++++++++ .../tests/unit/test_example.py | 15 ++++ 3 files changed, 102 insertions(+), 13 deletions(-) create mode 100644 packages/ref-metrics-example/src/ref_metrics_example/example.py create mode 100644 packages/ref-metrics-example/tests/unit/test_example.py diff --git a/packages/ref-core/src/ref_core/providers.py b/packages/ref-core/src/ref_core/providers.py index 3298de3..d25b21d 100644 --- a/packages/ref-core/src/ref_core/providers.py +++ b/packages/ref-core/src/ref_core/providers.py @@ -4,8 +4,11 @@ This defines how metrics packages interoperate with the REF framework. """ +import pathlib from typing import Protocol +from pydantic import BaseModel + class MetricsProvider(Protocol): """ @@ -15,22 +18,61 @@ class MetricsProvider(Protocol): """ name: str + version: str + + +class MetricResult(BaseModel): + """ + The result of running a metric. + + The content of the result follows the Earth System Metrics and Diagnostics Standards + ([EMDS](https://github.com/Earth-System-Diagnostics-Standards/EMDS/blob/main/standards.md)). + """ + output_bundle: pathlib.Path + """ + Path to the output bundle file. -def run_metric(metric: MetricInformation, configuration: Configuration) -> MetricResult: + The contents of this file are defined by + [EMDS standard](https://github.com/Earth-System-Diagnostics-Standards/EMDS/blob/main/standards.md#common-output-bundle-format-) + """ + successful: bool + """ + Whether the metric ran successfully. """ - Run a metric on a configuration. - Parameters - ---------- - metric : MetricInformation - The metric to run. - configuration : Configuration - The configuration to run the metric on. - Returns - ------- - MetricResult - The result of running the metric. +class Configuration: """ - pass + Configuration that describes the input data sources + """ + + ... + + +class Metric(Protocol): + """ + Interface for a metric that must be implemented. + """ + + name: str + """ + Name of the metric being run + """ + + provider: MetricsProvider + + def run(self, configuration: Configuration) -> MetricResult: + """ + Run the metric using . + + Parameters + ---------- + configuration : Configuration + The configuration to run the metric on. + + Returns + ------- + MetricResult + The result of running the metric. + """ diff --git a/packages/ref-metrics-example/src/ref_metrics_example/example.py b/packages/ref-metrics-example/src/ref_metrics_example/example.py new file mode 100644 index 0000000..e2ab4c4 --- /dev/null +++ b/packages/ref-metrics-example/src/ref_metrics_example/example.py @@ -0,0 +1,32 @@ +from pathlib import Path + +from ref_core.providers import Configuration, MetricResult + + +class ExampleMetric: + """ + Example metric that does nothing but count the number of times it has been run. + """ + + def __init__(self): + self._count = 0 + + def run(self, configuration: Configuration) -> MetricResult: + """ + Run a metric + + Parameters + ---------- + configuration + + Returns + ------- + : + The result of running the metric. + """ + self._count += 1 + + return MetricResult( + output_bundle=Path("output.json"), + successful=True, + ) diff --git a/packages/ref-metrics-example/tests/unit/test_example.py b/packages/ref-metrics-example/tests/unit/test_example.py new file mode 100644 index 0000000..287fb8a --- /dev/null +++ b/packages/ref-metrics-example/tests/unit/test_example.py @@ -0,0 +1,15 @@ +from ref_core.providers import Configuration +from ref_metrics_example.example import ExampleMetric + + +def test_example_metric(): + metric = ExampleMetric() + + configuration = Configuration() + + result = metric.run(configuration) + + assert result.successful + assert result.output_bundle.exists() + assert result.output_bundle.is_file() + assert result.output_bundle.name == "output_1.zip" From 0112594d80b77ec46f0f0fef07ace9d6f1c5dcdf Mon Sep 17 00:00:00 2001 From: Jared Lewis Date: Mon, 28 Oct 2024 21:56:57 +1100 Subject: [PATCH 03/14] chore: Update type hints --- .../src/ref_core/executor/__init__.py | 10 +- .../ref-core/src/ref_core/executor/local.py | 6 +- uv.lock | 100 ++++++++++++++++++ 3 files changed, 112 insertions(+), 4 deletions(-) diff --git a/packages/ref-core/src/ref_core/executor/__init__.py b/packages/ref-core/src/ref_core/executor/__init__.py index 464da68..463e037 100644 --- a/packages/ref-core/src/ref_core/executor/__init__.py +++ b/packages/ref-core/src/ref_core/executor/__init__.py @@ -15,6 +15,7 @@ import os from typing import Protocol, runtime_checkable +from ..providers import Metric, MetricResult from .local import LocalExecutor @@ -33,7 +34,7 @@ class Executor(Protocol): name: str - def run_metric(self, metric: object, *args, **kwargs) -> object: # type: ignore + def run_metric(self, metric: Metric, *args, **kwargs) -> MetricResult: # type: ignore """ Execute a metric """ @@ -94,7 +95,7 @@ def get(self, name: str) -> Executor: get_executor = _default_manager.get -def run_metric(metric: object, *args, **kwargs) -> object: # type: ignore +def run_metric(metric_name: str, *args, **kwargs) -> MetricResult: # type: ignore """ Run a metric using the default executor @@ -105,7 +106,8 @@ def run_metric(metric: object, *args, **kwargs) -> object: # type: ignore Parameters ---------- - metric + metric_name + Name of the metric args kwargs @@ -117,6 +119,8 @@ def run_metric(metric: object, *args, **kwargs) -> object: # type: ignore executor_name = os.environ.get("CMIP_REF_EXECUTOR", "local") executor = get_executor(executor_name) + # metric_name = get_metric(metric_name) + metric = "" return executor.run_metric(metric, *args, **kwargs) diff --git a/packages/ref-core/src/ref_core/executor/local.py b/packages/ref-core/src/ref_core/executor/local.py index af48231..166842f 100644 --- a/packages/ref-core/src/ref_core/executor/local.py +++ b/packages/ref-core/src/ref_core/executor/local.py @@ -1,3 +1,6 @@ +from ref_core.providers import Metric, MetricResult + + class LocalExecutor: """ Run a metric locally, in-process. @@ -9,13 +12,14 @@ class LocalExecutor: name = "local" - def run_metric(self, metric, *args, **kwargs): # type: ignore + def run_metric(self, metric: Metric, *args, **kwargs) -> MetricResult: # type: ignore """ Run a metric in process Parameters ---------- metric + Metric to run args kwargs diff --git a/uv.lock b/uv.lock index cc469d7..cf32751 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,9 @@ version = 1 requires-python = ">=3.10" +resolution-markers = [ + "python_full_version < '3.13'", + "python_full_version >= '3.13'", +] [manifest] members = [ @@ -17,6 +21,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929 }, ] +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + [[package]] name = "anyio" version = "4.6.0" @@ -1673,6 +1686,87 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, ] +[[package]] +name = "pydantic" +version = "2.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928 }, +] + +[[package]] +name = "pydantic-core" +version = "2.23.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/8b/d3ae387f66277bd8104096d6ec0a145f4baa2966ebb2cad746c0920c9526/pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b", size = 1867835 }, + { url = "https://files.pythonhosted.org/packages/46/76/f68272e4c3a7df8777798282c5e47d508274917f29992d84e1898f8908c7/pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166", size = 1776689 }, + { url = "https://files.pythonhosted.org/packages/cc/69/5f945b4416f42ea3f3bc9d2aaec66c76084a6ff4ff27555bf9415ab43189/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb", size = 1800748 }, + { url = "https://files.pythonhosted.org/packages/50/ab/891a7b0054bcc297fb02d44d05c50e68154e31788f2d9d41d0b72c89fdf7/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916", size = 1806469 }, + { url = "https://files.pythonhosted.org/packages/31/7c/6e3fa122075d78f277a8431c4c608f061881b76c2b7faca01d317ee39b5d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07", size = 2002246 }, + { url = "https://files.pythonhosted.org/packages/ad/6f/22d5692b7ab63fc4acbc74de6ff61d185804a83160adba5e6cc6068e1128/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232", size = 2659404 }, + { url = "https://files.pythonhosted.org/packages/11/ac/1e647dc1121c028b691028fa61a4e7477e6aeb5132628fde41dd34c1671f/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2", size = 2053940 }, + { url = "https://files.pythonhosted.org/packages/91/75/984740c17f12c3ce18b5a2fcc4bdceb785cce7df1511a4ce89bca17c7e2d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f", size = 1921437 }, + { url = "https://files.pythonhosted.org/packages/a0/74/13c5f606b64d93f0721e7768cd3e8b2102164866c207b8cd6f90bb15d24f/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3", size = 1966129 }, + { url = "https://files.pythonhosted.org/packages/18/03/9c4aa5919457c7b57a016c1ab513b1a926ed9b2bb7915bf8e506bf65c34b/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071", size = 2110908 }, + { url = "https://files.pythonhosted.org/packages/92/2c/053d33f029c5dc65e5cf44ff03ceeefb7cce908f8f3cca9265e7f9b540c8/pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119", size = 1735278 }, + { url = "https://files.pythonhosted.org/packages/de/81/7dfe464eca78d76d31dd661b04b5f2036ec72ea8848dd87ab7375e185c23/pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f", size = 1917453 }, + { url = "https://files.pythonhosted.org/packages/5d/30/890a583cd3f2be27ecf32b479d5d615710bb926d92da03e3f7838ff3e58b/pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8", size = 1865160 }, + { url = "https://files.pythonhosted.org/packages/1d/9a/b634442e1253bc6889c87afe8bb59447f106ee042140bd57680b3b113ec7/pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d", size = 1776777 }, + { url = "https://files.pythonhosted.org/packages/75/9a/7816295124a6b08c24c96f9ce73085032d8bcbaf7e5a781cd41aa910c891/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e", size = 1799244 }, + { url = "https://files.pythonhosted.org/packages/a9/8f/89c1405176903e567c5f99ec53387449e62f1121894aa9fc2c4fdc51a59b/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607", size = 1805307 }, + { url = "https://files.pythonhosted.org/packages/d5/a5/1a194447d0da1ef492e3470680c66048fef56fc1f1a25cafbea4bc1d1c48/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd", size = 2000663 }, + { url = "https://files.pythonhosted.org/packages/13/a5/1df8541651de4455e7d587cf556201b4f7997191e110bca3b589218745a5/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea", size = 2655941 }, + { url = "https://files.pythonhosted.org/packages/44/31/a3899b5ce02c4316865e390107f145089876dff7e1dfc770a231d836aed8/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e", size = 2052105 }, + { url = "https://files.pythonhosted.org/packages/1b/aa/98e190f8745d5ec831f6d5449344c48c0627ac5fed4e5340a44b74878f8e/pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b", size = 1919967 }, + { url = "https://files.pythonhosted.org/packages/ae/35/b6e00b6abb2acfee3e8f85558c02a0822e9a8b2f2d812ea8b9079b118ba0/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0", size = 1964291 }, + { url = "https://files.pythonhosted.org/packages/13/46/7bee6d32b69191cd649bbbd2361af79c472d72cb29bb2024f0b6e350ba06/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64", size = 2109666 }, + { url = "https://files.pythonhosted.org/packages/39/ef/7b34f1b122a81b68ed0a7d0e564da9ccdc9a2924c8d6c6b5b11fa3a56970/pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f", size = 1732940 }, + { url = "https://files.pythonhosted.org/packages/2f/76/37b7e76c645843ff46c1d73e046207311ef298d3f7b2f7d8f6ac60113071/pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3", size = 1916804 }, + { url = "https://files.pythonhosted.org/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459 }, + { url = "https://files.pythonhosted.org/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007 }, + { url = "https://files.pythonhosted.org/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245 }, + { url = "https://files.pythonhosted.org/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260 }, + { url = "https://files.pythonhosted.org/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872 }, + { url = "https://files.pythonhosted.org/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617 }, + { url = "https://files.pythonhosted.org/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831 }, + { url = "https://files.pythonhosted.org/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453 }, + { url = "https://files.pythonhosted.org/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793 }, + { url = "https://files.pythonhosted.org/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872 }, + { url = "https://files.pythonhosted.org/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535 }, + { url = "https://files.pythonhosted.org/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992 }, + { url = "https://files.pythonhosted.org/packages/ad/ef/16ee2df472bf0e419b6bc68c05bf0145c49247a1095e85cee1463c6a44a1/pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc", size = 1856143 }, + { url = "https://files.pythonhosted.org/packages/da/fa/bc3dbb83605669a34a93308e297ab22be82dfb9dcf88c6cf4b4f264e0a42/pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd", size = 1770063 }, + { url = "https://files.pythonhosted.org/packages/4e/48/e813f3bbd257a712303ebdf55c8dc46f9589ec74b384c9f652597df3288d/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05", size = 1790013 }, + { url = "https://files.pythonhosted.org/packages/b4/e0/56eda3a37929a1d297fcab1966db8c339023bcca0b64c5a84896db3fcc5c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d", size = 1801077 }, + { url = "https://files.pythonhosted.org/packages/04/be/5e49376769bfbf82486da6c5c1683b891809365c20d7c7e52792ce4c71f3/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510", size = 1996782 }, + { url = "https://files.pythonhosted.org/packages/bc/24/e3ee6c04f1d58cc15f37bcc62f32c7478ff55142b7b3e6d42ea374ea427c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6", size = 2661375 }, + { url = "https://files.pythonhosted.org/packages/c1/f8/11a9006de4e89d016b8de74ebb1db727dc100608bb1e6bbe9d56a3cbbcce/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b", size = 2071635 }, + { url = "https://files.pythonhosted.org/packages/7c/45/bdce5779b59f468bdf262a5bc9eecbae87f271c51aef628d8c073b4b4b4c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327", size = 1916994 }, + { url = "https://files.pythonhosted.org/packages/d8/fa/c648308fe711ee1f88192cad6026ab4f925396d1293e8356de7e55be89b5/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6", size = 1968877 }, + { url = "https://files.pythonhosted.org/packages/16/16/b805c74b35607d24d37103007f899abc4880923b04929547ae68d478b7f4/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f", size = 2116814 }, + { url = "https://files.pythonhosted.org/packages/d1/58/5305e723d9fcdf1c5a655e6a4cc2a07128bf644ff4b1d98daf7a9dbf57da/pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769", size = 1738360 }, + { url = "https://files.pythonhosted.org/packages/a5/ae/e14b0ff8b3f48e02394d8acd911376b7b66e164535687ef7dc24ea03072f/pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5", size = 1919411 }, + { url = "https://files.pythonhosted.org/packages/13/a9/5d582eb3204464284611f636b55c0a7410d748ff338756323cb1ce721b96/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5", size = 1857135 }, + { url = "https://files.pythonhosted.org/packages/2c/57/faf36290933fe16717f97829eabfb1868182ac495f99cf0eda9f59687c9d/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec", size = 1740583 }, + { url = "https://files.pythonhosted.org/packages/91/7c/d99e3513dc191c4fec363aef1bf4c8af9125d8fa53af7cb97e8babef4e40/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480", size = 1793637 }, + { url = "https://files.pythonhosted.org/packages/29/18/812222b6d18c2d13eebbb0f7cdc170a408d9ced65794fdb86147c77e1982/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068", size = 1941963 }, + { url = "https://files.pythonhosted.org/packages/0f/36/c1f3642ac3f05e6bb4aec3ffc399fa3f84895d259cf5f0ce3054b7735c29/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801", size = 1915332 }, + { url = "https://files.pythonhosted.org/packages/f7/ca/9c0854829311fb446020ebb540ee22509731abad886d2859c855dd29b904/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728", size = 1957926 }, + { url = "https://files.pythonhosted.org/packages/c0/1c/7836b67c42d0cd4441fcd9fafbf6a027ad4b79b6559f80cf11f89fd83648/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433", size = 2100342 }, + { url = "https://files.pythonhosted.org/packages/a9/f9/b6bcaf874f410564a78908739c80861a171788ef4d4f76f5009656672dfe/pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753", size = 1920344 }, +] + [[package]] name = "pygments" version = "2.18.0" @@ -1909,6 +2003,12 @@ wheels = [ name = "ref-core" version = "0.1.0" source = { editable = "packages/ref-core" } +dependencies = [ + { name = "pydantic" }, +] + +[package.metadata] +requires-dist = [{ name = "pydantic", specifier = ">=2.0" }] [[package]] name = "ref-metrics-example" From 14e85d09c12ff4659849c6389944147552fbcc8e Mon Sep 17 00:00:00 2001 From: Jared Lewis Date: Tue, 29 Oct 2024 21:06:39 +1100 Subject: [PATCH 04/14] feat: Add metrics manager --- .../src/ref_core/executor/__init__.py | 14 +-- .../ref-core/src/ref_core/executor/local.py | 2 +- packages/ref-core/src/ref_core/metrics.py | 100 ++++++++++++++++++ packages/ref-core/src/ref_core/providers.py | 52 +-------- packages/ref-core/tests/unit/test_executor.py | 2 + .../src/ref_metrics_example/__init__.py | 10 ++ .../src/ref_metrics_example/example.py | 12 ++- .../tests/unit/test_example.py | 8 +- 8 files changed, 137 insertions(+), 63 deletions(-) create mode 100644 packages/ref-core/src/ref_core/metrics.py diff --git a/packages/ref-core/src/ref_core/executor/__init__.py b/packages/ref-core/src/ref_core/executor/__init__.py index 463e037..8923d6a 100644 --- a/packages/ref-core/src/ref_core/executor/__init__.py +++ b/packages/ref-core/src/ref_core/executor/__init__.py @@ -15,8 +15,8 @@ import os from typing import Protocol, runtime_checkable -from ..providers import Metric, MetricResult -from .local import LocalExecutor +from ref_core.executor.local import LocalExecutor +from ref_core.metrics import Metric, MetricManager, MetricResult @runtime_checkable @@ -95,7 +95,7 @@ def get(self, name: str) -> Executor: get_executor = _default_manager.get -def run_metric(metric_name: str, *args, **kwargs) -> MetricResult: # type: ignore +def run_metric(metric_name: str, /, metric_manager: MetricManager, **kwargs) -> MetricResult: # type: ignore """ Run a metric using the default executor @@ -108,7 +108,8 @@ def run_metric(metric_name: str, *args, **kwargs) -> MetricResult: # type: igno ---------- metric_name Name of the metric - args + metric_manager + Metric manager to retrieve the metric kwargs Returns @@ -119,10 +120,9 @@ def run_metric(metric_name: str, *args, **kwargs) -> MetricResult: # type: igno executor_name = os.environ.get("CMIP_REF_EXECUTOR", "local") executor = get_executor(executor_name) - # metric_name = get_metric(metric_name) - metric = "" + metric = metric_manager.get(metric_name) - return executor.run_metric(metric, *args, **kwargs) + return executor.run_metric(metric, **kwargs) register_executor(LocalExecutor()) diff --git a/packages/ref-core/src/ref_core/executor/local.py b/packages/ref-core/src/ref_core/executor/local.py index 166842f..da589cd 100644 --- a/packages/ref-core/src/ref_core/executor/local.py +++ b/packages/ref-core/src/ref_core/executor/local.py @@ -1,4 +1,4 @@ -from ref_core.providers import Metric, MetricResult +from ref_core.metrics import Metric, MetricResult class LocalExecutor: diff --git a/packages/ref-core/src/ref_core/metrics.py b/packages/ref-core/src/ref_core/metrics.py new file mode 100644 index 0000000..bb95d72 --- /dev/null +++ b/packages/ref-core/src/ref_core/metrics.py @@ -0,0 +1,100 @@ +import pathlib +from typing import Protocol, runtime_checkable + +from pydantic import BaseModel + +from ref_core.providers import Configuration + + +class MetricResult(BaseModel): + """ + The result of running a metric. + + The content of the result follows the Earth System Metrics and Diagnostics Standards + ([EMDS](https://github.com/Earth-System-Diagnostics-Standards/EMDS/blob/main/standards.md)). + """ + + # Do we want to load a serialised version of the output bundle here or just a file path? + + output_bundle: pathlib.Path + """ + Path to the output bundle file. + + The contents of this file are defined by + [EMDS standard](https://github.com/Earth-System-Diagnostics-Standards/EMDS/blob/main/standards.md#common-output-bundle-format-) + """ + successful: bool + """ + Whether the metric ran successfully. + """ + # Log info is in the output bundle file already, but is definitely useful + + +@runtime_checkable +class Metric(Protocol): + """ + Interface for a metric that must be implemented. + """ + + name: str + """ + Name of the metric being run + """ + + def run(self, configuration: Configuration) -> MetricResult: + """ + Run the metric using . + + Parameters + ---------- + configuration : Configuration + The configuration to run the metric on. + + Returns + ------- + MetricResult + The result of running the metric. + """ + + +class MetricManager: + """ + Manages the registration of metrics and retrieval by name. + """ + + def __init__(self) -> None: + self._metrics: dict[str, Metric] = {} + + def register(self, metric: Metric) -> None: + """ + Register a metric with the manager. + + Parameters + ---------- + metric : Metric + The metric to register. + """ + if not isinstance(metric, Metric): + raise ValueError("Metric must be an instance of Metric") + self._metrics[metric.name.lower()] = metric + + def get(self, name: str) -> Metric: + """ + Get a metric by name. + + Parameters + ---------- + name : str + Name of the metric (case-sensitive). + + Raises + ------ + KeyError + If the metric with the given name is not found. + + Returns + ------- + Metric + The requested metric. + """ + return self._metrics[name.lower()] diff --git a/packages/ref-core/src/ref_core/providers.py b/packages/ref-core/src/ref_core/providers.py index d25b21d..a264567 100644 --- a/packages/ref-core/src/ref_core/providers.py +++ b/packages/ref-core/src/ref_core/providers.py @@ -21,58 +21,12 @@ class MetricsProvider(Protocol): version: str -class MetricResult(BaseModel): - """ - The result of running a metric. - - The content of the result follows the Earth System Metrics and Diagnostics Standards - ([EMDS](https://github.com/Earth-System-Diagnostics-Standards/EMDS/blob/main/standards.md)). - """ - - output_bundle: pathlib.Path - """ - Path to the output bundle file. - - The contents of this file are defined by - [EMDS standard](https://github.com/Earth-System-Diagnostics-Standards/EMDS/blob/main/standards.md#common-output-bundle-format-) - """ - successful: bool - """ - Whether the metric ran successfully. - """ - - -class Configuration: +class Configuration(BaseModel): """ Configuration that describes the input data sources """ - ... - - -class Metric(Protocol): - """ - Interface for a metric that must be implemented. - """ - - name: str + output_directory: pathlib.Path """ - Name of the metric being run + Directory to write output files to """ - - provider: MetricsProvider - - def run(self, configuration: Configuration) -> MetricResult: - """ - Run the metric using . - - Parameters - ---------- - configuration : Configuration - The configuration to run the metric on. - - Returns - ------- - MetricResult - The result of running the metric. - """ diff --git a/packages/ref-core/tests/unit/test_executor.py b/packages/ref-core/tests/unit/test_executor.py index c99be4d..23d5a66 100644 --- a/packages/ref-core/tests/unit/test_executor.py +++ b/packages/ref-core/tests/unit/test_executor.py @@ -4,6 +4,8 @@ class MockMetric: + name = "mock" + def run(self, *args, **kwargs): result = { "args": args, diff --git a/packages/ref-metrics-example/src/ref_metrics_example/__init__.py b/packages/ref-metrics-example/src/ref_metrics_example/__init__.py index 6bdc8a8..3bda8e9 100644 --- a/packages/ref-metrics-example/src/ref_metrics_example/__init__.py +++ b/packages/ref-metrics-example/src/ref_metrics_example/__init__.py @@ -4,5 +4,15 @@ import importlib.metadata +from ref_core.metrics import MetricManager + +from ref_metrics_example.example import ExampleMetric + __version__ = importlib.metadata.version("ref_metrics_example") __core_version__ = importlib.metadata.version("ref_core") + +# Initialise the metrics manager and register the example metric +metrics = MetricManager() +metrics.register(ExampleMetric()) + +# TODO: Figure out registering a provider diff --git a/packages/ref-metrics-example/src/ref_metrics_example/example.py b/packages/ref-metrics-example/src/ref_metrics_example/example.py index e2ab4c4..492f318 100644 --- a/packages/ref-metrics-example/src/ref_metrics_example/example.py +++ b/packages/ref-metrics-example/src/ref_metrics_example/example.py @@ -1,6 +1,7 @@ -from pathlib import Path +import json -from ref_core.providers import Configuration, MetricResult +from ref_core.metrics import MetricResult +from ref_core.providers import Configuration class ExampleMetric: @@ -8,6 +9,8 @@ class ExampleMetric: Example metric that does nothing but count the number of times it has been run. """ + name = "example" + def __init__(self): self._count = 0 @@ -26,7 +29,10 @@ def run(self, configuration: Configuration) -> MetricResult: """ self._count += 1 + with open(configuration.output_directory / "output.json", "w") as fh: + json.dump(({"count": self._count}), fh) + return MetricResult( - output_bundle=Path("output.json"), + output_bundle=configuration.output_directory / "output.json", successful=True, ) diff --git a/packages/ref-metrics-example/tests/unit/test_example.py b/packages/ref-metrics-example/tests/unit/test_example.py index 287fb8a..a5b886a 100644 --- a/packages/ref-metrics-example/tests/unit/test_example.py +++ b/packages/ref-metrics-example/tests/unit/test_example.py @@ -2,14 +2,16 @@ from ref_metrics_example.example import ExampleMetric -def test_example_metric(): +def test_example_metric(tmp_path): metric = ExampleMetric() - configuration = Configuration() + configuration = Configuration( + output_directory=tmp_path, + ) result = metric.run(configuration) assert result.successful assert result.output_bundle.exists() assert result.output_bundle.is_file() - assert result.output_bundle.name == "output_1.zip" + assert result.output_bundle.name == "output.json" From 0b421c235b53412de957f6c8dc323d06ba12f453 Mon Sep 17 00:00:00 2001 From: Jared Lewis Date: Tue, 29 Oct 2024 21:41:15 +1100 Subject: [PATCH 05/14] test: Update test suite --- .../src/ref_core/executor/__init__.py | 3 +- .../ref-core/src/ref_core/executor/local.py | 9 ++-- packages/ref-core/tests/conftest.py | 43 +++++++++++++++++++ packages/ref-core/tests/unit/test_executor.py | 42 ++++++------------ packages/ref-core/tests/unit/test_metrics.py | 11 +++++ 5 files changed, 75 insertions(+), 33 deletions(-) create mode 100644 packages/ref-core/tests/conftest.py create mode 100644 packages/ref-core/tests/unit/test_metrics.py diff --git a/packages/ref-core/src/ref_core/executor/__init__.py b/packages/ref-core/src/ref_core/executor/__init__.py index 8923d6a..c07c99a 100644 --- a/packages/ref-core/src/ref_core/executor/__init__.py +++ b/packages/ref-core/src/ref_core/executor/__init__.py @@ -17,6 +17,7 @@ from ref_core.executor.local import LocalExecutor from ref_core.metrics import Metric, MetricManager, MetricResult +from ref_core.providers import Configuration @runtime_checkable @@ -34,7 +35,7 @@ class Executor(Protocol): name: str - def run_metric(self, metric: Metric, *args, **kwargs) -> MetricResult: # type: ignore + def run_metric(self, metric: Metric, configuration: Configuration) -> MetricResult: # type: ignore """ Execute a metric """ diff --git a/packages/ref-core/src/ref_core/executor/local.py b/packages/ref-core/src/ref_core/executor/local.py index da589cd..76591d1 100644 --- a/packages/ref-core/src/ref_core/executor/local.py +++ b/packages/ref-core/src/ref_core/executor/local.py @@ -1,4 +1,5 @@ from ref_core.metrics import Metric, MetricResult +from ref_core.providers import Configuration class LocalExecutor: @@ -12,7 +13,7 @@ class LocalExecutor: name = "local" - def run_metric(self, metric: Metric, *args, **kwargs) -> MetricResult: # type: ignore + def run_metric(self, metric: Metric, configuration: Configuration) -> MetricResult: # type: ignore """ Run a metric in process @@ -20,12 +21,12 @@ def run_metric(self, metric: Metric, *args, **kwargs) -> MetricResult: # type: ---------- metric Metric to run - args - kwargs + configuration + Configuration to run the metric with Returns ------- : Results from running the metric """ - return metric.run(*args, **kwargs) + return metric.run(configuration=configuration) diff --git a/packages/ref-core/tests/conftest.py b/packages/ref-core/tests/conftest.py new file mode 100644 index 0000000..9f3c585 --- /dev/null +++ b/packages/ref-core/tests/conftest.py @@ -0,0 +1,43 @@ +import pytest +from ref_core.metrics import MetricManager, MetricResult +from ref_core.providers import Configuration + + +class MockMetric: + name = "mock" + + def run(self, configuration: Configuration) -> MetricResult: + return MetricResult( + output_bundle=configuration.output_directory / "output.json", + successful=True, + ) + + +class FailedMetric: + name = "failed" + + def run(self, configuration: Configuration) -> MetricResult: + return MetricResult( + successful=False, + ) + + +@pytest.fixture +def metric_manager() -> MetricManager: + manager = MetricManager() + manager.register(MockMetric()) + manager.register(FailedMetric()) + + return manager + + +@pytest.fixture +def mock_metric() -> MockMetric: + return MockMetric() + + +@pytest.fixture +def configuration(tmp_path) -> Configuration: + return Configuration( + output_directory=tmp_path, + ) diff --git a/packages/ref-core/tests/unit/test_executor.py b/packages/ref-core/tests/unit/test_executor.py index 23d5a66..d0c19dc 100644 --- a/packages/ref-core/tests/unit/test_executor.py +++ b/packages/ref-core/tests/unit/test_executor.py @@ -3,18 +3,6 @@ from ref_core.executor.local import LocalExecutor -class MockMetric: - name = "mock" - - def run(self, *args, **kwargs): - result = { - "args": args, - "kwargs": kwargs, - } - - return result - - class TestExecutorManager: def test_executor_register(self): manager = ExecutorManager() @@ -32,30 +20,28 @@ def test_is_executor(self): assert executor.name == "local" assert isinstance(executor, Executor) - def test_run_metric(self): + def test_run_metric(self, configuration, mock_metric): executor = LocalExecutor() - metric = MockMetric() - result = executor.run_metric(metric, "test", kwarg="test") - - assert result == { - "args": ("test",), - "kwargs": {"kwarg": "test"}, - } + result = executor.run_metric(mock_metric, configuration) + assert result.successful + assert result.output_bundle == configuration.output_directory / "output.json" @pytest.mark.parametrize("executor_name", ["local", None]) -def test_run_metric_local(monkeypatch, executor_name): +def test_run_metric_local(monkeypatch, executor_name, mock_metric, metric_manager, configuration): if executor_name: monkeypatch.setenv("CMIP_REF_EXECUTOR", executor_name) - result = run_metric(MockMetric(), "test", kwarg="test") - assert result == { - "args": ("test",), - "kwargs": {"kwarg": "test"}, - } + result = run_metric("mock", metric_manager, configuration=configuration) + assert result.successful -def test_run_metric_unknown(monkeypatch): +def test_run_metric_unknown_executor(monkeypatch, metric_manager): monkeypatch.setenv("CMIP_REF_EXECUTOR", "missing") with pytest.raises(KeyError): - run_metric(MockMetric(), "test", kwarg="test") + run_metric("mock", metric_manager=metric_manager, kwarg="test") + + +def test_run_metric_unknown_metric(monkeypatch, metric_manager): + with pytest.raises(KeyError): + run_metric("missing", metric_manager=metric_manager, kwarg="test") diff --git a/packages/ref-core/tests/unit/test_metrics.py b/packages/ref-core/tests/unit/test_metrics.py new file mode 100644 index 0000000..e1308f2 --- /dev/null +++ b/packages/ref-core/tests/unit/test_metrics.py @@ -0,0 +1,11 @@ +from ref_core.metrics import Metric, MetricManager + + +class TestMetricManager: + def test_manager_register(self, mock_metric): + manager = MetricManager() + manager.register(mock_metric) + + assert len(manager._metrics) == 1 + assert "mock" in manager._metrics + assert isinstance(manager.get("mock"), Metric) From b8ad5c357564c085108785d3dbad1798e43390a6 Mon Sep 17 00:00:00 2001 From: Jared Lewis Date: Tue, 29 Oct 2024 22:19:41 +1100 Subject: [PATCH 06/14] docs: Basic docs --- docs/explanation.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/explanation.md b/docs/explanation.md index ba4e8f3..b076241 100644 --- a/docs/explanation.md +++ b/docs/explanation.md @@ -12,6 +12,32 @@ Points we will aim to cover: We will aim to avoid writing instructions or technical descriptions here, they belong elsewhere. +## Metric Providers + +The REF aims to support a variety of metric providers. +These providers are responsible for performing the calculations and analyses. + +Each metric provider generally provides a number of different metrics that can be calculated. + +### Metrics + +A metric represents a specific calculation or analysis that can be performed on a dataset +or set of datasets with the aim for benchmarking the performance of different models. +These metrics often represent a specific aspects of the Earth system and are compared against +observations of the same quantities. + +A metric depends upon a set of input model data and observation datasets. + +The result of a metric calculation can be a range of different outcomes: + +* A single scalar value +* Timeseries +* Plots + +The Earth System Metrics and Diagnostics Standards +([EMDS](https://github.com/Earth-System-Diagnostics-Standards/EMDS)) +provide a community standard for reporting outputs. +This enables the ability to generate standardised outputs that can be distributed. ## Execution Environments From a009349ccfa8d2918314e4bfcc697f1f4db74969 Mon Sep 17 00:00:00 2001 From: Jared Lewis Date: Wed, 30 Oct 2024 10:04:22 +1100 Subject: [PATCH 07/14] refactor: Simplify interface by removing the need for a MetricManager --- docs/explanation.md | 1 + packages/ref-core/pyproject.toml | 1 + .../src/ref_core/executor/__init__.py | 38 +++++-- .../ref-core/src/ref_core/executor/local.py | 15 ++- packages/ref-core/src/ref_core/metrics.py | 103 ++++++++++-------- packages/ref-core/src/ref_core/providers.py | 62 ++++++++--- packages/ref-core/tests/conftest.py | 18 +-- packages/ref-core/tests/unit/test_executor.py | 12 +- packages/ref-core/tests/unit/test_metrics.py | 11 -- .../ref-core/tests/unit/test_providers.py | 29 +++++ .../src/ref_metrics_example/__init__.py | 8 +- .../src/ref_metrics_example/example.py | 7 +- .../unit/{test_example.py => test_metrics.py} | 4 +- .../tests/unit/test_provider.py | 14 +++ .../tests/unit/test_version.py | 8 -- 15 files changed, 209 insertions(+), 122 deletions(-) delete mode 100644 packages/ref-core/tests/unit/test_metrics.py create mode 100644 packages/ref-core/tests/unit/test_providers.py rename packages/ref-metrics-example/tests/unit/{test_example.py => test_metrics.py} (79%) create mode 100644 packages/ref-metrics-example/tests/unit/test_provider.py delete mode 100644 packages/ref-metrics-example/tests/unit/test_version.py diff --git a/docs/explanation.md b/docs/explanation.md index b076241..acaf71b 100644 --- a/docs/explanation.md +++ b/docs/explanation.md @@ -18,6 +18,7 @@ The REF aims to support a variety of metric providers. These providers are responsible for performing the calculations and analyses. Each metric provider generally provides a number of different metrics that can be calculated. +An example implementation of a metric provider is provided in the `ref_metrics_example` package. ### Metrics diff --git a/packages/ref-core/pyproject.toml b/packages/ref-core/pyproject.toml index 1a51cb7..d9b2592 100644 --- a/packages/ref-core/pyproject.toml +++ b/packages/ref-core/pyproject.toml @@ -26,6 +26,7 @@ dependencies = [ [tool.uv] dev-dependencies = [ + ] [build-system] diff --git a/packages/ref-core/src/ref_core/executor/__init__.py b/packages/ref-core/src/ref_core/executor/__init__.py index c07c99a..18060ea 100644 --- a/packages/ref-core/src/ref_core/executor/__init__.py +++ b/packages/ref-core/src/ref_core/executor/__init__.py @@ -13,11 +13,11 @@ """ import os -from typing import Protocol, runtime_checkable +from typing import Any, Protocol, runtime_checkable from ref_core.executor.local import LocalExecutor -from ref_core.metrics import Metric, MetricManager, MetricResult -from ref_core.providers import Configuration +from ref_core.metrics import Configuration, Metric, MetricResult, TriggerInfo +from ref_core.providers import MetricsProvider @runtime_checkable @@ -35,11 +35,28 @@ class Executor(Protocol): name: str - def run_metric(self, metric: Metric, configuration: Configuration) -> MetricResult: # type: ignore + def run_metric( + self, metric: Metric, configuration: Configuration, trigger: TriggerInfo | None, **kwargs: Any + ) -> MetricResult: """ Execute a metric + + Parameters + ---------- + metric + Metric to run + configuration + Configuration to run the metric with + trigger + Information about the dataset that triggered the metric run + kwargs + Additional keyword arguments for the executor + + Returns + ------- + : + Results from running the metric """ - # TODO: Add type hints for metric and return value in follow-up PR ... @@ -96,7 +113,7 @@ def get(self, name: str) -> Executor: get_executor = _default_manager.get -def run_metric(metric_name: str, /, metric_manager: MetricManager, **kwargs) -> MetricResult: # type: ignore +def run_metric(metric_name: str, /, metrics_provider: MetricsProvider, **kwargs) -> MetricResult: # type: ignore """ Run a metric using the default executor @@ -109,9 +126,10 @@ def run_metric(metric_name: str, /, metric_manager: MetricManager, **kwargs) -> ---------- metric_name Name of the metric - metric_manager - Metric manager to retrieve the metric + metrics_provider + Provider from where to retrieve the metric kwargs + Additional options passed to the metric executor Returns ------- @@ -121,9 +139,9 @@ def run_metric(metric_name: str, /, metric_manager: MetricManager, **kwargs) -> executor_name = os.environ.get("CMIP_REF_EXECUTOR", "local") executor = get_executor(executor_name) - metric = metric_manager.get(metric_name) + metric = metrics_provider.get(metric_name) - return executor.run_metric(metric, **kwargs) + return executor.run_metric(metric, trigger=None, **kwargs) register_executor(LocalExecutor()) diff --git a/packages/ref-core/src/ref_core/executor/local.py b/packages/ref-core/src/ref_core/executor/local.py index 76591d1..ed1d4cb 100644 --- a/packages/ref-core/src/ref_core/executor/local.py +++ b/packages/ref-core/src/ref_core/executor/local.py @@ -1,5 +1,6 @@ -from ref_core.metrics import Metric, MetricResult -from ref_core.providers import Configuration +from typing import Any + +from ref_core.metrics import Configuration, Metric, MetricResult, TriggerInfo class LocalExecutor: @@ -13,7 +14,9 @@ class LocalExecutor: name = "local" - def run_metric(self, metric: Metric, configuration: Configuration) -> MetricResult: # type: ignore + def run_metric( + self, metric: Metric, configuration: Configuration, trigger: TriggerInfo | None = None, **kwargs: Any + ) -> MetricResult: """ Run a metric in process @@ -23,10 +26,14 @@ def run_metric(self, metric: Metric, configuration: Configuration) -> MetricResu Metric to run configuration Configuration to run the metric with + trigger + Information about the dataset that triggered the metric run + kwargs + Additional keyword arguments for the executor Returns ------- : Results from running the metric """ - return metric.run(configuration=configuration) + return metric.run(configuration=configuration, trigger=trigger) diff --git a/packages/ref-core/src/ref_core/metrics.py b/packages/ref-core/src/ref_core/metrics.py index bb95d72..8270d3d 100644 --- a/packages/ref-core/src/ref_core/metrics.py +++ b/packages/ref-core/src/ref_core/metrics.py @@ -3,7 +3,18 @@ from pydantic import BaseModel -from ref_core.providers import Configuration + +class Configuration(BaseModel): + """ + Configuration that describes the input data sources + """ + + output_directory: pathlib.Path + """ + Directory to write output files to + """ + + # TODO: Add more configuration options here class MetricResult(BaseModel): @@ -30,71 +41,73 @@ class MetricResult(BaseModel): # Log info is in the output bundle file already, but is definitely useful -@runtime_checkable -class Metric(Protocol): +class TriggerInfo(BaseModel): """ - Interface for a metric that must be implemented. + The reason why the metric was run. """ - name: str + dataset: pathlib.Path """ - Name of the metric being run + Path to the dataset that triggered the metric run. """ - def run(self, configuration: Configuration) -> MetricResult: - """ - Run the metric using . + # TODO: + # Add/remove/modified? + # dataset metadata - Parameters - ---------- - configuration : Configuration - The configuration to run the metric on. - Returns - ------- - MetricResult - The result of running the metric. - """ +@runtime_checkable +class Metric(Protocol): + """ + Interface for the calculation of a metric. + This is a very high-level interface to provide maximum scope for the metrics packages + to have differing assumptions. + The configuration and output of the metric should follow the + Earth System Metrics and Diagnostics Standards formats as much as possible. -class MetricManager: + See (ref_example.example.ExampleMetric)[] for an example implementation. """ - Manages the registration of metrics and retrieval by name. + + name: str """ + Name of the metric being run - def __init__(self) -> None: - self._metrics: dict[str, Metric] = {} + This should be unique for a given provider, + but multiple providers can implement the same metric. + """ - def register(self, metric: Metric) -> None: - """ - Register a metric with the manager. + # input_variable: list[VariableDefinition] + """ + TODO: implement VariableDefinition + Should be extend the configuration defined in EMDS - Parameters - ---------- - metric : Metric - The metric to register. - """ - if not isinstance(metric, Metric): - raise ValueError("Metric must be an instance of Metric") - self._metrics[metric.name.lower()] = metric + Variables that the metric requires to run + Any modifications to the input data will trigger a new metric calculation. + """ + # observation_dataset: list[ObservationDatasetDefinition] + """ + TODO: implement ObservationDatasetDefinition + Should be extend the configuration defined in EMDS. To check with Bouwe. + """ - def get(self, name: str) -> Metric: + def run(self, configuration: Configuration, trigger: TriggerInfo | None) -> MetricResult: """ - Get a metric by name. + Run the metric on the given configuration. + + The implementation of this method method is left to the metrics providers. + + A CMEC-compatible package can use: TODO: Add link to CMEC metric wrapper Parameters ---------- - name : str - Name of the metric (case-sensitive). - - Raises - ------ - KeyError - If the metric with the given name is not found. + configuration : Configuration + The configuration to run the metric on. + trigger : TriggerInfo | None + Optional information about the dataset that triggered the metric run. Returns ------- - Metric - The requested metric. + MetricResult + The result of running the metric. """ - return self._metrics[name.lower()] diff --git a/packages/ref-core/src/ref_core/providers.py b/packages/ref-core/src/ref_core/providers.py index a264567..7bc14b6 100644 --- a/packages/ref-core/src/ref_core/providers.py +++ b/packages/ref-core/src/ref_core/providers.py @@ -4,29 +4,55 @@ This defines how metrics packages interoperate with the REF framework. """ -import pathlib -from typing import Protocol +from ref_core.metrics import Metric -from pydantic import BaseModel - -class MetricsProvider(Protocol): +class MetricsProvider: """ Interface for that a metrics provider must implement. This provides a consistent interface to multiple different metrics packages. """ - name: str - version: str - - -class Configuration(BaseModel): - """ - Configuration that describes the input data sources - """ - - output_directory: pathlib.Path - """ - Directory to write output files to - """ + def __init__(self, name: str, version: str) -> None: + self.name = name + self.version = version + + self._metrics: dict[str, Metric] = {} + + def __len__(self) -> int: + return len(self._metrics) + + def register(self, metric: Metric) -> None: + """ + Register a metric with the manager. + + Parameters + ---------- + metric : Metric + The metric to register. + """ + if not isinstance(metric, Metric): + raise ValueError("Metric must be an instance of Metric") + self._metrics[metric.name.lower()] = metric + + def get(self, name: str) -> Metric: + """ + Get a metric by name. + + Parameters + ---------- + name : str + Name of the metric (case-sensitive). + + Raises + ------ + KeyError + If the metric with the given name is not found. + + Returns + ------- + Metric + The requested metric. + """ + return self._metrics[name.lower()] diff --git a/packages/ref-core/tests/conftest.py b/packages/ref-core/tests/conftest.py index 9f3c585..d4f3143 100644 --- a/packages/ref-core/tests/conftest.py +++ b/packages/ref-core/tests/conftest.py @@ -1,12 +1,12 @@ import pytest -from ref_core.metrics import MetricManager, MetricResult -from ref_core.providers import Configuration +from ref_core.metrics import Configuration, MetricResult, TriggerInfo +from ref_core.providers import MetricsProvider class MockMetric: name = "mock" - def run(self, configuration: Configuration) -> MetricResult: + def run(self, configuration: Configuration, trigger: TriggerInfo) -> MetricResult: return MetricResult( output_bundle=configuration.output_directory / "output.json", successful=True, @@ -16,19 +16,19 @@ def run(self, configuration: Configuration) -> MetricResult: class FailedMetric: name = "failed" - def run(self, configuration: Configuration) -> MetricResult: + def run(self, configuration: Configuration, trigger: TriggerInfo) -> MetricResult: return MetricResult( successful=False, ) @pytest.fixture -def metric_manager() -> MetricManager: - manager = MetricManager() - manager.register(MockMetric()) - manager.register(FailedMetric()) +def provider() -> MetricsProvider: + provider = MetricsProvider("mock_provider", "v0.1.0") + provider.register(MockMetric()) + provider.register(FailedMetric()) - return manager + return provider @pytest.fixture diff --git a/packages/ref-core/tests/unit/test_executor.py b/packages/ref-core/tests/unit/test_executor.py index d0c19dc..37867b2 100644 --- a/packages/ref-core/tests/unit/test_executor.py +++ b/packages/ref-core/tests/unit/test_executor.py @@ -29,19 +29,19 @@ def test_run_metric(self, configuration, mock_metric): @pytest.mark.parametrize("executor_name", ["local", None]) -def test_run_metric_local(monkeypatch, executor_name, mock_metric, metric_manager, configuration): +def test_run_metric_local(monkeypatch, executor_name, mock_metric, provider, configuration): if executor_name: monkeypatch.setenv("CMIP_REF_EXECUTOR", executor_name) - result = run_metric("mock", metric_manager, configuration=configuration) + result = run_metric("mock", provider, configuration=configuration) assert result.successful -def test_run_metric_unknown_executor(monkeypatch, metric_manager): +def test_run_metric_unknown_executor(monkeypatch, provider): monkeypatch.setenv("CMIP_REF_EXECUTOR", "missing") with pytest.raises(KeyError): - run_metric("mock", metric_manager=metric_manager, kwarg="test") + run_metric("mock", metrics_provider=provider, kwarg="test") -def test_run_metric_unknown_metric(monkeypatch, metric_manager): +def test_run_metric_unknown_metric(monkeypatch, provider): with pytest.raises(KeyError): - run_metric("missing", metric_manager=metric_manager, kwarg="test") + run_metric("missing", metrics_provider=provider, kwarg="test") diff --git a/packages/ref-core/tests/unit/test_metrics.py b/packages/ref-core/tests/unit/test_metrics.py deleted file mode 100644 index e1308f2..0000000 --- a/packages/ref-core/tests/unit/test_metrics.py +++ /dev/null @@ -1,11 +0,0 @@ -from ref_core.metrics import Metric, MetricManager - - -class TestMetricManager: - def test_manager_register(self, mock_metric): - manager = MetricManager() - manager.register(mock_metric) - - assert len(manager._metrics) == 1 - assert "mock" in manager._metrics - assert isinstance(manager.get("mock"), Metric) diff --git a/packages/ref-core/tests/unit/test_providers.py b/packages/ref-core/tests/unit/test_providers.py new file mode 100644 index 0000000..a9007cf --- /dev/null +++ b/packages/ref-core/tests/unit/test_providers.py @@ -0,0 +1,29 @@ +from ref_core.metrics import Metric +from ref_core.providers import MetricsProvider + + +class TestMetricsProvider: + def test_provider(self): + provider = MetricsProvider("provider_name", "v0.23") + + assert provider.name == "provider_name" + assert provider.version == "v0.23" + assert len(provider) == 0 + + def test_provider_register(self, mock_metric): + provider = MetricsProvider("provider_name", "v0.23") + provider.register(mock_metric) + + assert len(provider) == 1 + assert "mock" in provider._metrics + assert isinstance(provider.get("mock"), Metric) + + def test_provider_fixture(self, provider): + assert provider.name == "mock_provider" + assert provider.version == "v0.1.0" + assert len(provider) == 2 + assert "mock" in provider._metrics + assert "failed" in provider._metrics + + result = provider.get("mock") + assert isinstance(result, Metric) diff --git a/packages/ref-metrics-example/src/ref_metrics_example/__init__.py b/packages/ref-metrics-example/src/ref_metrics_example/__init__.py index 3bda8e9..f645a72 100644 --- a/packages/ref-metrics-example/src/ref_metrics_example/__init__.py +++ b/packages/ref-metrics-example/src/ref_metrics_example/__init__.py @@ -4,7 +4,7 @@ import importlib.metadata -from ref_core.metrics import MetricManager +from ref_core.providers import MetricsProvider from ref_metrics_example.example import ExampleMetric @@ -12,7 +12,5 @@ __core_version__ = importlib.metadata.version("ref_core") # Initialise the metrics manager and register the example metric -metrics = MetricManager() -metrics.register(ExampleMetric()) - -# TODO: Figure out registering a provider +provider = MetricsProvider("example", __version__) +provider.register(ExampleMetric()) diff --git a/packages/ref-metrics-example/src/ref_metrics_example/example.py b/packages/ref-metrics-example/src/ref_metrics_example/example.py index 492f318..6f10f44 100644 --- a/packages/ref-metrics-example/src/ref_metrics_example/example.py +++ b/packages/ref-metrics-example/src/ref_metrics_example/example.py @@ -1,7 +1,6 @@ import json -from ref_core.metrics import MetricResult -from ref_core.providers import Configuration +from ref_core.metrics import Configuration, MetricResult, TriggerInfo class ExampleMetric: @@ -11,10 +10,10 @@ class ExampleMetric: name = "example" - def __init__(self): + def __init__(self) -> None: self._count = 0 - def run(self, configuration: Configuration) -> MetricResult: + def run(self, configuration: Configuration, trigger: TriggerInfo | None) -> MetricResult: """ Run a metric diff --git a/packages/ref-metrics-example/tests/unit/test_example.py b/packages/ref-metrics-example/tests/unit/test_metrics.py similarity index 79% rename from packages/ref-metrics-example/tests/unit/test_example.py rename to packages/ref-metrics-example/tests/unit/test_metrics.py index a5b886a..9163114 100644 --- a/packages/ref-metrics-example/tests/unit/test_example.py +++ b/packages/ref-metrics-example/tests/unit/test_metrics.py @@ -1,4 +1,4 @@ -from ref_core.providers import Configuration +from ref_core.metrics import Configuration from ref_metrics_example.example import ExampleMetric @@ -9,7 +9,7 @@ def test_example_metric(tmp_path): output_directory=tmp_path, ) - result = metric.run(configuration) + result = metric.run(configuration, trigger=None) assert result.successful assert result.output_bundle.exists() diff --git a/packages/ref-metrics-example/tests/unit/test_provider.py b/packages/ref-metrics-example/tests/unit/test_provider.py new file mode 100644 index 0000000..5de9e74 --- /dev/null +++ b/packages/ref-metrics-example/tests/unit/test_provider.py @@ -0,0 +1,14 @@ +from ref_metrics_example import __core_version__, __version__, provider + + +# Placeholder to get CI working +def test_version(): + assert __version__ == "0.1.0" + assert __core_version__ == "0.1.0" + + +def test_provider(): + assert provider.name == "example" + assert provider.version == __version__ + + assert len(provider) == 1 diff --git a/packages/ref-metrics-example/tests/unit/test_version.py b/packages/ref-metrics-example/tests/unit/test_version.py deleted file mode 100644 index 6a5f45c..0000000 --- a/packages/ref-metrics-example/tests/unit/test_version.py +++ /dev/null @@ -1,8 +0,0 @@ -from ref_metrics_example import __core_version__ as core_version -from ref_metrics_example import __version__ as version - - -# Placeholder to get CI working -def test_version(): - assert version == "0.1.0" - assert core_version == "0.1.0" From 41fadd9f262c7ee38d70eff5c034105bab2f3049 Mon Sep 17 00:00:00 2001 From: Jared Lewis Date: Wed, 30 Oct 2024 12:04:12 +1100 Subject: [PATCH 08/14] docs: Changelog --- changelog/5.feature.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog/5.feature.md diff --git a/changelog/5.feature.md b/changelog/5.feature.md new file mode 100644 index 0000000..cfe123f --- /dev/null +++ b/changelog/5.feature.md @@ -0,0 +1,3 @@ +Adds the concept of MetricProvider's and Metrics to the core. +These represent the functionality that metric providers must implement in order to be part of the REF. +The implementation is still a work in progress and will be expanding in follow-up PRs. From f55d257e035bb8d03f0f646906b886f093797a24 Mon Sep 17 00:00:00 2001 From: Jared Lewis Date: Wed, 30 Oct 2024 12:27:10 +1100 Subject: [PATCH 09/14] chore: cleanup --- packages/ref-core/src/ref_core/executor/__init__.py | 13 +++++++++---- packages/ref-core/src/ref_core/executor/local.py | 2 +- packages/ref-metrics-example/README.md | 8 +++++--- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/ref-core/src/ref_core/executor/__init__.py b/packages/ref-core/src/ref_core/executor/__init__.py index 2ce622f..5c4ceb4 100644 --- a/packages/ref-core/src/ref_core/executor/__init__.py +++ b/packages/ref-core/src/ref_core/executor/__init__.py @@ -49,6 +49,8 @@ def run_metric( Configuration to run the metric with trigger Information about the dataset that triggered the metric run + + TODO: The optionality of this parameter is a placeholder and will be expanded in the future. kwargs Additional keyword arguments for the executor @@ -113,7 +115,7 @@ def get(self, name: str) -> Executor: get_executor = _default_manager.get -def run_metric(metric_name: str, /, metrics_provider: MetricsProvider, **kwargs) -> MetricResult: # type: ignore +def run_metric(metric_name: str, /, metrics_provider: MetricsProvider, **kwargs: Any) -> MetricResult: """ Run a metric using the default executor @@ -128,8 +130,6 @@ def run_metric(metric_name: str, /, metrics_provider: MetricsProvider, **kwargs) Name of the metric to run. metrics_provider Provider from where to retrieve the metric - args - Extra arguments passed to the metric of interest kwargs Additional options passed to the metric executor @@ -143,7 +143,12 @@ def run_metric(metric_name: str, /, metrics_provider: MetricsProvider, **kwargs) executor = get_executor(executor_name) metric = metrics_provider.get(metric_name) - return executor.run_metric(metric, trigger=None, **kwargs) + result = executor.run_metric(metric, trigger=None, **kwargs) + + # TODO: Validate the result + # TODO: Log the result + + return result register_executor(LocalExecutor()) diff --git a/packages/ref-core/src/ref_core/executor/local.py b/packages/ref-core/src/ref_core/executor/local.py index ed1d4cb..3fdecc2 100644 --- a/packages/ref-core/src/ref_core/executor/local.py +++ b/packages/ref-core/src/ref_core/executor/local.py @@ -15,7 +15,7 @@ class LocalExecutor: name = "local" def run_metric( - self, metric: Metric, configuration: Configuration, trigger: TriggerInfo | None = None, **kwargs: Any + self, metric: Metric, configuration: Configuration, trigger: TriggerInfo | None, **kwargs: Any ) -> MetricResult: """ Run a metric in process diff --git a/packages/ref-metrics-example/README.md b/packages/ref-metrics-example/README.md index 7c497c5..f8c4c84 100644 --- a/packages/ref-metrics-example/README.md +++ b/packages/ref-metrics-example/README.md @@ -1,4 +1,6 @@ -# ref-core +# ref-metrics-example -This package provides the core functionality for the REF. -This package is designed to be a library so may be published and consumed by othe packages if needed. +An example of a basic REF metrics provider. + +This package provides an example of how to implement a REF metrics provider, +that exposes a single metric, `example_metric`, which is a simple counter. From eaa68d2058b7adbb55cb804bd4e25e76afc7645f Mon Sep 17 00:00:00 2001 From: Jared Lewis Date: Thu, 31 Oct 2024 14:05:58 +1100 Subject: [PATCH 10/14] test: Update tests to pass --- packages/ref-core/tests/unit/test_executor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ref-core/tests/unit/test_executor.py b/packages/ref-core/tests/unit/test_executor.py index 37867b2..120986d 100644 --- a/packages/ref-core/tests/unit/test_executor.py +++ b/packages/ref-core/tests/unit/test_executor.py @@ -23,7 +23,7 @@ def test_is_executor(self): def test_run_metric(self, configuration, mock_metric): executor = LocalExecutor() - result = executor.run_metric(mock_metric, configuration) + result = executor.run_metric(mock_metric, configuration, trigger=None) assert result.successful assert result.output_bundle == configuration.output_directory / "output.json" From d73402896d217d42eaf951a3a6014a906d896f75 Mon Sep 17 00:00:00 2001 From: Jared Lewis Date: Thu, 31 Oct 2024 21:05:14 +1100 Subject: [PATCH 11/14] feat: Make a more real-world example --- packages/ref-core/pyproject.toml | 2 +- packages/ref-core/src/ref_core/metrics.py | 41 +- packages/ref-metrics-example/pyproject.toml | 5 +- .../src/ref_metrics_example/example.py | 93 ++++- .../tests/unit/test_metrics.py | 49 ++- scripts/fetch_test_data.py | 3 +- uv.lock | 384 +++++++++++++----- 7 files changed, 460 insertions(+), 117 deletions(-) diff --git a/packages/ref-core/pyproject.toml b/packages/ref-core/pyproject.toml index d9b2592..464cf1e 100644 --- a/packages/ref-core/pyproject.toml +++ b/packages/ref-core/pyproject.toml @@ -21,7 +21,7 @@ classifiers = [ "Topic :: Scientific/Engineering", ] dependencies = [ - "pydantic>=2.0" + "attrs" ] [tool.uv] diff --git a/packages/ref-core/src/ref_core/metrics.py b/packages/ref-core/src/ref_core/metrics.py index 8270d3d..0c6c51d 100644 --- a/packages/ref-core/src/ref_core/metrics.py +++ b/packages/ref-core/src/ref_core/metrics.py @@ -1,10 +1,12 @@ +import json import pathlib from typing import Protocol, runtime_checkable -from pydantic import BaseModel +from attrs import frozen -class Configuration(BaseModel): +@frozen +class Configuration: """ Configuration that describes the input data sources """ @@ -17,7 +19,8 @@ class Configuration(BaseModel): # TODO: Add more configuration options here -class MetricResult(BaseModel): +@frozen +class MetricResult: """ The result of running a metric. @@ -27,7 +30,7 @@ class MetricResult(BaseModel): # Do we want to load a serialised version of the output bundle here or just a file path? - output_bundle: pathlib.Path + output_bundle: pathlib.Path | None """ Path to the output bundle file. @@ -40,8 +43,36 @@ class MetricResult(BaseModel): """ # Log info is in the output bundle file already, but is definitely useful + @staticmethod + def build(configuration: Configuration, cmec_output_bundle: dict) -> "MetricResult": + """ + Build a MetricResult from a CMEC output bundle. + + Parameters + ---------- + configuration + The configuration used to run the metric. + cmec_output_bundle + An output bundle in the CMEC format. + + TODO: This needs a better type hint + + Returns + ------- + : + A prepared MetricResult object. + The output bundle will be written to the output directory. + """ + with open(configuration.output_directory / "output.json", "w") as file_handle: + json.dump(cmec_output_bundle, file_handle) + return MetricResult( + output_bundle=configuration.output_directory / "output.json", + successful=True, + ) + -class TriggerInfo(BaseModel): +@frozen +class TriggerInfo: """ The reason why the metric was run. """ diff --git a/packages/ref-metrics-example/pyproject.toml b/packages/ref-metrics-example/pyproject.toml index d6310a4..a76af08 100644 --- a/packages/ref-metrics-example/pyproject.toml +++ b/packages/ref-metrics-example/pyproject.toml @@ -21,7 +21,10 @@ classifiers = [ "Topic :: Scientific/Engineering", ] dependencies = [ - "ref-core" + "ref-core", + "xarray >= 2022", + "netcdf4", + "dask>=2024.10.0", ] [tool.uv] diff --git a/packages/ref-metrics-example/src/ref_metrics_example/example.py b/packages/ref-metrics-example/src/ref_metrics_example/example.py index 6f10f44..904e699 100644 --- a/packages/ref-metrics-example/src/ref_metrics_example/example.py +++ b/packages/ref-metrics-example/src/ref_metrics_example/example.py @@ -1,8 +1,77 @@ -import json +from pathlib import Path +import xarray as xr from ref_core.metrics import Configuration, MetricResult, TriggerInfo +def calculate_annual_mean_timeseries(dataset: Path) -> xr.Dataset: + """ + Calculate the annual mean timeseries for a dataset. + + While this function is implemented here, + in most cases the metric calculation will be in the underlying benchmarking package. + How the metric is calculated is up to the provider. + + Parameters + ---------- + dataset + A path to a CMIP6 dataset. + + This dataset may consist of multiple data files. + + Returns + ------- + : + The annual mean timeseries of the dataset + """ + input_files = dataset.glob("*.nc") + + dataset = xr.open_mfdataset(list(input_files), combine="by_coords", chunks=None) + + annual_mean = dataset.resample(time="YS").mean() + return annual_mean.mean(dim=["lat", "lon"], keep_attrs=True) + + +def format_cmec_output_bundle(dataset: xr.Dataset) -> dict: + """ + Create a simple CMEC output bundle for the dataset. + + Parameters + ---------- + dataset + Processed dataset + + Returns + ------- + A CMEC output bundle ready to be written to disk + """ + cmec_output = { + "DIMENSIONS": { + "dimensions": { + "source_id": {dataset.attrs["source_id"]: {}}, + "region": {"global": {}}, + "variable": {"tas": {}}, + }, + "json_structure": [ + "model", + "region", + "statistic", + ], + }, + # Is the schema tracked? + "SCHEMA": { + "name": "CMEC-REF", + "package": "example", + "version": "v1", + }, + "RESULTS": { + dataset.attrs["source_id"]: {"global": {"tas": ""}}, + }, + } + + return cmec_output + + class ExampleMetric: """ Example metric that does nothing but count the number of times it has been run. @@ -10,28 +79,32 @@ class ExampleMetric: name = "example" - def __init__(self) -> None: - self._count = 0 - def run(self, configuration: Configuration, trigger: TriggerInfo | None) -> MetricResult: """ Run a metric Parameters ---------- + trigger + Trigger for what caused the metric to be executed. + configuration + Configuration object Returns ------- : The result of running the metric. """ - self._count += 1 + if trigger is None: + # TODO: This should probably raise an exception + return MetricResult( + output_bundle=configuration.output_directory / "output.json", + successful=False, + ) - with open(configuration.output_directory / "output.json", "w") as fh: - json.dump(({"count": self._count}), fh) + annual_mean_global_mean_timeseries = calculate_annual_mean_timeseries(trigger.dataset) - return MetricResult( - output_bundle=configuration.output_directory / "output.json", - successful=True, + return MetricResult.build( + configuration, format_cmec_output_bundle(annual_mean_global_mean_timeseries) ) diff --git a/packages/ref-metrics-example/tests/unit/test_metrics.py b/packages/ref-metrics-example/tests/unit/test_metrics.py index 9163114..3b65543 100644 --- a/packages/ref-metrics-example/tests/unit/test_metrics.py +++ b/packages/ref-metrics-example/tests/unit/test_metrics.py @@ -1,15 +1,56 @@ -from ref_core.metrics import Configuration -from ref_metrics_example.example import ExampleMetric +from pathlib import Path +import pytest +from ref_core.metrics import Configuration, TriggerInfo +from ref_metrics_example.example import ExampleMetric, calculate_annual_mean_timeseries -def test_example_metric(tmp_path): + +@pytest.fixture +def test_dataset(esgf_data_dir) -> Path: + return ( + esgf_data_dir + / "CMIP6" + / "ScenarioMIP" + / "CSIRO" + / "ACCESS-ESM1-5" + / "ssp245" + / "r1i1p1f1" + / "Amon" + / "tas" + / "gn" + / "v20191115" + ) + + +def test_annual_mean(esgf_data_dir): + input_files = list( + ( + esgf_data_dir + / "CMIP6" + / "ScenarioMIP" + / "CSIRO" + / "ACCESS-ESM1-5" + / "ssp245" + / "r1i1p1f1" + / "Amon" + / "tas" + / "gn" + / "v20191115" + ).glob("*.nc") + ) + annual_mean = calculate_annual_mean_timeseries(input_files) + + assert annual_mean.time.size == 86 + + +def test_example_metric(tmp_path, test_dataset): metric = ExampleMetric() configuration = Configuration( output_directory=tmp_path, ) - result = metric.run(configuration, trigger=None) + result = metric.run(configuration, trigger=TriggerInfo(dataset=test_dataset)) assert result.successful assert result.output_bundle.exists() diff --git a/scripts/fetch_test_data.py b/scripts/fetch_test_data.py index 84d1d90..96e6737 100644 --- a/scripts/fetch_test_data.py +++ b/scripts/fetch_test_data.py @@ -67,7 +67,7 @@ def queue_esgf_download( # noqa: PLR0913 ] ) - res = subprocess.run( + subprocess.run( [ "esgpull", "update", @@ -78,7 +78,6 @@ def queue_esgf_download( # noqa: PLR0913 input=b"y", check=False, ) - res.check_returncode() return search_tag diff --git a/uv.lock b/uv.lock index 545195d..b9f7a23 100644 --- a/uv.lock +++ b/uv.lock @@ -1,7 +1,9 @@ version = 1 requires-python = ">=3.10" resolution-markers = [ - "python_full_version < '3.13'", + "python_full_version < '3.11'", + "python_full_version == '3.11.*'", + "python_full_version == '3.12.*'", "python_full_version >= '3.13'", ] @@ -56,15 +58,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c2/12/58f4f11385fddafef5d6f7bfaaf2f42899c8da6b4f95c04b7c3b744851a8/alembic-1.13.3-py3-none-any.whl", hash = "sha256:908e905976d15235fae59c9ac42c4c5b75cfcefe3d27c0fbf7ae15a37715d80e", size = 233217 }, ] -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, -] - [[package]] name = "anyio" version = "4.6.0" @@ -291,6 +284,41 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, ] +[[package]] +name = "cftime" +version = "1.6.4.post1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/c8/1155d1d58003105307c7e5985f422ae5bcb2ca0cbc553cc828f3c5a934a7/cftime-1.6.4.post1.tar.gz", hash = "sha256:50ac76cc9f10ab7bd46e44a71c51a6927051b499b4407df4f29ab13d741b942f", size = 54631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/6a/7ebd692ccf5b28d8c5e170fd11b0a2945f530392bc9887e858a0302b1745/cftime-1.6.4.post1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0baa9bc4850929da9f92c25329aa1f651e2d6f23e237504f337ee9e12a769f5d", size = 233017 }, + { url = "https://files.pythonhosted.org/packages/b9/65/3b7a11139282f81ce40872acad7f99b65291f7401ceec7b6bb94c39c8441/cftime-1.6.4.post1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6bb6b087f4b2513c37670bccd457e2a666ca489c5f2aad6e2c0e94604dc1b5b9", size = 213927 }, + { url = "https://files.pythonhosted.org/packages/70/e3/1a56832b13ce0c5f3b798bf7bc60d4550fa1c514e04b613f9b0e48edc535/cftime-1.6.4.post1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d9bdeb9174962c9ca00015190bfd693de6b0ec3ec0b3dbc35c693a4f48efdcc", size = 1252052 }, + { url = "https://files.pythonhosted.org/packages/5c/aa/f62ce24417ecb19f5ba1aa1dbe72394d11f11f5e53fc53497ccfaab83d3c/cftime-1.6.4.post1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e735cfd544878eb94d0108ff5a093bd1a332dba90f979a31a357756d609a90d5", size = 1289731 }, + { url = "https://files.pythonhosted.org/packages/e4/21/0cf99e16e9953d17cc37286201922d07f17ffc1743dbc50d0c9e6f98ddda/cftime-1.6.4.post1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1dcd1b140bf50da6775c56bd7ca179e84bd258b2f159b53eefd5c514b341f2bf", size = 1317229 }, + { url = "https://files.pythonhosted.org/packages/68/0f/95ce359a3bd91a8ec9b79d4961753053c72a5115e820a072d451568684c3/cftime-1.6.4.post1-cp310-cp310-win_amd64.whl", hash = "sha256:e60b8f24b20753f7548f410f7510e28b941f336f84bd34e3cfd7874af6e70281", size = 189078 }, + { url = "https://files.pythonhosted.org/packages/85/e6/6a7d2120fcffee208cf637d22b0d8f2701d91f69f68a96940056429950f3/cftime-1.6.4.post1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1bf7be0a0afc87628cb8c8483412aac6e48e83877004faa0936afb5bf8a877ba", size = 233445 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/fe0d14d52cffa72d3f1c281ff9f0f384968058d86ce24fdf9e736ce5b755/cftime-1.6.4.post1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0f64ca83acc4e3029f737bf3a32530ffa1fbf53124f5bee70b47548bc58671a7", size = 214458 }, + { url = "https://files.pythonhosted.org/packages/55/c6/72f8fb5ee057f33ab747ba361f1396d2839a4689669aabd6217bc38430f7/cftime-1.6.4.post1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7ebdfd81726b0cfb8b524309224fa952898dfa177c13d5f6af5b18cefbf497d", size = 1379075 }, + { url = "https://files.pythonhosted.org/packages/77/81/6b30815698ede50f89013f25e46d66ed3a290b8a2d6b97f95bacbbe1eb5c/cftime-1.6.4.post1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9ea0965a4c87739aebd84fe8eed966e5809d10065eeffd35c99c274b6f8da15", size = 1415218 }, + { url = "https://files.pythonhosted.org/packages/24/0d/73ab09a32da1478d3ef5f4ab6c59d42f2db2a2383b427c87e05ad81b71ad/cftime-1.6.4.post1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:800a18aea4e8cb2b206450397cb8a53b154798738af3cdd3c922ce1ca198b0e6", size = 1450704 }, + { url = "https://files.pythonhosted.org/packages/79/b1/6551603f8ea31de55913c84e4def3c36670563bdea6e195fcc4b6225ddf7/cftime-1.6.4.post1-cp311-cp311-win_amd64.whl", hash = "sha256:5dcfc872f455db1f12eabe3c3ba98e93757cd60ed3526a53246e966ccde46c8a", size = 190200 }, + { url = "https://files.pythonhosted.org/packages/50/81/0bb28d54088a61592f61a11e7fcabcea6d261c47af79e18d0f9cbcd940ae/cftime-1.6.4.post1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a590f73506f4704ba5e154ef55bfbaed5e1b4ac170f3caeb8c58e4f2c619ee4e", size = 226615 }, + { url = "https://files.pythonhosted.org/packages/f3/1e/38dbbf8a828dfb5e0e6e5c912818b77aacf2e7bcb97b262ac6126beeb29f/cftime-1.6.4.post1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:933cb10e1af4e362e77f513e3eb92b34a688729ddbf938bbdfa5ac20a7f44ba0", size = 209193 }, + { url = "https://files.pythonhosted.org/packages/9b/60/0db884c76311ecaaf31f628aa9358beae5fcb0fbbdc2eb0b790a93aa258f/cftime-1.6.4.post1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf17a1b36f62e9e73c4c9363dd811e1bbf1170f5ac26d343fb26012ccf482908", size = 1320215 }, + { url = "https://files.pythonhosted.org/packages/8d/7d/2d5fc7af06da4f3bdea59a204f741bf7a30bc5019355991b2f083e557e4e/cftime-1.6.4.post1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e18021f421aa26527bad8688c1acf0c85fa72730beb6efce969c316743294f2", size = 1367426 }, + { url = "https://files.pythonhosted.org/packages/5d/ab/e8b26d05323fc5629356c82a7f64026248f121ea1361b49df441bbc8f2d7/cftime-1.6.4.post1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5835b9d622f9304d1c23a35603a0f068739f428d902860f25e6e7e5a1b7cd8ea", size = 1385593 }, + { url = "https://files.pythonhosted.org/packages/af/7b/ca72a075a3f660315b031d62d39a3e9cfef71f7929da2621d5120077a75f/cftime-1.6.4.post1-cp312-cp312-win_amd64.whl", hash = "sha256:7f50bf0d1b664924aaee636eb2933746b942417d1f8b82ab6c1f6e8ba0da6885", size = 178918 }, + { url = "https://files.pythonhosted.org/packages/da/d8/81f086dbdc6f5a4e0bb068263471f1d12861b72562fe8c18df38268e4e29/cftime-1.6.4.post1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5c89766ebf088c097832ea618c24ed5075331f0b7bf8e9c2d4144aefbf2f1850", size = 223418 }, + { url = "https://files.pythonhosted.org/packages/4a/cc/60a825d92a4023655e330470758280a31e7b82665ef77d0e2a0fe71ea958/cftime-1.6.4.post1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f27113f7ccd1ca32881fdcb9a4bec806a5f54ae621fc1c374f1171f3ed98ef2", size = 207395 }, + { url = "https://files.pythonhosted.org/packages/ca/90/f5b26949899decce262fc76a1e64915b92050473114e0160cd6f7297f854/cftime-1.6.4.post1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da367b23eea7cf4df071c88e014a1600d6c5bbf22e3393a4af409903fa397e28", size = 1318113 }, + { url = "https://files.pythonhosted.org/packages/c3/f8/6f13d37abb7ade46e65a08acc31af776a96dde0eb569e05d4c4b01422ba6/cftime-1.6.4.post1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6579c5c83cdf09d73aa94c7bc34925edd93c5f2c7dd28e074f568f7e376271a0", size = 1366034 }, + { url = "https://files.pythonhosted.org/packages/fa/08/335cb17f3b708f9a24f96ca4abb00889c7aa20b0ae273313e7c11faf1f97/cftime-1.6.4.post1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6b731c7133d17b479ca0c3c46a7a04f96197f0a4d753f4c2284c3ff0447279b4", size = 1390156 }, + { url = "https://files.pythonhosted.org/packages/f3/2d/980323fb5ec1ef369604b61ba259a41d0336cc1a85b639ed7bd210bd1290/cftime-1.6.4.post1-cp313-cp313-win_amd64.whl", hash = "sha256:d2a8c223faea7f1248ab469cc0d7795dd46f2a423789038f439fee7190bae259", size = 178496 }, +] + [[package]] name = "charset-normalizer" version = "3.3.2" @@ -371,6 +399,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ca/c7/a04832e84f1c613194231a657612aee2e377d63a44a5847386c83c38bbd6/click_params-0.5.0-py3-none-any.whl", hash = "sha256:bbb2efe44197ab896bffcb50f42f22240fb077e6756b568fbdab3e1700b859d6", size = 13152 }, ] +[[package]] +name = "cloudpickle" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/97/c7/f746cadd08c4c08129215cf1b984b632f9e579fc781301e63da9e85c76c1/cloudpickle-3.1.0.tar.gz", hash = "sha256:81a929b6e3c7335c863c771d673d105f02efdb89dfaba0c90495d1c64796601b", size = 66155 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/41/e1d85ca3cab0b674e277c8c4f678cf66a91cd2cecf93df94353a606fe0db/cloudpickle-3.1.0-py3-none-any.whl", hash = "sha256:fe11acda67f61aaaec473e3afe030feb131d78a43461b718185363384f1ba12e", size = 22021 }, +] + [[package]] name = "cmip-ref" version = "0.1.0" @@ -552,6 +589,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/91/bb/cd2c13be3332e7af3cdf16154147952d39075b9f61ea5e6b5241bf4bf436/cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7", size = 2988811 }, ] +[[package]] +name = "dask" +version = "2024.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "cloudpickle" }, + { name = "fsspec" }, + { name = "importlib-metadata", marker = "python_full_version < '3.12'" }, + { name = "packaging" }, + { name = "partd" }, + { name = "pyyaml" }, + { name = "toolz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/38/10d4e3c5625b118c5a3b3066bb8bd473cc211434a4486c0ea5d93bc575be/dask-2024.10.0.tar.gz", hash = "sha256:dfd3efec5d8d8340fb647d0347637133030cad261b714623cc27de286e9db037", size = 10154629 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/ec/0fa1997dd7070a336063663a1305e6967b7c906bd7907f2435c5ad049b0e/dask-2024.10.0-py3-none-any.whl", hash = "sha256:1ddc27c7967e134b4f8296a488521485a5ac4927cc63e2abfa0b24227b93217f", size = 1259489 }, +] + [[package]] name = "debugpy" version = "1.8.6" @@ -695,6 +751,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121 }, ] +[[package]] +name = "fsspec" +version = "2024.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a0/52/f16a068ebadae42526484c31f4398e62962504e5724a8ba5dc3409483df2/fsspec-2024.10.0.tar.gz", hash = "sha256:eda2d8a4116d4f2429db8550f2457da57279247dd930bb12f821b58391359493", size = 286853 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/b2/454d6e7f0158951d8a78c2e1eb4f69ae81beb8dca5fee9809c6c99e9d0d0/fsspec-2024.10.0-py3-none-any.whl", hash = "sha256:03b9a6785766a4de40368b88906366755e2819e758b83705c88cd7cb5fe81871", size = 179641 }, +] + [[package]] name = "ghp-import" version = "2.1.0" @@ -1199,6 +1264,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/bb/fbc7dd6ea215b97b90c35efc8c8f3dbfcbacb91af8c806dff1f49deddd8e/liccheck-0.9.2-py2.py3-none-any.whl", hash = "sha256:15cbedd042515945fe9d58b62e0a5af2f2a7795def216f163bb35b3016a16637", size = 13652 }, ] +[[package]] +name = "locket" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/83/97b29fe05cb6ae28d2dbd30b81e2e402a3eed5f460c26e9eaa5895ceacf5/locket-1.0.0.tar.gz", hash = "sha256:5c0d4c052a8bbbf750e056a8e65ccd309086f4f0f18a2eac306a8dfa4112a632", size = 4350 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl", hash = "sha256:b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3", size = 4398 }, +] + [[package]] name = "mako" version = "1.3.6" @@ -1657,6 +1731,39 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 }, ] +[[package]] +name = "netcdf4" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "cftime" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/ed/4d27fcfa40ebfdad3d2088a3de7ee48dbff7f35163e815ec1870d2a7398c/netcdf4-1.7.2.tar.gz", hash = "sha256:a4c6375540b19989896136943abb6d44850ff6f1fa7d3f063253b1ad3f8b7fce", size = 835064 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/00/2b1fb43e46e3d986e961e420046453796d67200b58639bd29f18657a39b7/netCDF4-1.7.2-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:5e9b485e3bd9294d25ff7dc9addefce42b3d23c1ee7e3627605277d159819392", size = 2977508 }, + { url = "https://files.pythonhosted.org/packages/81/c2/a5001f25de53b7312609d2733ac887a5051c1ce196288af4b9777ead5a75/netCDF4-1.7.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:118b476fd00d7e3ab9aa7771186d547da645ae3b49c0c7bdab866793ebf22f07", size = 2461128 }, + { url = "https://files.pythonhosted.org/packages/da/33/ecb4790d053c58ec03f940ab55aacb59a207e356e57792cfd4b4eedbcc4d/netCDF4-1.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abe5b1837ff209185ecfe50bd71884c866b3ee69691051833e410e57f177e059", size = 9210818 }, + { url = "https://files.pythonhosted.org/packages/db/a6/54f0f335b28228b89e1598fda950382c83b1d7b1f75d28c5eebbcb7f113e/netCDF4-1.7.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28021c7e886e5bccf9a8ce504c032d1d7f98d86f67495fb7cf2c9564eba04510", size = 9059470 }, + { url = "https://files.pythonhosted.org/packages/a7/ea/80b9feddd36721f92bac056a7dea41cd48bd4fc676f3f248fc48332d0bd2/netCDF4-1.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:7460b638e41c8ce4179d082a81cb6456f0ce083d4d959f4d9e87a95cd86f64cb", size = 7005418 }, + { url = "https://files.pythonhosted.org/packages/a0/d8/b7079ecbab35f7c95ab27e5146fa91daf0e39ba76093f0fc1187fc748749/netCDF4-1.7.2-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:09d61c2ddb6011afb51e77ea0f25cd0bdc28887fb426ffbbc661d920f20c9749", size = 2981078 }, + { url = "https://files.pythonhosted.org/packages/4b/c1/ae83fdcc05d1db00a340f5f3e252247d73f11f8eaa890c59e7b5c8e35b56/netCDF4-1.7.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:fd2a16dbddeb8fa7cf48c37bfc1967290332f2862bb82f984eec2007bb120aeb", size = 2462104 }, + { url = "https://files.pythonhosted.org/packages/f2/bd/6f76916fae5d375eedd0cb48acd713d8d8db267d0c3cf3d209a4631923a5/netCDF4-1.7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f54f5d39ffbcf1726a1e6fd90cb5fa74277ecea739a5fa0f424636d71beafe24", size = 9451498 }, + { url = "https://files.pythonhosted.org/packages/18/c1/7e564dbd28228ba4a35a272bf53b9a2e8b0ba9ac06b2c84b57c03c84e87b/netCDF4-1.7.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:902aa50d70f49d002d896212a171d344c38f7b8ca520837c56c922ac1535c4a3", size = 9283073 }, + { url = "https://files.pythonhosted.org/packages/cf/ba/d26e8278ad8a2306580bab076b6d64cd16459a60e632e6c1a9cbb68dd3d9/netCDF4-1.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:3291f9ad0c98c49a4dd16aefad1a9abd3a1b884171db6c81bdcee94671cfabe3", size = 7010215 }, + { url = "https://files.pythonhosted.org/packages/52/7f/3a0f18a39efca0e093b54d634b66573c25ecab5c482d73138ae14aa55c6d/netCDF4-1.7.2-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:e73e3baa0b74afc414e53ff5095748fdbec7fb346eda351e567c23f2f0d247f1", size = 2952127 }, + { url = "https://files.pythonhosted.org/packages/ed/c4/8aac0f8ca95a41bdf1364d34ff4e9bcc24494bfe69a1157301d884c2e392/netCDF4-1.7.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a51da09258b31776f474c1d47e484fc7214914cdc59edf4cee789ba632184591", size = 2460781 }, + { url = "https://files.pythonhosted.org/packages/2d/1a/32b7427aaf62fed3d4e4456f874b25ce39373dbddf6cfde9edbcfc2417fc/netCDF4-1.7.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb95b11804fe051897d1f2044b05d82a1847bc2549631cdd2f655dde7de77a9c", size = 9377415 }, + { url = "https://files.pythonhosted.org/packages/fd/bf/5e671495c8bdf6b628e091aa8980793579474a10e51bc6ba302a3af6a778/netCDF4-1.7.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9d8a848373723f41ef662590b4f5e1832227501c9fd4513e8ad8da58c269977", size = 9260579 }, + { url = "https://files.pythonhosted.org/packages/d4/57/0a0bcdebcfaf72e96e7bcaa512f80ee096bf71945a3318d38253338e9c25/netCDF4-1.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:568ea369e00b581302d77fc5fd0b8f78e520c7e08d0b5af5219ba51f3f1cd694", size = 6991523 }, + { url = "https://files.pythonhosted.org/packages/e6/7a/ce4f9038d8726c9c90e07b2d3a404ae111a27720d712cfcded0c8ef160e8/netCDF4-1.7.2-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:205a5f1de3ddb993c7c97fb204a923a22408cc2e5facf08d75a8eb89b3e7e1a8", size = 2948911 }, + { url = "https://files.pythonhosted.org/packages/58/3e/5736880a607edabca4c4fc49f1ccf9a2bb2485f84478e4cd19ba11c3b803/netCDF4-1.7.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:96653fc75057df196010818367c63ba6d7e9af603df0a7fe43fcdad3fe0e9e56", size = 2455078 }, + { url = "https://files.pythonhosted.org/packages/71/96/d5d8859a6dac29f8ebc815ff8e75770bd513db9f08d7a711e21ae562a948/netCDF4-1.7.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30d20e56b9ba2c48884eb89c91b63e6c0612b4927881707e34402719153ef17f", size = 9378149 }, + { url = "https://files.pythonhosted.org/packages/d1/80/b9c19f1bb4ac6c5fa6f94a4f278bc68a778473d1814a86a375d7cffa193a/netCDF4-1.7.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d6bfd38ba0bde04d56f06c1554714a2ea9dab75811c89450dc3ec57a9d36b80", size = 9254471 }, + { url = "https://files.pythonhosted.org/packages/66/b5/e04550fd53de57001dbd5a87242da7ff784c80790adc48897977b6ccf891/netCDF4-1.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:5c5fbee6134ee1246c397e1508e5297d825aa19221fdf3fa8dc9727ad824d7a5", size = 6990521 }, +] + [[package]] name = "nodeenv" version = "1.9.1" @@ -1678,6 +1785,66 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307 }, ] +[[package]] +name = "numpy" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/d1/8a730ea07f4a37d94f9172f4ce1d81064b7a64766b460378be278952de75/numpy-2.1.2.tar.gz", hash = "sha256:13532a088217fa624c99b843eeb54640de23b3414b14aa66d023805eb731066c", size = 18878063 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/a2/40a76d357f168e9f9f06d6cc2c8e22dd5fb2bfbe63fe2c433057258c145a/numpy-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30d53720b726ec36a7f88dc873f0eec8447fbc93d93a8f079dfac2629598d6ee", size = 21150947 }, + { url = "https://files.pythonhosted.org/packages/b5/d0/ba271ea9108d7278d3889a7eb38d77370a88713fb94339964e71ac184d4a/numpy-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8d3ca0a72dd8846eb6f7dfe8f19088060fcb76931ed592d29128e0219652884", size = 13758184 }, + { url = "https://files.pythonhosted.org/packages/7c/b9/5c6507439cd756201010f7937bf90712c2469052ae094584af14557dd64f/numpy-2.1.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:fc44e3c68ff00fd991b59092a54350e6e4911152682b4782f68070985aa9e648", size = 5354091 }, + { url = "https://files.pythonhosted.org/packages/60/21/7938cf724d9e84e45fb886f3fc794ab431d71facfebc261e3e9f19f3233a/numpy-2.1.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:7c1c60328bd964b53f8b835df69ae8198659e2b9302ff9ebb7de4e5a5994db3d", size = 6887169 }, + { url = "https://files.pythonhosted.org/packages/09/8d/42a124657f5d31902fca73921b25a0d022cead2b32ce7e6975762cd2995a/numpy-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cdb606a7478f9ad91c6283e238544451e3a95f30fb5467fbf715964341a8a86", size = 13888165 }, + { url = "https://files.pythonhosted.org/packages/fb/25/ba023652a39a2c127200e85aed975fc6119b421e2c348e5d0171e2046edb/numpy-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d666cb72687559689e9906197e3bec7b736764df6a2e58ee265e360663e9baf7", size = 16326954 }, + { url = "https://files.pythonhosted.org/packages/34/58/23e6b07fad492b7c47cf09cd8bad6983658f0f925b6c535fd008e3e86274/numpy-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6eef7a2dbd0abfb0d9eaf78b73017dbfd0b54051102ff4e6a7b2980d5ac1a03", size = 16702916 }, + { url = "https://files.pythonhosted.org/packages/91/24/37b5cf2dc7d385ac97f7b7fe50cba312abb70a2a5eac74c23af028811f73/numpy-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:12edb90831ff481f7ef5f6bc6431a9d74dc0e5ff401559a71e5e4611d4f2d466", size = 14384372 }, + { url = "https://files.pythonhosted.org/packages/ea/ec/0f6d471058a01d1a05a50d2793898de1549280fa715a8537987ee866b5d9/numpy-2.1.2-cp310-cp310-win32.whl", hash = "sha256:a65acfdb9c6ebb8368490dbafe83c03c7e277b37e6857f0caeadbbc56e12f4fb", size = 6535361 }, + { url = "https://files.pythonhosted.org/packages/c2/3d/293cc5927f916a7bc6bf74da8f6defab63d1b13f0959d7e21878ad8a20d8/numpy-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:860ec6e63e2c5c2ee5e9121808145c7bf86c96cca9ad396c0bd3e0f2798ccbe2", size = 12865501 }, + { url = "https://files.pythonhosted.org/packages/aa/9c/9a6ec3ae89cd0648d419781284308f2956d2a61d932b5ac9682c956a171b/numpy-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b42a1a511c81cc78cbc4539675713bbcf9d9c3913386243ceff0e9429ca892fe", size = 21154845 }, + { url = "https://files.pythonhosted.org/packages/02/69/9f05c4ecc75fabf297b17743996371b4c3dfc4d92e15c5c38d8bb3db8d74/numpy-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:faa88bc527d0f097abdc2c663cddf37c05a1c2f113716601555249805cf573f1", size = 13789409 }, + { url = "https://files.pythonhosted.org/packages/34/4e/f95c99217bf77bbfaaf660d693c10bd0dc03b6032d19316d316088c9e479/numpy-2.1.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c82af4b2ddd2ee72d1fc0c6695048d457e00b3582ccde72d8a1c991b808bb20f", size = 5352097 }, + { url = "https://files.pythonhosted.org/packages/06/13/f5d87a497c16658e9af8920449b0b5692b469586b8231340c672962071c5/numpy-2.1.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:13602b3174432a35b16c4cfb5de9a12d229727c3dd47a6ce35111f2ebdf66ff4", size = 6891195 }, + { url = "https://files.pythonhosted.org/packages/6c/89/691ac07429ac061b344d5e37fa8e94be51a6017734aea15f2d9d7c6d119a/numpy-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ebec5fd716c5a5b3d8dfcc439be82a8407b7b24b230d0ad28a81b61c2f4659a", size = 13895153 }, + { url = "https://files.pythonhosted.org/packages/23/69/538317f0d925095537745f12aced33be1570bbdc4acde49b33748669af96/numpy-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2b49c3c0804e8ecb05d59af8386ec2f74877f7ca8fd9c1e00be2672e4d399b1", size = 16338306 }, + { url = "https://files.pythonhosted.org/packages/af/03/863fe7062c2106d3c151f7df9353f2ae2237c1dd6900f127a3eb1f24cb1b/numpy-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cbba4b30bf31ddbe97f1c7205ef976909a93a66bb1583e983adbd155ba72ac2", size = 16710893 }, + { url = "https://files.pythonhosted.org/packages/70/77/0ad9efe25482009873f9660d29a40a8c41a6f0e8b541195e3c95c70684c5/numpy-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8e00ea6fc82e8a804433d3e9cedaa1051a1422cb6e443011590c14d2dea59146", size = 14398048 }, + { url = "https://files.pythonhosted.org/packages/3e/0f/e785fe75544db9f2b0bb1c181e13ceff349ce49753d807fd9672916aa06d/numpy-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5006b13a06e0b38d561fab5ccc37581f23c9511879be7693bd33c7cd15ca227c", size = 6533458 }, + { url = "https://files.pythonhosted.org/packages/d4/96/450054662295125af861d48d2c4bc081dadcf1974a879b2104613157aa62/numpy-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:f1eb068ead09f4994dec71c24b2844f1e4e4e013b9629f812f292f04bd1510d9", size = 12870896 }, + { url = "https://files.pythonhosted.org/packages/a0/7d/554a6838f37f3ada5a55f25173c619d556ae98092a6e01afb6e710501d70/numpy-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7bf0a4f9f15b32b5ba53147369e94296f5fffb783db5aacc1be15b4bf72f43b", size = 20848077 }, + { url = "https://files.pythonhosted.org/packages/b0/29/cb48a402ea879e645b16218718f3f7d9588a77d674a9dcf22e4c43487636/numpy-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b1d0fcae4f0949f215d4632be684a539859b295e2d0cb14f78ec231915d644db", size = 13493242 }, + { url = "https://files.pythonhosted.org/packages/56/44/f899b0581766c230da42f751b7b8896d096640b19b312164c267e48d36cb/numpy-2.1.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f751ed0a2f250541e19dfca9f1eafa31a392c71c832b6bb9e113b10d050cb0f1", size = 5089219 }, + { url = "https://files.pythonhosted.org/packages/79/8f/b987070d45161a7a4504afc67ed38544ed2c0ed5576263599a0402204a9c/numpy-2.1.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bd33f82e95ba7ad632bc57837ee99dba3d7e006536200c4e9124089e1bf42426", size = 6620167 }, + { url = "https://files.pythonhosted.org/packages/c4/a7/af3329fda3c3ec31d9b650e42bbcd3422fc62a765cbb1405fde4177a0996/numpy-2.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b8cde4f11f0a975d1fd59373b32e2f5a562ade7cde4f85b7137f3de8fbb29a0", size = 13604905 }, + { url = "https://files.pythonhosted.org/packages/9b/b4/e3c7e6fab0f77fff6194afa173d1f2342073d91b1d3b4b30b17c3fb4407a/numpy-2.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d95f286b8244b3649b477ac066c6906fbb2905f8ac19b170e2175d3d799f4df", size = 16041825 }, + { url = "https://files.pythonhosted.org/packages/e9/50/6828e66a78aa03147c111f84d55f33ce2dde547cb578d6744a3b06a0124b/numpy-2.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ab4754d432e3ac42d33a269c8567413bdb541689b02d93788af4131018cbf366", size = 16409541 }, + { url = "https://files.pythonhosted.org/packages/bf/72/66af7916d9c3c6dbfbc8acdd4930c65461e1953374a2bc43d00f948f004a/numpy-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e585c8ae871fd38ac50598f4763d73ec5497b0de9a0ab4ef5b69f01c6a046142", size = 14081134 }, + { url = "https://files.pythonhosted.org/packages/dc/5a/59a67d84f33fe00ae74f0b5b69dd4f93a586a4aba7f7e19b54b2133db038/numpy-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9c6c754df29ce6a89ed23afb25550d1c2d5fdb9901d9c67a16e0b16eaf7e2550", size = 6237784 }, + { url = "https://files.pythonhosted.org/packages/4c/79/73735a6a5dad6059c085f240a4e74c9270feccd2bc66e4d31b5ca01d329c/numpy-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:456e3b11cb79ac9946c822a56346ec80275eaf2950314b249b512896c0d2505e", size = 12568254 }, + { url = "https://files.pythonhosted.org/packages/16/72/716fa1dbe92395a9a623d5049203ff8ddb0cfce65b9df9117c3696ccc011/numpy-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a84498e0d0a1174f2b3ed769b67b656aa5460c92c9554039e11f20a05650f00d", size = 20834690 }, + { url = "https://files.pythonhosted.org/packages/1e/fb/3e85a39511586053b5c6a59a643879e376fae22230ebfef9cfabb0e032e2/numpy-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4d6ec0d4222e8ffdab1744da2560f07856421b367928026fb540e1945f2eeeaf", size = 13507474 }, + { url = "https://files.pythonhosted.org/packages/35/eb/5677556d9ba13436dab51e129f98d4829d95cd1b6bd0e199c14485a4bdb9/numpy-2.1.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:259ec80d54999cc34cd1eb8ded513cb053c3bf4829152a2e00de2371bd406f5e", size = 5074742 }, + { url = "https://files.pythonhosted.org/packages/3e/c5/6c5ef5ba41b65a7e51bed50dbf3e1483eb578055633dd013e811a28e96a1/numpy-2.1.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:675c741d4739af2dc20cd6c6a5c4b7355c728167845e3c6b0e824e4e5d36a6c3", size = 6606787 }, + { url = "https://files.pythonhosted.org/packages/08/ac/f2f29dd4fd325b379c7dc932a0ebab22f0e031dbe80b2f6019b291a3a544/numpy-2.1.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b2d4e667895cc55e3ff2b56077e4c8a5604361fc21a042845ea3ad67465aa8", size = 13601333 }, + { url = "https://files.pythonhosted.org/packages/44/26/63f5f4e5089654dfb858f4892215ed968cd1a68e6f4a83f9961f84f855cb/numpy-2.1.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43cca367bf94a14aca50b89e9bc2061683116cfe864e56740e083392f533ce7a", size = 16038090 }, + { url = "https://files.pythonhosted.org/packages/1d/21/015e0594de9c3a8d5edd24943d2bd23f102ec71aec026083f822f86497e2/numpy-2.1.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:76322dcdb16fccf2ac56f99048af32259dcc488d9b7e25b51e5eca5147a3fb98", size = 16410865 }, + { url = "https://files.pythonhosted.org/packages/df/01/c1bcf9e6025d79077fbf3f3ee503b50aa7bfabfcd8f4b54f5829f4c00f3f/numpy-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:32e16a03138cabe0cb28e1007ee82264296ac0983714094380b408097a418cfe", size = 14078077 }, + { url = "https://files.pythonhosted.org/packages/ba/06/db9d127d63bd11591770ba9f3d960f8041e0f895184b9351d4b1b5b56983/numpy-2.1.2-cp313-cp313-win32.whl", hash = "sha256:242b39d00e4944431a3cd2db2f5377e15b5785920421993770cddb89992c3f3a", size = 6234904 }, + { url = "https://files.pythonhosted.org/packages/a9/96/9f61f8f95b6e0ea0aa08633b704c75d1882bdcb331bdf8bfd63263b25b00/numpy-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f2ded8d9b6f68cc26f8425eda5d3877b47343e68ca23d0d0846f4d312ecaa445", size = 12561910 }, + { url = "https://files.pythonhosted.org/packages/36/b8/033f627821784a48e8f75c218033471eebbaacdd933f8979c79637a1b44b/numpy-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ffef621c14ebb0188a8633348504a35c13680d6da93ab5cb86f4e54b7e922b5", size = 20857719 }, + { url = "https://files.pythonhosted.org/packages/96/46/af5726fde5b74ed83f2f17a73386d399319b7ed4d51279fb23b721d0816d/numpy-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ad369ed238b1959dfbade9018a740fb9392c5ac4f9b5173f420bd4f37ba1f7a0", size = 13518826 }, + { url = "https://files.pythonhosted.org/packages/db/6e/8ce677edf36da1c4dae80afe5529f47690697eb55b4864673af260ccea7b/numpy-2.1.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d82075752f40c0ddf57e6e02673a17f6cb0f8eb3f587f63ca1eaab5594da5b17", size = 5115036 }, + { url = "https://files.pythonhosted.org/packages/6a/ba/3cce44fb1b8438042c11847048812a776f75ee0e7070179c22e4cfbf420c/numpy-2.1.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:1600068c262af1ca9580a527d43dc9d959b0b1d8e56f8a05d830eea39b7c8af6", size = 6628641 }, + { url = "https://files.pythonhosted.org/packages/59/c8/e722998720ccbd35ffbcf1d1b8ed0aa2304af88d3f1c38e06ebf983599b3/numpy-2.1.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a26ae94658d3ba3781d5e103ac07a876b3e9b29db53f68ed7df432fd033358a8", size = 13574803 }, + { url = "https://files.pythonhosted.org/packages/7c/8e/fc1fdd83a55476765329ac2913321c4aed5b082a7915095628c4ca30ea72/numpy-2.1.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13311c2db4c5f7609b462bc0f43d3c465424d25c626d95040f073e30f7570e35", size = 16021174 }, + { url = "https://files.pythonhosted.org/packages/2a/b6/a790742aa88067adb4bd6c89a946778c1417d4deaeafce3ca928f26d4c52/numpy-2.1.2-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:2abbf905a0b568706391ec6fa15161fad0fb5d8b68d73c461b3c1bab6064dd62", size = 16400117 }, + { url = "https://files.pythonhosted.org/packages/48/6f/129e3c17e3befe7fefdeaa6890f4c4df3f3cf0831aa053802c3862da67aa/numpy-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ef444c57d664d35cac4e18c298c47d7b504c66b17c2ea91312e979fcfbdfb08a", size = 14066202 }, + { url = "https://files.pythonhosted.org/packages/73/c9/3e1d6bbe6d3d2e2c5a9483b24b2f29a229b323f62054278a3bba7fee11e5/numpy-2.1.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:bdd407c40483463898b84490770199d5714dcc9dd9b792f6c6caccc523c00952", size = 20981945 }, + { url = "https://files.pythonhosted.org/packages/6e/62/989c4988bde1a8e08117fccc3bab73d2886421fb98cde597168714f3c54e/numpy-2.1.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:da65fb46d4cbb75cb417cddf6ba5e7582eb7bb0b47db4b99c9fe5787ce5d91f5", size = 6750558 }, + { url = "https://files.pythonhosted.org/packages/53/b1/00ef9f30975f1312a53257f68e57b4513d14d537e03d507e2773a684b1e8/numpy-2.1.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c193d0b0238638e6fc5f10f1b074a6993cb13b0b431f64079a509d63d3aa8b7", size = 16141552 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/0c04903b48dfea6be1d7b47ba70f98709fb7198fd970784a1400c391d522/numpy-2.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a7d80b2e904faa63068ead63107189164ca443b42dd1930299e0d1cb041cec2e", size = 12789924 }, +] + [[package]] name = "overrides" version = "7.7.0" @@ -1705,6 +1872,54 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746 }, ] +[[package]] +name = "pandas" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/70/c853aec59839bceed032d52010ff5f1b8d87dc3114b762e4ba2727661a3b/pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5", size = 12580827 }, + { url = "https://files.pythonhosted.org/packages/99/f2/c4527768739ffa4469b2b4fff05aa3768a478aed89a2f271a79a40eee984/pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348", size = 11303897 }, + { url = "https://files.pythonhosted.org/packages/ed/12/86c1747ea27989d7a4064f806ce2bae2c6d575b950be087837bdfcabacc9/pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed", size = 66480908 }, + { url = "https://files.pythonhosted.org/packages/44/50/7db2cd5e6373ae796f0ddad3675268c8d59fb6076e66f0c339d61cea886b/pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57", size = 13064210 }, + { url = "https://files.pythonhosted.org/packages/61/61/a89015a6d5536cb0d6c3ba02cebed51a95538cf83472975275e28ebf7d0c/pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42", size = 16754292 }, + { url = "https://files.pythonhosted.org/packages/ce/0d/4cc7b69ce37fac07645a94e1d4b0880b15999494372c1523508511b09e40/pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f", size = 14416379 }, + { url = "https://files.pythonhosted.org/packages/31/9e/6ebb433de864a6cd45716af52a4d7a8c3c9aaf3a98368e61db9e69e69a9c/pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645", size = 11598471 }, + { url = "https://files.pythonhosted.org/packages/a8/44/d9502bf0ed197ba9bf1103c9867d5904ddcaf869e52329787fc54ed70cc8/pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039", size = 12602222 }, + { url = "https://files.pythonhosted.org/packages/52/11/9eac327a38834f162b8250aab32a6781339c69afe7574368fffe46387edf/pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd", size = 11321274 }, + { url = "https://files.pythonhosted.org/packages/45/fb/c4beeb084718598ba19aa9f5abbc8aed8b42f90930da861fcb1acdb54c3a/pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698", size = 15579836 }, + { url = "https://files.pythonhosted.org/packages/cd/5f/4dba1d39bb9c38d574a9a22548c540177f78ea47b32f99c0ff2ec499fac5/pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc", size = 13058505 }, + { url = "https://files.pythonhosted.org/packages/b9/57/708135b90391995361636634df1f1130d03ba456e95bcf576fada459115a/pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3", size = 16744420 }, + { url = "https://files.pythonhosted.org/packages/86/4a/03ed6b7ee323cf30404265c284cee9c65c56a212e0a08d9ee06984ba2240/pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32", size = 14440457 }, + { url = "https://files.pythonhosted.org/packages/ed/8c/87ddf1fcb55d11f9f847e3c69bb1c6f8e46e2f40ab1a2d2abadb2401b007/pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5", size = 11617166 }, + { url = "https://files.pythonhosted.org/packages/17/a3/fb2734118db0af37ea7433f57f722c0a56687e14b14690edff0cdb4b7e58/pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", size = 12529893 }, + { url = "https://files.pythonhosted.org/packages/e1/0c/ad295fd74bfac85358fd579e271cded3ac969de81f62dd0142c426b9da91/pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", size = 11363475 }, + { url = "https://files.pythonhosted.org/packages/c6/2a/4bba3f03f7d07207481fed47f5b35f556c7441acddc368ec43d6643c5777/pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", size = 15188645 }, + { url = "https://files.pythonhosted.org/packages/38/f8/d8fddee9ed0d0c0f4a2132c1dfcf0e3e53265055da8df952a53e7eaf178c/pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319", size = 12739445 }, + { url = "https://files.pythonhosted.org/packages/20/e8/45a05d9c39d2cea61ab175dbe6a2de1d05b679e8de2011da4ee190d7e748/pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", size = 16359235 }, + { url = "https://files.pythonhosted.org/packages/1d/99/617d07a6a5e429ff90c90da64d428516605a1ec7d7bea494235e1c3882de/pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", size = 14056756 }, + { url = "https://files.pythonhosted.org/packages/29/d4/1244ab8edf173a10fd601f7e13b9566c1b525c4f365d6bee918e68381889/pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", size = 11504248 }, + { url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643 }, + { url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573 }, + { url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085 }, + { url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809 }, + { url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316 }, + { url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055 }, + { url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175 }, + { url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650 }, + { url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177 }, + { url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526 }, + { url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013 }, + { url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620 }, + { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 }, +] + [[package]] name = "pandocfilters" version = "1.5.1" @@ -1723,6 +1938,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, ] +[[package]] +name = "partd" +version = "1.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "locket" }, + { name = "toolz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/3a/3f06f34820a31257ddcabdfafc2672c5816be79c7e353b02c1f318daa7d4/partd-1.4.2.tar.gz", hash = "sha256:d022c33afbdc8405c226621b015e8067888173d85f7f5ecebb3cafed9a20f02c", size = 21029 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl", hash = "sha256:978e4ac767ec4ba5b86c6eaa52e5a2a3bc748a2ca839e8cc798f1cc6ce6efb0f", size = 18905 }, +] + [[package]] name = "pathspec" version = "0.12.1" @@ -1850,87 +2078,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, ] -[[package]] -name = "pydantic" -version = "2.9.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928 }, -] - -[[package]] -name = "pydantic-core" -version = "2.23.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/8b/d3ae387f66277bd8104096d6ec0a145f4baa2966ebb2cad746c0920c9526/pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b", size = 1867835 }, - { url = "https://files.pythonhosted.org/packages/46/76/f68272e4c3a7df8777798282c5e47d508274917f29992d84e1898f8908c7/pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166", size = 1776689 }, - { url = "https://files.pythonhosted.org/packages/cc/69/5f945b4416f42ea3f3bc9d2aaec66c76084a6ff4ff27555bf9415ab43189/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb", size = 1800748 }, - { url = "https://files.pythonhosted.org/packages/50/ab/891a7b0054bcc297fb02d44d05c50e68154e31788f2d9d41d0b72c89fdf7/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916", size = 1806469 }, - { url = "https://files.pythonhosted.org/packages/31/7c/6e3fa122075d78f277a8431c4c608f061881b76c2b7faca01d317ee39b5d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07", size = 2002246 }, - { url = "https://files.pythonhosted.org/packages/ad/6f/22d5692b7ab63fc4acbc74de6ff61d185804a83160adba5e6cc6068e1128/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232", size = 2659404 }, - { url = "https://files.pythonhosted.org/packages/11/ac/1e647dc1121c028b691028fa61a4e7477e6aeb5132628fde41dd34c1671f/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2", size = 2053940 }, - { url = "https://files.pythonhosted.org/packages/91/75/984740c17f12c3ce18b5a2fcc4bdceb785cce7df1511a4ce89bca17c7e2d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f", size = 1921437 }, - { url = "https://files.pythonhosted.org/packages/a0/74/13c5f606b64d93f0721e7768cd3e8b2102164866c207b8cd6f90bb15d24f/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3", size = 1966129 }, - { url = "https://files.pythonhosted.org/packages/18/03/9c4aa5919457c7b57a016c1ab513b1a926ed9b2bb7915bf8e506bf65c34b/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071", size = 2110908 }, - { url = "https://files.pythonhosted.org/packages/92/2c/053d33f029c5dc65e5cf44ff03ceeefb7cce908f8f3cca9265e7f9b540c8/pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119", size = 1735278 }, - { url = "https://files.pythonhosted.org/packages/de/81/7dfe464eca78d76d31dd661b04b5f2036ec72ea8848dd87ab7375e185c23/pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f", size = 1917453 }, - { url = "https://files.pythonhosted.org/packages/5d/30/890a583cd3f2be27ecf32b479d5d615710bb926d92da03e3f7838ff3e58b/pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8", size = 1865160 }, - { url = "https://files.pythonhosted.org/packages/1d/9a/b634442e1253bc6889c87afe8bb59447f106ee042140bd57680b3b113ec7/pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d", size = 1776777 }, - { url = "https://files.pythonhosted.org/packages/75/9a/7816295124a6b08c24c96f9ce73085032d8bcbaf7e5a781cd41aa910c891/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e", size = 1799244 }, - { url = "https://files.pythonhosted.org/packages/a9/8f/89c1405176903e567c5f99ec53387449e62f1121894aa9fc2c4fdc51a59b/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607", size = 1805307 }, - { url = "https://files.pythonhosted.org/packages/d5/a5/1a194447d0da1ef492e3470680c66048fef56fc1f1a25cafbea4bc1d1c48/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd", size = 2000663 }, - { url = "https://files.pythonhosted.org/packages/13/a5/1df8541651de4455e7d587cf556201b4f7997191e110bca3b589218745a5/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea", size = 2655941 }, - { url = "https://files.pythonhosted.org/packages/44/31/a3899b5ce02c4316865e390107f145089876dff7e1dfc770a231d836aed8/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e", size = 2052105 }, - { url = "https://files.pythonhosted.org/packages/1b/aa/98e190f8745d5ec831f6d5449344c48c0627ac5fed4e5340a44b74878f8e/pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b", size = 1919967 }, - { url = "https://files.pythonhosted.org/packages/ae/35/b6e00b6abb2acfee3e8f85558c02a0822e9a8b2f2d812ea8b9079b118ba0/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0", size = 1964291 }, - { url = "https://files.pythonhosted.org/packages/13/46/7bee6d32b69191cd649bbbd2361af79c472d72cb29bb2024f0b6e350ba06/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64", size = 2109666 }, - { url = "https://files.pythonhosted.org/packages/39/ef/7b34f1b122a81b68ed0a7d0e564da9ccdc9a2924c8d6c6b5b11fa3a56970/pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f", size = 1732940 }, - { url = "https://files.pythonhosted.org/packages/2f/76/37b7e76c645843ff46c1d73e046207311ef298d3f7b2f7d8f6ac60113071/pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3", size = 1916804 }, - { url = "https://files.pythonhosted.org/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459 }, - { url = "https://files.pythonhosted.org/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007 }, - { url = "https://files.pythonhosted.org/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245 }, - { url = "https://files.pythonhosted.org/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260 }, - { url = "https://files.pythonhosted.org/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872 }, - { url = "https://files.pythonhosted.org/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617 }, - { url = "https://files.pythonhosted.org/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831 }, - { url = "https://files.pythonhosted.org/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453 }, - { url = "https://files.pythonhosted.org/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793 }, - { url = "https://files.pythonhosted.org/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872 }, - { url = "https://files.pythonhosted.org/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535 }, - { url = "https://files.pythonhosted.org/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992 }, - { url = "https://files.pythonhosted.org/packages/ad/ef/16ee2df472bf0e419b6bc68c05bf0145c49247a1095e85cee1463c6a44a1/pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc", size = 1856143 }, - { url = "https://files.pythonhosted.org/packages/da/fa/bc3dbb83605669a34a93308e297ab22be82dfb9dcf88c6cf4b4f264e0a42/pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd", size = 1770063 }, - { url = "https://files.pythonhosted.org/packages/4e/48/e813f3bbd257a712303ebdf55c8dc46f9589ec74b384c9f652597df3288d/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05", size = 1790013 }, - { url = "https://files.pythonhosted.org/packages/b4/e0/56eda3a37929a1d297fcab1966db8c339023bcca0b64c5a84896db3fcc5c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d", size = 1801077 }, - { url = "https://files.pythonhosted.org/packages/04/be/5e49376769bfbf82486da6c5c1683b891809365c20d7c7e52792ce4c71f3/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510", size = 1996782 }, - { url = "https://files.pythonhosted.org/packages/bc/24/e3ee6c04f1d58cc15f37bcc62f32c7478ff55142b7b3e6d42ea374ea427c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6", size = 2661375 }, - { url = "https://files.pythonhosted.org/packages/c1/f8/11a9006de4e89d016b8de74ebb1db727dc100608bb1e6bbe9d56a3cbbcce/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b", size = 2071635 }, - { url = "https://files.pythonhosted.org/packages/7c/45/bdce5779b59f468bdf262a5bc9eecbae87f271c51aef628d8c073b4b4b4c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327", size = 1916994 }, - { url = "https://files.pythonhosted.org/packages/d8/fa/c648308fe711ee1f88192cad6026ab4f925396d1293e8356de7e55be89b5/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6", size = 1968877 }, - { url = "https://files.pythonhosted.org/packages/16/16/b805c74b35607d24d37103007f899abc4880923b04929547ae68d478b7f4/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f", size = 2116814 }, - { url = "https://files.pythonhosted.org/packages/d1/58/5305e723d9fcdf1c5a655e6a4cc2a07128bf644ff4b1d98daf7a9dbf57da/pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769", size = 1738360 }, - { url = "https://files.pythonhosted.org/packages/a5/ae/e14b0ff8b3f48e02394d8acd911376b7b66e164535687ef7dc24ea03072f/pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5", size = 1919411 }, - { url = "https://files.pythonhosted.org/packages/13/a9/5d582eb3204464284611f636b55c0a7410d748ff338756323cb1ce721b96/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5", size = 1857135 }, - { url = "https://files.pythonhosted.org/packages/2c/57/faf36290933fe16717f97829eabfb1868182ac495f99cf0eda9f59687c9d/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec", size = 1740583 }, - { url = "https://files.pythonhosted.org/packages/91/7c/d99e3513dc191c4fec363aef1bf4c8af9125d8fa53af7cb97e8babef4e40/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480", size = 1793637 }, - { url = "https://files.pythonhosted.org/packages/29/18/812222b6d18c2d13eebbb0f7cdc170a408d9ced65794fdb86147c77e1982/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068", size = 1941963 }, - { url = "https://files.pythonhosted.org/packages/0f/36/c1f3642ac3f05e6bb4aec3ffc399fa3f84895d259cf5f0ce3054b7735c29/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801", size = 1915332 }, - { url = "https://files.pythonhosted.org/packages/f7/ca/9c0854829311fb446020ebb540ee22509731abad886d2859c855dd29b904/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728", size = 1957926 }, - { url = "https://files.pythonhosted.org/packages/c0/1c/7836b67c42d0cd4441fcd9fafbf6a027ad4b79b6559f80cf11f89fd83648/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433", size = 2100342 }, - { url = "https://files.pythonhosted.org/packages/a9/f9/b6bcaf874f410564a78908739c80861a171788ef4d4f76f5009656672dfe/pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753", size = 1920344 }, -] - [[package]] name = "pygments" version = "2.18.0" @@ -2025,6 +2172,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/35/a6/145655273568ee78a581e734cf35beb9e33a370b29c5d3c8fee3744de29f/python_json_logger-2.0.7-py3-none-any.whl", hash = "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd", size = 8067 }, ] +[[package]] +name = "pytz" +version = "2024.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/31/3c70bf7603cc2dca0f19bdc53b4537a797747a58875b552c8c413d963a3f/pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", size = 319692 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 }, +] + [[package]] name = "pywin32" version = "307" @@ -2189,22 +2345,30 @@ name = "ref-core" version = "0.1.0" source = { editable = "packages/ref-core" } dependencies = [ - { name = "pydantic" }, + { name = "attrs" }, ] [package.metadata] -requires-dist = [{ name = "pydantic", specifier = ">=2.0" }] +requires-dist = [{ name = "attrs" }] [[package]] name = "ref-metrics-example" version = "0.1.0" source = { editable = "packages/ref-metrics-example" } dependencies = [ + { name = "dask" }, + { name = "netcdf4" }, { name = "ref-core" }, + { name = "xarray" }, ] [package.metadata] -requires-dist = [{ name = "ref-core", editable = "packages/ref-core" }] +requires-dist = [ + { name = "dask", specifier = ">=2024.10.0" }, + { name = "netcdf4" }, + { name = "ref-core", editable = "packages/ref-core" }, + { name = "xarray", specifier = ">=2022" }, +] [[package]] name = "referencing" @@ -2693,6 +2857,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955 }, ] +[[package]] +name = "toolz" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/0b/d80dfa675bf592f636d1ea0b835eab4ec8df6e9415d8cfd766df54456123/toolz-1.0.0.tar.gz", hash = "sha256:2c86e3d9a04798ac556793bced838816296a2f085017664e4995cb40a1047a02", size = 66790 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/98/eb27cc78ad3af8e302c9d8ff4977f5026676e130d28dd7578132a457170c/toolz-1.0.0-py3-none-any.whl", hash = "sha256:292c8f1c4e7516bf9086f8850935c799a874039c8bcf959d47b600e4c44a6236", size = 56383 }, +] + [[package]] name = "tornado" version = "6.4.1" @@ -2752,6 +2925,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, ] +[[package]] +name = "tzdata" +version = "2024.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/34/943888654477a574a86a98e9896bae89c7aa15078ec29f490fef2f1e5384/tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", size = 193282 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586 }, +] + [[package]] name = "uri-template" version = "1.3.0" @@ -2900,6 +3082,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ff/21/abdedb4cdf6ff41ebf01a74087740a709e2edb146490e4d9beea054b0b7a/wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1", size = 23362 }, ] +[[package]] +name = "xarray" +version = "2024.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/f2/a3e3ec1ffd29b0b5be800d2606c229f04f303ee9e61a1377dc5c1996cf8a/xarray-2024.10.0.tar.gz", hash = "sha256:e369e2bac430e418c2448e5b96f07da4635f98c1319aa23cfeb3fbcb9a01d2e0", size = 3788358 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/b7/9830def68e5575a24ca6d6f46b285d35ed27860beaa4f72848cd82870253/xarray-2024.10.0-py3-none-any.whl", hash = "sha256:ae1d38cb44a0324dfb61e492394158ae22389bf7de9f3c174309c17376df63a0", size = 1212984 }, +] + [[package]] name = "zipp" version = "3.20.2" From aa819b45523b5cb445401ef81b8cce3eeef1bddd Mon Sep 17 00:00:00 2001 From: Jared Lewis Date: Thu, 31 Oct 2024 21:10:24 +1100 Subject: [PATCH 12/14] chore: Fix mypy --- packages/ref-core/src/ref_core/metrics.py | 4 ++-- .../src/ref_metrics_example/example.py | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/ref-core/src/ref_core/metrics.py b/packages/ref-core/src/ref_core/metrics.py index 0c6c51d..2d2bb02 100644 --- a/packages/ref-core/src/ref_core/metrics.py +++ b/packages/ref-core/src/ref_core/metrics.py @@ -1,6 +1,6 @@ import json import pathlib -from typing import Protocol, runtime_checkable +from typing import Any, Protocol, runtime_checkable from attrs import frozen @@ -44,7 +44,7 @@ class MetricResult: # Log info is in the output bundle file already, but is definitely useful @staticmethod - def build(configuration: Configuration, cmec_output_bundle: dict) -> "MetricResult": + def build(configuration: Configuration, cmec_output_bundle: dict[str, Any]) -> "MetricResult": """ Build a MetricResult from a CMEC output bundle. diff --git a/packages/ref-metrics-example/src/ref_metrics_example/example.py b/packages/ref-metrics-example/src/ref_metrics_example/example.py index 904e699..b6c9701 100644 --- a/packages/ref-metrics-example/src/ref_metrics_example/example.py +++ b/packages/ref-metrics-example/src/ref_metrics_example/example.py @@ -1,4 +1,5 @@ from pathlib import Path +from typing import Any import xarray as xr from ref_core.metrics import Configuration, MetricResult, TriggerInfo @@ -26,13 +27,13 @@ def calculate_annual_mean_timeseries(dataset: Path) -> xr.Dataset: """ input_files = dataset.glob("*.nc") - dataset = xr.open_mfdataset(list(input_files), combine="by_coords", chunks=None) + xr_ds = xr.open_mfdataset(list(input_files), combine="by_coords", chunks=None) - annual_mean = dataset.resample(time="YS").mean() + annual_mean = xr_ds.resample(time="YS").mean() return annual_mean.mean(dim=["lat", "lon"], keep_attrs=True) -def format_cmec_output_bundle(dataset: xr.Dataset) -> dict: +def format_cmec_output_bundle(dataset: xr.Dataset) -> dict[str, Any]: """ Create a simple CMEC output bundle for the dataset. @@ -45,6 +46,7 @@ def format_cmec_output_bundle(dataset: xr.Dataset) -> dict: ------- A CMEC output bundle ready to be written to disk """ + # TODO: Check how timeseries data are generally serialised cmec_output = { "DIMENSIONS": { "dimensions": { @@ -74,7 +76,7 @@ def format_cmec_output_bundle(dataset: xr.Dataset) -> dict: class ExampleMetric: """ - Example metric that does nothing but count the number of times it has been run. + Calculate the annual mean global mean timeseries for a dataset """ name = "example" From 8c11643259e1f4955c787c3827ffeb775026cb7b Mon Sep 17 00:00:00 2001 From: Jared Lewis Date: Thu, 31 Oct 2024 21:13:53 +1100 Subject: [PATCH 13/14] chore: Update tests --- packages/ref-core/tests/unit/test_metrics.py | 15 ++++++++++ .../tests/unit/test_metrics.py | 30 ++++++++----------- 2 files changed, 28 insertions(+), 17 deletions(-) create mode 100644 packages/ref-core/tests/unit/test_metrics.py diff --git a/packages/ref-core/tests/unit/test_metrics.py b/packages/ref-core/tests/unit/test_metrics.py new file mode 100644 index 0000000..d08b557 --- /dev/null +++ b/packages/ref-core/tests/unit/test_metrics.py @@ -0,0 +1,15 @@ +from ref_core.metrics import Configuration, MetricResult + + +class TestMetricResult: + def test_build(self, tmp_path): + config = Configuration(output_directory=tmp_path) + result = MetricResult.build(config, {"data": "value"}) + + assert result.successful + assert result.output_bundle.exists() + assert result.output_bundle.is_file() + with open(result.output_bundle) as f: + assert f.read() == '{"data": "value"}' + + assert result.output_bundle.is_relative_to(tmp_path) diff --git a/packages/ref-metrics-example/tests/unit/test_metrics.py b/packages/ref-metrics-example/tests/unit/test_metrics.py index 3b65543..eae3fe2 100644 --- a/packages/ref-metrics-example/tests/unit/test_metrics.py +++ b/packages/ref-metrics-example/tests/unit/test_metrics.py @@ -22,23 +22,8 @@ def test_dataset(esgf_data_dir) -> Path: ) -def test_annual_mean(esgf_data_dir): - input_files = list( - ( - esgf_data_dir - / "CMIP6" - / "ScenarioMIP" - / "CSIRO" - / "ACCESS-ESM1-5" - / "ssp245" - / "r1i1p1f1" - / "Amon" - / "tas" - / "gn" - / "v20191115" - ).glob("*.nc") - ) - annual_mean = calculate_annual_mean_timeseries(input_files) +def test_annual_mean(esgf_data_dir, test_dataset): + annual_mean = calculate_annual_mean_timeseries(test_dataset) assert annual_mean.time.size == 86 @@ -56,3 +41,14 @@ def test_example_metric(tmp_path, test_dataset): assert result.output_bundle.exists() assert result.output_bundle.is_file() assert result.output_bundle.name == "output.json" + + +def test_example_metric_no_trigger(tmp_path, test_dataset): + metric = ExampleMetric() + + configuration = Configuration( + output_directory=tmp_path, + ) + + result = metric.run(configuration, trigger=None) + assert result.successful is False From 53d65080690a432d70c3f9fcc8733a64f8f39355 Mon Sep 17 00:00:00 2001 From: Jared Lewis Date: Thu, 31 Oct 2024 21:37:16 +1100 Subject: [PATCH 14/14] chore: Use the correct test data --- .../ref-metrics-example/src/ref_metrics_example/example.py | 5 ++++- packages/ref-metrics-example/tests/unit/test_metrics.py | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/ref-metrics-example/src/ref_metrics_example/example.py b/packages/ref-metrics-example/src/ref_metrics_example/example.py index b6c9701..0e378c8 100644 --- a/packages/ref-metrics-example/src/ref_metrics_example/example.py +++ b/packages/ref-metrics-example/src/ref_metrics_example/example.py @@ -27,7 +27,7 @@ def calculate_annual_mean_timeseries(dataset: Path) -> xr.Dataset: """ input_files = dataset.glob("*.nc") - xr_ds = xr.open_mfdataset(list(input_files), combine="by_coords", chunks=None) + xr_ds = xr.open_mfdataset(list(input_files), combine="by_coords", chunks=None, use_cftime=True) annual_mean = xr_ds.resample(time="YS").mean() return annual_mean.mean(dim=["lat", "lon"], keep_attrs=True) @@ -105,6 +105,9 @@ def run(self, configuration: Configuration, trigger: TriggerInfo | None) -> Metr successful=False, ) + # This is where one would hook into how ever they want to run + # their benchmarking packages. + # cmec-driver, python calls, subprocess calls all would work annual_mean_global_mean_timeseries = calculate_annual_mean_timeseries(trigger.dataset) return MetricResult.build( diff --git a/packages/ref-metrics-example/tests/unit/test_metrics.py b/packages/ref-metrics-example/tests/unit/test_metrics.py index eae3fe2..d9f245f 100644 --- a/packages/ref-metrics-example/tests/unit/test_metrics.py +++ b/packages/ref-metrics-example/tests/unit/test_metrics.py @@ -13,19 +13,19 @@ def test_dataset(esgf_data_dir) -> Path: / "ScenarioMIP" / "CSIRO" / "ACCESS-ESM1-5" - / "ssp245" + / "ssp126" / "r1i1p1f1" / "Amon" / "tas" / "gn" - / "v20191115" + / "v20210318" ) def test_annual_mean(esgf_data_dir, test_dataset): annual_mean = calculate_annual_mean_timeseries(test_dataset) - assert annual_mean.time.size == 86 + assert annual_mean.time.size == 286 def test_example_metric(tmp_path, test_dataset):