From 11e4e98a51da3ede134cd0deca19c249b705dc3a Mon Sep 17 00:00:00 2001 From: wahadameh Date: Thu, 23 Jan 2025 11:51:49 +0100 Subject: [PATCH 01/19] feat(api): adding renewable and thermal matrix upload method (unit testing too) --- src/antares/craft/exceptions/exceptions.py | 8 ++++++ src/antares/craft/model/renewable.py | 3 +++ src/antares/craft/model/thermal.py | 3 +++ .../service/api_services/renewable_api.py | 18 +++++++++++-- .../craft/service/api_services/thermal_api.py | 22 ++++++++++++++-- .../api_services/test_renewable_api.py | 26 ++++++++++++++++++- .../services/api_services/test_thermal_api.py | 25 +++++++++++++++++- 7 files changed, 99 insertions(+), 6 deletions(-) diff --git a/src/antares/craft/exceptions/exceptions.py b/src/antares/craft/exceptions/exceptions.py index 98e4330b..308ff36b 100644 --- a/src/antares/craft/exceptions/exceptions.py +++ b/src/antares/craft/exceptions/exceptions.py @@ -263,12 +263,20 @@ def __init__(self, area_name: str, cluster_name: str, matrix_name: str, message: ) super().__init__(self.message) +class ThermalMatrixUploadError(Exception): + def __init__(self, area_name: str, cluster_name: str, message: str) -> None: + self.message = f"Could not upload matrix for cluster {cluster_name} inside area {area_name}: " + message + super().__init__(self.message) class RenewableMatrixDownloadError(Exception): def __init__(self, area_name: str, renewable_name: str, message: str) -> None: self.message = f"Could not download matrix for cluster {renewable_name} inside area {area_name}: " + message super().__init__(self.message) +class RenewableMatrixUploadError(Exception): + def __init__(self, area_name: str, renewable_name: str, message: str) -> None: + self.message = f"Could not upload matrix for cluster {renewable_name} inside area {area_name}: " + message + super().__init__(self.message) class MatrixUploadError(Exception): def __init__(self, area_id: str, matrix_type: str, message: str) -> None: diff --git a/src/antares/craft/model/renewable.py b/src/antares/craft/model/renewable.py index 97b1a52e..fe26a8ea 100644 --- a/src/antares/craft/model/renewable.py +++ b/src/antares/craft/model/renewable.py @@ -127,3 +127,6 @@ def update_properties(self, properties: RenewableClusterProperties) -> None: def get_timeseries(self) -> pd.DataFrame: return self._renewable_service.get_renewable_matrix(self.id, self.area_id) + + def upload_renewable_matrix(self, matrix: pd.DataFrame) -> None: + self._renewable_service.upload_renewable_matrix(self, matrix) diff --git a/src/antares/craft/model/thermal.py b/src/antares/craft/model/thermal.py index abf4c4b0..c0cda780 100644 --- a/src/antares/craft/model/thermal.py +++ b/src/antares/craft/model/thermal.py @@ -214,3 +214,6 @@ def get_co2_cost_matrix(self) -> pd.DataFrame: def get_fuel_cost_matrix(self) -> pd.DataFrame: return self._thermal_service.get_thermal_matrix(self, ThermalClusterMatrixName.SERIES_FUEL_COST) + + def upload_thermal_matrix(self, matrix: pd.DataFrame) -> None: + self._thermal_service.upload_thermal_matrix(self, matrix) diff --git a/src/antares/craft/service/api_services/renewable_api.py b/src/antares/craft/service/api_services/renewable_api.py index 64bf6007..6c8c0d30 100644 --- a/src/antares/craft/service/api_services/renewable_api.py +++ b/src/antares/craft/service/api_services/renewable_api.py @@ -17,9 +17,14 @@ from antares.craft.api_conf.api_conf import APIconf from antares.craft.api_conf.request_wrapper import RequestWrapper -from antares.craft.exceptions.exceptions import APIError, RenewableMatrixDownloadError, RenewablePropertiesUpdateError +from antares.craft.exceptions.exceptions import ( + APIError, + RenewableMatrixDownloadError, + RenewableMatrixUploadError, + RenewablePropertiesUpdateError, +) from antares.craft.model.renewable import RenewableCluster, RenewableClusterProperties -from antares.craft.service.api_services.utils import get_matrix +from antares.craft.service.api_services.utils import get_matrix, upload_series from antares.craft.service.base_services import BaseRenewableService @@ -51,6 +56,15 @@ def update_renewable_properties( return new_properties + def upload_renewable_matrix(self, renewable_cluster: RenewableCluster, matrix: pd.DataFrame) -> None: + + try: + path = PurePosixPath("input") / "renewables" / "series" / f"{renewable_cluster.area_id}" / f"{renewable_cluster.id}" / "series" + + upload_series(self._base_url, self.study_id, self._wrapper, matrix, path.as_posix()) + except APIError as e: + raise RenewableMatrixUploadError(renewable_cluster.area_id, renewable_cluster.id, e.message) from e + def get_renewable_matrix(self, cluster_id: str, area_id: str) -> pd.DataFrame: try: path = PurePosixPath("input") / "renewables" / "series" / f"{area_id}" / f"{cluster_id}" / "series" diff --git a/src/antares/craft/service/api_services/thermal_api.py b/src/antares/craft/service/api_services/thermal_api.py index 1e127249..564c12fa 100644 --- a/src/antares/craft/service/api_services/thermal_api.py +++ b/src/antares/craft/service/api_services/thermal_api.py @@ -17,9 +17,14 @@ from antares.craft.api_conf.api_conf import APIconf from antares.craft.api_conf.request_wrapper import RequestWrapper -from antares.craft.exceptions.exceptions import APIError, ThermalMatrixDownloadError, ThermalPropertiesUpdateError +from antares.craft.exceptions.exceptions import ( + APIError, + ThermalMatrixDownloadError, + ThermalMatrixUploadError, + ThermalPropertiesUpdateError, +) from antares.craft.model.thermal import ThermalCluster, ThermalClusterMatrixName, ThermalClusterProperties -from antares.craft.service.api_services.utils import get_matrix +from antares.craft.service.api_services.utils import get_matrix, upload_series from antares.craft.service.base_services import BaseThermalService @@ -51,6 +56,19 @@ def update_thermal_properties( return new_properties + def upload_thermal_matrix(self, thermal_cluster: ThermalCluster, matrix: pd.DataFrame): + path = PurePosixPath("input") / "thermal" / "series" / f"{thermal_cluster.area_id}" / f"{thermal_cluster.id}" / "series" + try: + body={ + "data": matrix.to_numpy().tolist(), + "index": matrix.index.tolist(), + "columns": matrix.columns.tolist(), + } + data = pd.json_normalize(body) + upload_series(self._base_url, self.study_id, self._wrapper, data, path.as_posix()) + except APIError as e: + raise ThermalMatrixUploadError(thermal_cluster.area_id, thermal_cluster.name, e.message) from e + def get_thermal_matrix(self, thermal_cluster: ThermalCluster, ts_name: ThermalClusterMatrixName) -> pd.DataFrame: try: keyword = "series" if "SERIES" in ts_name.name else "prepro" diff --git a/tests/antares/services/api_services/test_renewable_api.py b/tests/antares/services/api_services/test_renewable_api.py index 520b61a3..4352bd2e 100644 --- a/tests/antares/services/api_services/test_renewable_api.py +++ b/tests/antares/services/api_services/test_renewable_api.py @@ -14,9 +14,11 @@ import requests_mock import pandas as pd +from numpy.matrixlib.defmatrix import matrix from antares.craft.api_conf.api_conf import APIconf -from antares.craft.exceptions.exceptions import RenewableMatrixDownloadError, RenewablePropertiesUpdateError +from antares.craft.exceptions.exceptions import RenewableMatrixDownloadError, RenewablePropertiesUpdateError, \ + RenewableMatrixUploadError from antares.craft.model.area import Area from antares.craft.model.renewable import RenewableCluster, RenewableClusterProperties from antares.craft.service.api_services.area_api import AreaApiService @@ -90,6 +92,28 @@ def test_get_renewable_matrices_fails(self): ): self.renewable.get_timeseries() + def test_upload_renewable_matrices_success(self): + with requests_mock.Mocker() as mocker: + url = ( + f"https://antares.com/api/v1/studies/{self.study_id}/raw?path=input/renewables/series/" + f"{self.area.id}/{self.renewable.name}/series" + ) + mocker.post(url, status_code=200) + self.renewable.upload_renewable_matrix(self.matrix) + + def test_upload_renewable_matrices_fail(self): + with requests_mock.Mocker() as mocker: + url = ( + f"https://antares.com/api/v1/studies/{self.study_id}/raw?path=input/renewables/series/" + f"{self.area.id}/{self.renewable.name}/series" + ) + mocker.post(url, json={"description": self.antares_web_description_msg}, status_code=404) + with pytest.raises( + RenewableMatrixUploadError, + match=f"Could not upload matrix for cluster {self.renewable.name} inside area {self.area.name}: " + self.antares_web_description_msg + ): + self.renewable.upload_renewable_matrix(self.matrix) + def test_read_renewables(self): json_renewable = [ { diff --git a/tests/antares/services/api_services/test_thermal_api.py b/tests/antares/services/api_services/test_thermal_api.py index 4e0ebb85..4ef9ec2d 100644 --- a/tests/antares/services/api_services/test_thermal_api.py +++ b/tests/antares/services/api_services/test_thermal_api.py @@ -16,7 +16,8 @@ import pandas as pd from antares.craft.api_conf.api_conf import APIconf -from antares.craft.exceptions.exceptions import ThermalMatrixDownloadError, ThermalPropertiesUpdateError +from antares.craft.exceptions.exceptions import ThermalMatrixDownloadError, ThermalPropertiesUpdateError, \ + ThermalMatrixUploadError from antares.craft.model.area import Area from antares.craft.model.study import Study from antares.craft.model.thermal import ThermalCluster, ThermalClusterMatrixName, ThermalClusterProperties @@ -108,6 +109,28 @@ def test_get_thermal_matrices_fails(self, thermal_matrix_set): ): getattr(self.thermal, matrix_method)() + def test_upload_thermal_matrix_success(self): + with requests_mock.Mocker() as mocker: + url = ( + f"https://antares.com/api/v1/studies/{self.study_id}/raw?path=input/thermal/series/" + f"{self.area.id}/{self.thermal.name}/series" + ) + mocker.post(url, status_code=200) + self.thermal.upload_thermal_matrix(self.matrix) + + def test_upload_thermal_matrix_fail(self): + with requests_mock.Mocker() as mocker: + url = ( + f"https://antares.com/api/v1/studies/{self.study_id}/raw?path=input/thermal/series/" + f"{self.area.id}/{self.thermal.name}/series" + ) + mocker.post(url, json={"description": self.antares_web_description_msg}, status_code=404) + with pytest.raises( + ThermalMatrixUploadError, + match=f"Could not upload matrix for cluster {self.thermal.name} inside area {self.area.name}" + self.antares_web_description_msg + ): + self.thermal.upload_thermal_matrix(self.matrix) + def test_read_thermals(self): json_thermal = [ { From 23e7d51396fae723bff3bbae456c5a989cff27ed Mon Sep 17 00:00:00 2001 From: wahadameh Date: Thu, 23 Jan 2025 11:52:30 +0100 Subject: [PATCH 02/19] feat(api): adding renewable and thermal matrix upload method (unit testing too) --- src/antares/craft/exceptions/exceptions.py | 4 ++++ .../craft/service/api_services/renewable_api.py | 10 ++++++++-- .../craft/service/api_services/thermal_api.py | 13 ++++++++++--- .../services/api_services/test_renewable_api.py | 11 +++++++---- .../services/api_services/test_thermal_api.py | 12 ++++++++---- 5 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/antares/craft/exceptions/exceptions.py b/src/antares/craft/exceptions/exceptions.py index 308ff36b..48165d94 100644 --- a/src/antares/craft/exceptions/exceptions.py +++ b/src/antares/craft/exceptions/exceptions.py @@ -263,21 +263,25 @@ def __init__(self, area_name: str, cluster_name: str, matrix_name: str, message: ) super().__init__(self.message) + class ThermalMatrixUploadError(Exception): def __init__(self, area_name: str, cluster_name: str, message: str) -> None: self.message = f"Could not upload matrix for cluster {cluster_name} inside area {area_name}: " + message super().__init__(self.message) + class RenewableMatrixDownloadError(Exception): def __init__(self, area_name: str, renewable_name: str, message: str) -> None: self.message = f"Could not download matrix for cluster {renewable_name} inside area {area_name}: " + message super().__init__(self.message) + class RenewableMatrixUploadError(Exception): def __init__(self, area_name: str, renewable_name: str, message: str) -> None: self.message = f"Could not upload matrix for cluster {renewable_name} inside area {area_name}: " + message super().__init__(self.message) + class MatrixUploadError(Exception): def __init__(self, area_id: str, matrix_type: str, message: str) -> None: self.message = f"Error uploading {matrix_type} matrix for area {area_id}: {message}" diff --git a/src/antares/craft/service/api_services/renewable_api.py b/src/antares/craft/service/api_services/renewable_api.py index 6c8c0d30..9a7ed36c 100644 --- a/src/antares/craft/service/api_services/renewable_api.py +++ b/src/antares/craft/service/api_services/renewable_api.py @@ -57,9 +57,15 @@ def update_renewable_properties( return new_properties def upload_renewable_matrix(self, renewable_cluster: RenewableCluster, matrix: pd.DataFrame) -> None: - try: - path = PurePosixPath("input") / "renewables" / "series" / f"{renewable_cluster.area_id}" / f"{renewable_cluster.id}" / "series" + path = ( + PurePosixPath("input") + / "renewables" + / "series" + / f"{renewable_cluster.area_id}" + / f"{renewable_cluster.id}" + / "series" + ) upload_series(self._base_url, self.study_id, self._wrapper, matrix, path.as_posix()) except APIError as e: diff --git a/src/antares/craft/service/api_services/thermal_api.py b/src/antares/craft/service/api_services/thermal_api.py index 564c12fa..935e9fb1 100644 --- a/src/antares/craft/service/api_services/thermal_api.py +++ b/src/antares/craft/service/api_services/thermal_api.py @@ -56,10 +56,17 @@ def update_thermal_properties( return new_properties - def upload_thermal_matrix(self, thermal_cluster: ThermalCluster, matrix: pd.DataFrame): - path = PurePosixPath("input") / "thermal" / "series" / f"{thermal_cluster.area_id}" / f"{thermal_cluster.id}" / "series" + def upload_thermal_matrix(self, thermal_cluster: ThermalCluster, matrix: pd.DataFrame) -> None: + path = ( + PurePosixPath("input") + / "thermal" + / "series" + / f"{thermal_cluster.area_id}" + / f"{thermal_cluster.id}" + / "series" + ) try: - body={ + body = { "data": matrix.to_numpy().tolist(), "index": matrix.index.tolist(), "columns": matrix.columns.tolist(), diff --git a/tests/antares/services/api_services/test_renewable_api.py b/tests/antares/services/api_services/test_renewable_api.py index 4352bd2e..6d3659f4 100644 --- a/tests/antares/services/api_services/test_renewable_api.py +++ b/tests/antares/services/api_services/test_renewable_api.py @@ -14,11 +14,13 @@ import requests_mock import pandas as pd -from numpy.matrixlib.defmatrix import matrix from antares.craft.api_conf.api_conf import APIconf -from antares.craft.exceptions.exceptions import RenewableMatrixDownloadError, RenewablePropertiesUpdateError, \ - RenewableMatrixUploadError +from antares.craft.exceptions.exceptions import ( + RenewableMatrixDownloadError, + RenewableMatrixUploadError, + RenewablePropertiesUpdateError, +) from antares.craft.model.area import Area from antares.craft.model.renewable import RenewableCluster, RenewableClusterProperties from antares.craft.service.api_services.area_api import AreaApiService @@ -110,7 +112,8 @@ def test_upload_renewable_matrices_fail(self): mocker.post(url, json={"description": self.antares_web_description_msg}, status_code=404) with pytest.raises( RenewableMatrixUploadError, - match=f"Could not upload matrix for cluster {self.renewable.name} inside area {self.area.name}: " + self.antares_web_description_msg + match=f"Could not upload matrix for cluster {self.renewable.name} inside area {self.area.name}: " + + self.antares_web_description_msg, ): self.renewable.upload_renewable_matrix(self.matrix) diff --git a/tests/antares/services/api_services/test_thermal_api.py b/tests/antares/services/api_services/test_thermal_api.py index 4ef9ec2d..491118dc 100644 --- a/tests/antares/services/api_services/test_thermal_api.py +++ b/tests/antares/services/api_services/test_thermal_api.py @@ -16,8 +16,11 @@ import pandas as pd from antares.craft.api_conf.api_conf import APIconf -from antares.craft.exceptions.exceptions import ThermalMatrixDownloadError, ThermalPropertiesUpdateError, \ - ThermalMatrixUploadError +from antares.craft.exceptions.exceptions import ( + ThermalMatrixDownloadError, + ThermalMatrixUploadError, + ThermalPropertiesUpdateError, +) from antares.craft.model.area import Area from antares.craft.model.study import Study from antares.craft.model.thermal import ThermalCluster, ThermalClusterMatrixName, ThermalClusterProperties @@ -126,8 +129,9 @@ def test_upload_thermal_matrix_fail(self): ) mocker.post(url, json={"description": self.antares_web_description_msg}, status_code=404) with pytest.raises( - ThermalMatrixUploadError, - match=f"Could not upload matrix for cluster {self.thermal.name} inside area {self.area.name}" + self.antares_web_description_msg + ThermalMatrixUploadError, + match=f"Could not upload matrix for cluster {self.thermal.name} inside area {self.area.name}" + + self.antares_web_description_msg, ): self.thermal.upload_thermal_matrix(self.matrix) From a616eaaee44cf5a01cfc0c9a4d44959968e4a6c5 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Thu, 23 Jan 2025 12:03:10 +0100 Subject: [PATCH 03/19] feat(api): WIP integration tests --- tests/integration/test_web_client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/integration/test_web_client.py b/tests/integration/test_web_client.py index 0951bfa3..7970bfb5 100644 --- a/tests/integration/test_web_client.py +++ b/tests/integration/test_web_client.py @@ -464,6 +464,9 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): area_fr.delete_st_storage(battery_fr) assert battery_fr.id not in study.get_areas().get(area_be.id).get_st_storages() + #tests uploading thermal and renewable matrices + + # tests area deletion error with pytest.raises( AreaDeletionError, From 800a13a19b445e027179dde8d92cace3ef914d9a Mon Sep 17 00:00:00 2001 From: wahadameh Date: Thu, 23 Jan 2025 12:03:50 +0100 Subject: [PATCH 04/19] feat(api): WIP integration tests --- tests/antares/services/api_services/test_thermal_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/antares/services/api_services/test_thermal_api.py b/tests/antares/services/api_services/test_thermal_api.py index 491118dc..c589f295 100644 --- a/tests/antares/services/api_services/test_thermal_api.py +++ b/tests/antares/services/api_services/test_thermal_api.py @@ -130,7 +130,7 @@ def test_upload_thermal_matrix_fail(self): mocker.post(url, json={"description": self.antares_web_description_msg}, status_code=404) with pytest.raises( ThermalMatrixUploadError, - match=f"Could not upload matrix for cluster {self.thermal.name} inside area {self.area.name}" + match=f"Could not upload matrix for cluster {self.thermal.name} inside area {self.area.name}: " + self.antares_web_description_msg, ): self.thermal.upload_thermal_matrix(self.matrix) From 81cce67b87a71983006b0038d2960757adcf06a5 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Thu, 23 Jan 2025 12:05:11 +0100 Subject: [PATCH 05/19] feat(api): WIP integration tests --- tests/integration/test_web_client.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/integration/test_web_client.py b/tests/integration/test_web_client.py index 7970bfb5..98fb5661 100644 --- a/tests/integration/test_web_client.py +++ b/tests/integration/test_web_client.py @@ -464,8 +464,7 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): area_fr.delete_st_storage(battery_fr) assert battery_fr.id not in study.get_areas().get(area_be.id).get_st_storages() - #tests uploading thermal and renewable matrices - + # tests uploading thermal and renewable matrices # tests area deletion error with pytest.raises( From decd58a901f5fa6dffa6f64da7b1c2d86e5d40cd Mon Sep 17 00:00:00 2001 From: wahadameh Date: Thu, 23 Jan 2025 11:51:49 +0100 Subject: [PATCH 06/19] feat(api): adding renewable and thermal matrix upload method (unit testing too) --- src/antares/craft/exceptions/exceptions.py | 8 ++++++ src/antares/craft/model/renewable.py | 3 +++ src/antares/craft/model/thermal.py | 3 +++ .../service/api_services/renewable_api.py | 18 +++++++++++-- .../craft/service/api_services/thermal_api.py | 22 ++++++++++++++-- .../api_services/test_renewable_api.py | 26 ++++++++++++++++++- .../services/api_services/test_thermal_api.py | 25 +++++++++++++++++- 7 files changed, 99 insertions(+), 6 deletions(-) diff --git a/src/antares/craft/exceptions/exceptions.py b/src/antares/craft/exceptions/exceptions.py index 98e4330b..308ff36b 100644 --- a/src/antares/craft/exceptions/exceptions.py +++ b/src/antares/craft/exceptions/exceptions.py @@ -263,12 +263,20 @@ def __init__(self, area_name: str, cluster_name: str, matrix_name: str, message: ) super().__init__(self.message) +class ThermalMatrixUploadError(Exception): + def __init__(self, area_name: str, cluster_name: str, message: str) -> None: + self.message = f"Could not upload matrix for cluster {cluster_name} inside area {area_name}: " + message + super().__init__(self.message) class RenewableMatrixDownloadError(Exception): def __init__(self, area_name: str, renewable_name: str, message: str) -> None: self.message = f"Could not download matrix for cluster {renewable_name} inside area {area_name}: " + message super().__init__(self.message) +class RenewableMatrixUploadError(Exception): + def __init__(self, area_name: str, renewable_name: str, message: str) -> None: + self.message = f"Could not upload matrix for cluster {renewable_name} inside area {area_name}: " + message + super().__init__(self.message) class MatrixUploadError(Exception): def __init__(self, area_id: str, matrix_type: str, message: str) -> None: diff --git a/src/antares/craft/model/renewable.py b/src/antares/craft/model/renewable.py index 97b1a52e..fe26a8ea 100644 --- a/src/antares/craft/model/renewable.py +++ b/src/antares/craft/model/renewable.py @@ -127,3 +127,6 @@ def update_properties(self, properties: RenewableClusterProperties) -> None: def get_timeseries(self) -> pd.DataFrame: return self._renewable_service.get_renewable_matrix(self.id, self.area_id) + + def upload_renewable_matrix(self, matrix: pd.DataFrame) -> None: + self._renewable_service.upload_renewable_matrix(self, matrix) diff --git a/src/antares/craft/model/thermal.py b/src/antares/craft/model/thermal.py index abf4c4b0..c0cda780 100644 --- a/src/antares/craft/model/thermal.py +++ b/src/antares/craft/model/thermal.py @@ -214,3 +214,6 @@ def get_co2_cost_matrix(self) -> pd.DataFrame: def get_fuel_cost_matrix(self) -> pd.DataFrame: return self._thermal_service.get_thermal_matrix(self, ThermalClusterMatrixName.SERIES_FUEL_COST) + + def upload_thermal_matrix(self, matrix: pd.DataFrame) -> None: + self._thermal_service.upload_thermal_matrix(self, matrix) diff --git a/src/antares/craft/service/api_services/renewable_api.py b/src/antares/craft/service/api_services/renewable_api.py index 64bf6007..6c8c0d30 100644 --- a/src/antares/craft/service/api_services/renewable_api.py +++ b/src/antares/craft/service/api_services/renewable_api.py @@ -17,9 +17,14 @@ from antares.craft.api_conf.api_conf import APIconf from antares.craft.api_conf.request_wrapper import RequestWrapper -from antares.craft.exceptions.exceptions import APIError, RenewableMatrixDownloadError, RenewablePropertiesUpdateError +from antares.craft.exceptions.exceptions import ( + APIError, + RenewableMatrixDownloadError, + RenewableMatrixUploadError, + RenewablePropertiesUpdateError, +) from antares.craft.model.renewable import RenewableCluster, RenewableClusterProperties -from antares.craft.service.api_services.utils import get_matrix +from antares.craft.service.api_services.utils import get_matrix, upload_series from antares.craft.service.base_services import BaseRenewableService @@ -51,6 +56,15 @@ def update_renewable_properties( return new_properties + def upload_renewable_matrix(self, renewable_cluster: RenewableCluster, matrix: pd.DataFrame) -> None: + + try: + path = PurePosixPath("input") / "renewables" / "series" / f"{renewable_cluster.area_id}" / f"{renewable_cluster.id}" / "series" + + upload_series(self._base_url, self.study_id, self._wrapper, matrix, path.as_posix()) + except APIError as e: + raise RenewableMatrixUploadError(renewable_cluster.area_id, renewable_cluster.id, e.message) from e + def get_renewable_matrix(self, cluster_id: str, area_id: str) -> pd.DataFrame: try: path = PurePosixPath("input") / "renewables" / "series" / f"{area_id}" / f"{cluster_id}" / "series" diff --git a/src/antares/craft/service/api_services/thermal_api.py b/src/antares/craft/service/api_services/thermal_api.py index 1e127249..564c12fa 100644 --- a/src/antares/craft/service/api_services/thermal_api.py +++ b/src/antares/craft/service/api_services/thermal_api.py @@ -17,9 +17,14 @@ from antares.craft.api_conf.api_conf import APIconf from antares.craft.api_conf.request_wrapper import RequestWrapper -from antares.craft.exceptions.exceptions import APIError, ThermalMatrixDownloadError, ThermalPropertiesUpdateError +from antares.craft.exceptions.exceptions import ( + APIError, + ThermalMatrixDownloadError, + ThermalMatrixUploadError, + ThermalPropertiesUpdateError, +) from antares.craft.model.thermal import ThermalCluster, ThermalClusterMatrixName, ThermalClusterProperties -from antares.craft.service.api_services.utils import get_matrix +from antares.craft.service.api_services.utils import get_matrix, upload_series from antares.craft.service.base_services import BaseThermalService @@ -51,6 +56,19 @@ def update_thermal_properties( return new_properties + def upload_thermal_matrix(self, thermal_cluster: ThermalCluster, matrix: pd.DataFrame): + path = PurePosixPath("input") / "thermal" / "series" / f"{thermal_cluster.area_id}" / f"{thermal_cluster.id}" / "series" + try: + body={ + "data": matrix.to_numpy().tolist(), + "index": matrix.index.tolist(), + "columns": matrix.columns.tolist(), + } + data = pd.json_normalize(body) + upload_series(self._base_url, self.study_id, self._wrapper, data, path.as_posix()) + except APIError as e: + raise ThermalMatrixUploadError(thermal_cluster.area_id, thermal_cluster.name, e.message) from e + def get_thermal_matrix(self, thermal_cluster: ThermalCluster, ts_name: ThermalClusterMatrixName) -> pd.DataFrame: try: keyword = "series" if "SERIES" in ts_name.name else "prepro" diff --git a/tests/antares/services/api_services/test_renewable_api.py b/tests/antares/services/api_services/test_renewable_api.py index 520b61a3..4352bd2e 100644 --- a/tests/antares/services/api_services/test_renewable_api.py +++ b/tests/antares/services/api_services/test_renewable_api.py @@ -14,9 +14,11 @@ import requests_mock import pandas as pd +from numpy.matrixlib.defmatrix import matrix from antares.craft.api_conf.api_conf import APIconf -from antares.craft.exceptions.exceptions import RenewableMatrixDownloadError, RenewablePropertiesUpdateError +from antares.craft.exceptions.exceptions import RenewableMatrixDownloadError, RenewablePropertiesUpdateError, \ + RenewableMatrixUploadError from antares.craft.model.area import Area from antares.craft.model.renewable import RenewableCluster, RenewableClusterProperties from antares.craft.service.api_services.area_api import AreaApiService @@ -90,6 +92,28 @@ def test_get_renewable_matrices_fails(self): ): self.renewable.get_timeseries() + def test_upload_renewable_matrices_success(self): + with requests_mock.Mocker() as mocker: + url = ( + f"https://antares.com/api/v1/studies/{self.study_id}/raw?path=input/renewables/series/" + f"{self.area.id}/{self.renewable.name}/series" + ) + mocker.post(url, status_code=200) + self.renewable.upload_renewable_matrix(self.matrix) + + def test_upload_renewable_matrices_fail(self): + with requests_mock.Mocker() as mocker: + url = ( + f"https://antares.com/api/v1/studies/{self.study_id}/raw?path=input/renewables/series/" + f"{self.area.id}/{self.renewable.name}/series" + ) + mocker.post(url, json={"description": self.antares_web_description_msg}, status_code=404) + with pytest.raises( + RenewableMatrixUploadError, + match=f"Could not upload matrix for cluster {self.renewable.name} inside area {self.area.name}: " + self.antares_web_description_msg + ): + self.renewable.upload_renewable_matrix(self.matrix) + def test_read_renewables(self): json_renewable = [ { diff --git a/tests/antares/services/api_services/test_thermal_api.py b/tests/antares/services/api_services/test_thermal_api.py index 4e0ebb85..4ef9ec2d 100644 --- a/tests/antares/services/api_services/test_thermal_api.py +++ b/tests/antares/services/api_services/test_thermal_api.py @@ -16,7 +16,8 @@ import pandas as pd from antares.craft.api_conf.api_conf import APIconf -from antares.craft.exceptions.exceptions import ThermalMatrixDownloadError, ThermalPropertiesUpdateError +from antares.craft.exceptions.exceptions import ThermalMatrixDownloadError, ThermalPropertiesUpdateError, \ + ThermalMatrixUploadError from antares.craft.model.area import Area from antares.craft.model.study import Study from antares.craft.model.thermal import ThermalCluster, ThermalClusterMatrixName, ThermalClusterProperties @@ -108,6 +109,28 @@ def test_get_thermal_matrices_fails(self, thermal_matrix_set): ): getattr(self.thermal, matrix_method)() + def test_upload_thermal_matrix_success(self): + with requests_mock.Mocker() as mocker: + url = ( + f"https://antares.com/api/v1/studies/{self.study_id}/raw?path=input/thermal/series/" + f"{self.area.id}/{self.thermal.name}/series" + ) + mocker.post(url, status_code=200) + self.thermal.upload_thermal_matrix(self.matrix) + + def test_upload_thermal_matrix_fail(self): + with requests_mock.Mocker() as mocker: + url = ( + f"https://antares.com/api/v1/studies/{self.study_id}/raw?path=input/thermal/series/" + f"{self.area.id}/{self.thermal.name}/series" + ) + mocker.post(url, json={"description": self.antares_web_description_msg}, status_code=404) + with pytest.raises( + ThermalMatrixUploadError, + match=f"Could not upload matrix for cluster {self.thermal.name} inside area {self.area.name}" + self.antares_web_description_msg + ): + self.thermal.upload_thermal_matrix(self.matrix) + def test_read_thermals(self): json_thermal = [ { From b23ba51eb88d1c51688c30d41eb98339fdd6ce49 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Thu, 23 Jan 2025 11:52:30 +0100 Subject: [PATCH 07/19] feat(api): adding renewable and thermal matrix upload method (unit testing too) --- src/antares/craft/exceptions/exceptions.py | 4 ++++ .../craft/service/api_services/renewable_api.py | 10 ++++++++-- .../craft/service/api_services/thermal_api.py | 13 ++++++++++--- .../services/api_services/test_renewable_api.py | 11 +++++++---- .../services/api_services/test_thermal_api.py | 12 ++++++++---- 5 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/antares/craft/exceptions/exceptions.py b/src/antares/craft/exceptions/exceptions.py index 308ff36b..48165d94 100644 --- a/src/antares/craft/exceptions/exceptions.py +++ b/src/antares/craft/exceptions/exceptions.py @@ -263,21 +263,25 @@ def __init__(self, area_name: str, cluster_name: str, matrix_name: str, message: ) super().__init__(self.message) + class ThermalMatrixUploadError(Exception): def __init__(self, area_name: str, cluster_name: str, message: str) -> None: self.message = f"Could not upload matrix for cluster {cluster_name} inside area {area_name}: " + message super().__init__(self.message) + class RenewableMatrixDownloadError(Exception): def __init__(self, area_name: str, renewable_name: str, message: str) -> None: self.message = f"Could not download matrix for cluster {renewable_name} inside area {area_name}: " + message super().__init__(self.message) + class RenewableMatrixUploadError(Exception): def __init__(self, area_name: str, renewable_name: str, message: str) -> None: self.message = f"Could not upload matrix for cluster {renewable_name} inside area {area_name}: " + message super().__init__(self.message) + class MatrixUploadError(Exception): def __init__(self, area_id: str, matrix_type: str, message: str) -> None: self.message = f"Error uploading {matrix_type} matrix for area {area_id}: {message}" diff --git a/src/antares/craft/service/api_services/renewable_api.py b/src/antares/craft/service/api_services/renewable_api.py index 6c8c0d30..9a7ed36c 100644 --- a/src/antares/craft/service/api_services/renewable_api.py +++ b/src/antares/craft/service/api_services/renewable_api.py @@ -57,9 +57,15 @@ def update_renewable_properties( return new_properties def upload_renewable_matrix(self, renewable_cluster: RenewableCluster, matrix: pd.DataFrame) -> None: - try: - path = PurePosixPath("input") / "renewables" / "series" / f"{renewable_cluster.area_id}" / f"{renewable_cluster.id}" / "series" + path = ( + PurePosixPath("input") + / "renewables" + / "series" + / f"{renewable_cluster.area_id}" + / f"{renewable_cluster.id}" + / "series" + ) upload_series(self._base_url, self.study_id, self._wrapper, matrix, path.as_posix()) except APIError as e: diff --git a/src/antares/craft/service/api_services/thermal_api.py b/src/antares/craft/service/api_services/thermal_api.py index 564c12fa..935e9fb1 100644 --- a/src/antares/craft/service/api_services/thermal_api.py +++ b/src/antares/craft/service/api_services/thermal_api.py @@ -56,10 +56,17 @@ def update_thermal_properties( return new_properties - def upload_thermal_matrix(self, thermal_cluster: ThermalCluster, matrix: pd.DataFrame): - path = PurePosixPath("input") / "thermal" / "series" / f"{thermal_cluster.area_id}" / f"{thermal_cluster.id}" / "series" + def upload_thermal_matrix(self, thermal_cluster: ThermalCluster, matrix: pd.DataFrame) -> None: + path = ( + PurePosixPath("input") + / "thermal" + / "series" + / f"{thermal_cluster.area_id}" + / f"{thermal_cluster.id}" + / "series" + ) try: - body={ + body = { "data": matrix.to_numpy().tolist(), "index": matrix.index.tolist(), "columns": matrix.columns.tolist(), diff --git a/tests/antares/services/api_services/test_renewable_api.py b/tests/antares/services/api_services/test_renewable_api.py index 4352bd2e..6d3659f4 100644 --- a/tests/antares/services/api_services/test_renewable_api.py +++ b/tests/antares/services/api_services/test_renewable_api.py @@ -14,11 +14,13 @@ import requests_mock import pandas as pd -from numpy.matrixlib.defmatrix import matrix from antares.craft.api_conf.api_conf import APIconf -from antares.craft.exceptions.exceptions import RenewableMatrixDownloadError, RenewablePropertiesUpdateError, \ - RenewableMatrixUploadError +from antares.craft.exceptions.exceptions import ( + RenewableMatrixDownloadError, + RenewableMatrixUploadError, + RenewablePropertiesUpdateError, +) from antares.craft.model.area import Area from antares.craft.model.renewable import RenewableCluster, RenewableClusterProperties from antares.craft.service.api_services.area_api import AreaApiService @@ -110,7 +112,8 @@ def test_upload_renewable_matrices_fail(self): mocker.post(url, json={"description": self.antares_web_description_msg}, status_code=404) with pytest.raises( RenewableMatrixUploadError, - match=f"Could not upload matrix for cluster {self.renewable.name} inside area {self.area.name}: " + self.antares_web_description_msg + match=f"Could not upload matrix for cluster {self.renewable.name} inside area {self.area.name}: " + + self.antares_web_description_msg, ): self.renewable.upload_renewable_matrix(self.matrix) diff --git a/tests/antares/services/api_services/test_thermal_api.py b/tests/antares/services/api_services/test_thermal_api.py index 4ef9ec2d..491118dc 100644 --- a/tests/antares/services/api_services/test_thermal_api.py +++ b/tests/antares/services/api_services/test_thermal_api.py @@ -16,8 +16,11 @@ import pandas as pd from antares.craft.api_conf.api_conf import APIconf -from antares.craft.exceptions.exceptions import ThermalMatrixDownloadError, ThermalPropertiesUpdateError, \ - ThermalMatrixUploadError +from antares.craft.exceptions.exceptions import ( + ThermalMatrixDownloadError, + ThermalMatrixUploadError, + ThermalPropertiesUpdateError, +) from antares.craft.model.area import Area from antares.craft.model.study import Study from antares.craft.model.thermal import ThermalCluster, ThermalClusterMatrixName, ThermalClusterProperties @@ -126,8 +129,9 @@ def test_upload_thermal_matrix_fail(self): ) mocker.post(url, json={"description": self.antares_web_description_msg}, status_code=404) with pytest.raises( - ThermalMatrixUploadError, - match=f"Could not upload matrix for cluster {self.thermal.name} inside area {self.area.name}" + self.antares_web_description_msg + ThermalMatrixUploadError, + match=f"Could not upload matrix for cluster {self.thermal.name} inside area {self.area.name}" + + self.antares_web_description_msg, ): self.thermal.upload_thermal_matrix(self.matrix) From 9959b1c41ffd9d6c87428cc6a2cb9d0a82d608dc Mon Sep 17 00:00:00 2001 From: wahadameh Date: Thu, 23 Jan 2025 12:03:10 +0100 Subject: [PATCH 08/19] feat(api): WIP integration tests --- tests/integration/test_web_client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/integration/test_web_client.py b/tests/integration/test_web_client.py index 0951bfa3..7970bfb5 100644 --- a/tests/integration/test_web_client.py +++ b/tests/integration/test_web_client.py @@ -464,6 +464,9 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): area_fr.delete_st_storage(battery_fr) assert battery_fr.id not in study.get_areas().get(area_be.id).get_st_storages() + #tests uploading thermal and renewable matrices + + # tests area deletion error with pytest.raises( AreaDeletionError, From 0e70d20996517cec674e7c41270d8a2fea610f05 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Thu, 23 Jan 2025 12:03:50 +0100 Subject: [PATCH 09/19] feat(api): WIP integration tests --- tests/antares/services/api_services/test_thermal_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/antares/services/api_services/test_thermal_api.py b/tests/antares/services/api_services/test_thermal_api.py index 491118dc..c589f295 100644 --- a/tests/antares/services/api_services/test_thermal_api.py +++ b/tests/antares/services/api_services/test_thermal_api.py @@ -130,7 +130,7 @@ def test_upload_thermal_matrix_fail(self): mocker.post(url, json={"description": self.antares_web_description_msg}, status_code=404) with pytest.raises( ThermalMatrixUploadError, - match=f"Could not upload matrix for cluster {self.thermal.name} inside area {self.area.name}" + match=f"Could not upload matrix for cluster {self.thermal.name} inside area {self.area.name}: " + self.antares_web_description_msg, ): self.thermal.upload_thermal_matrix(self.matrix) From d83d92293e9ec1409dea3708a791f6932d2abccb Mon Sep 17 00:00:00 2001 From: wahadameh Date: Thu, 23 Jan 2025 12:05:11 +0100 Subject: [PATCH 10/19] feat(api): WIP integration tests --- tests/integration/test_web_client.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/integration/test_web_client.py b/tests/integration/test_web_client.py index 7970bfb5..98fb5661 100644 --- a/tests/integration/test_web_client.py +++ b/tests/integration/test_web_client.py @@ -464,8 +464,7 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): area_fr.delete_st_storage(battery_fr) assert battery_fr.id not in study.get_areas().get(area_be.id).get_st_storages() - #tests uploading thermal and renewable matrices - + # tests uploading thermal and renewable matrices # tests area deletion error with pytest.raises( From 2b7855cc31544a5e3a0b4cfeee3cc869e9c0a45b Mon Sep 17 00:00:00 2001 From: wahadameh Date: Thu, 23 Jan 2025 15:43:25 +0100 Subject: [PATCH 11/19] feat(api): included integration tests and correcting minor errors --- src/antares/craft/model/renewable.py | 4 ++-- src/antares/craft/model/thermal.py | 4 ++-- .../craft/service/api_services/renewable_api.py | 2 +- .../craft/service/api_services/thermal_api.py | 10 ++-------- src/antares/craft/service/base_services.py | 14 ++++++++++++++ .../service/local_services/renewable_local.py | 3 +++ .../craft/service/local_services/thermal_local.py | 3 +++ .../services/api_services/test_renewable_api.py | 4 ++-- .../services/api_services/test_thermal_api.py | 4 ++-- tests/integration/test_web_client.py | 5 +++++ 10 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/antares/craft/model/renewable.py b/src/antares/craft/model/renewable.py index fe26a8ea..98d9ed7a 100644 --- a/src/antares/craft/model/renewable.py +++ b/src/antares/craft/model/renewable.py @@ -128,5 +128,5 @@ def update_properties(self, properties: RenewableClusterProperties) -> None: def get_timeseries(self) -> pd.DataFrame: return self._renewable_service.get_renewable_matrix(self.id, self.area_id) - def upload_renewable_matrix(self, matrix: pd.DataFrame) -> None: - self._renewable_service.upload_renewable_matrix(self, matrix) + def update_renewable_matrix(self, matrix: pd.DataFrame) -> None: + self._renewable_service.update_renewable_matrix(self, matrix) diff --git a/src/antares/craft/model/thermal.py b/src/antares/craft/model/thermal.py index c0cda780..b4895e56 100644 --- a/src/antares/craft/model/thermal.py +++ b/src/antares/craft/model/thermal.py @@ -215,5 +215,5 @@ def get_co2_cost_matrix(self) -> pd.DataFrame: def get_fuel_cost_matrix(self) -> pd.DataFrame: return self._thermal_service.get_thermal_matrix(self, ThermalClusterMatrixName.SERIES_FUEL_COST) - def upload_thermal_matrix(self, matrix: pd.DataFrame) -> None: - self._thermal_service.upload_thermal_matrix(self, matrix) + def update_thermal_matrix(self, matrix: pd.DataFrame) -> None: + self._thermal_service.update_thermal_matrix(self, matrix) diff --git a/src/antares/craft/service/api_services/renewable_api.py b/src/antares/craft/service/api_services/renewable_api.py index 9a7ed36c..63e5d267 100644 --- a/src/antares/craft/service/api_services/renewable_api.py +++ b/src/antares/craft/service/api_services/renewable_api.py @@ -56,7 +56,7 @@ def update_renewable_properties( return new_properties - def upload_renewable_matrix(self, renewable_cluster: RenewableCluster, matrix: pd.DataFrame) -> None: + def update_renewable_matrix(self, renewable_cluster: RenewableCluster, matrix: pd.DataFrame) -> None: try: path = ( PurePosixPath("input") diff --git a/src/antares/craft/service/api_services/thermal_api.py b/src/antares/craft/service/api_services/thermal_api.py index 935e9fb1..aa14b421 100644 --- a/src/antares/craft/service/api_services/thermal_api.py +++ b/src/antares/craft/service/api_services/thermal_api.py @@ -56,7 +56,7 @@ def update_thermal_properties( return new_properties - def upload_thermal_matrix(self, thermal_cluster: ThermalCluster, matrix: pd.DataFrame) -> None: + def update_thermal_matrix(self, thermal_cluster: ThermalCluster, matrix: pd.DataFrame) -> None: path = ( PurePosixPath("input") / "thermal" @@ -66,13 +66,7 @@ def upload_thermal_matrix(self, thermal_cluster: ThermalCluster, matrix: pd.Data / "series" ) try: - body = { - "data": matrix.to_numpy().tolist(), - "index": matrix.index.tolist(), - "columns": matrix.columns.tolist(), - } - data = pd.json_normalize(body) - upload_series(self._base_url, self.study_id, self._wrapper, data, path.as_posix()) + upload_series(self._base_url, self.study_id, self._wrapper, matrix, path.as_posix()) except APIError as e: raise ThermalMatrixUploadError(thermal_cluster.area_id, thermal_cluster.name, e.message) from e diff --git a/src/antares/craft/service/base_services.py b/src/antares/craft/service/base_services.py index fe03c2af..d74ed61f 100644 --- a/src/antares/craft/service/base_services.py +++ b/src/antares/craft/service/base_services.py @@ -439,6 +439,10 @@ def update_thermal_properties( """ pass + @abstractmethod + def update_thermal_matrix(self, thermal_cluster: ThermalCluster, matrix: pd.DataFrame) -> None: + pass + @abstractmethod def get_thermal_matrix(self, thermal_cluster: ThermalCluster, ts_name: ThermalClusterMatrixName) -> pd.DataFrame: """ @@ -660,6 +664,16 @@ def get_renewable_matrix(self, cluster_id: str, area_id: str) -> pd.DataFrame: """ pass + @abstractmethod + def update_renewable_matrix(self, renewable_cluster: RenewableCluster, matrix: pd.DataFrame) -> None: + """ + Args: + renewable_cluster: the renewable_cluster + matrix: the renewable matrix we want to update + Returns: None + """ + pass + @abstractmethod def read_renewables(self, area_id: str) -> List[RenewableCluster]: pass diff --git a/src/antares/craft/service/local_services/renewable_local.py b/src/antares/craft/service/local_services/renewable_local.py index c5e8b479..50c94b5b 100644 --- a/src/antares/craft/service/local_services/renewable_local.py +++ b/src/antares/craft/service/local_services/renewable_local.py @@ -63,3 +63,6 @@ def read_renewables(self, area_id: str) -> List[RenewableCluster]: ) ) return renewables_clusters + + def update_renewable_matrix(self, renewable_cluster: RenewableCluster, matrix: pd.DataFrame) -> None: + pass diff --git a/src/antares/craft/service/local_services/thermal_local.py b/src/antares/craft/service/local_services/thermal_local.py index c8540585..ef2fd219 100644 --- a/src/antares/craft/service/local_services/thermal_local.py +++ b/src/antares/craft/service/local_services/thermal_local.py @@ -113,3 +113,6 @@ def read_thermal_clusters(self, area_id: str) -> List[ThermalCluster]: ) ) return thermal_clusters + + def update_thermal_matrix(self, thermal_cluster: ThermalCluster, matrix: pd.DataFrame) -> None: + raise NotImplementedError diff --git a/tests/antares/services/api_services/test_renewable_api.py b/tests/antares/services/api_services/test_renewable_api.py index 6d3659f4..3fee4803 100644 --- a/tests/antares/services/api_services/test_renewable_api.py +++ b/tests/antares/services/api_services/test_renewable_api.py @@ -101,7 +101,7 @@ def test_upload_renewable_matrices_success(self): f"{self.area.id}/{self.renewable.name}/series" ) mocker.post(url, status_code=200) - self.renewable.upload_renewable_matrix(self.matrix) + self.renewable.update_renewable_matrix(self.matrix) def test_upload_renewable_matrices_fail(self): with requests_mock.Mocker() as mocker: @@ -115,7 +115,7 @@ def test_upload_renewable_matrices_fail(self): match=f"Could not upload matrix for cluster {self.renewable.name} inside area {self.area.name}: " + self.antares_web_description_msg, ): - self.renewable.upload_renewable_matrix(self.matrix) + self.renewable.update_renewable_matrix(self.matrix) def test_read_renewables(self): json_renewable = [ diff --git a/tests/antares/services/api_services/test_thermal_api.py b/tests/antares/services/api_services/test_thermal_api.py index c589f295..b1258a9b 100644 --- a/tests/antares/services/api_services/test_thermal_api.py +++ b/tests/antares/services/api_services/test_thermal_api.py @@ -119,7 +119,7 @@ def test_upload_thermal_matrix_success(self): f"{self.area.id}/{self.thermal.name}/series" ) mocker.post(url, status_code=200) - self.thermal.upload_thermal_matrix(self.matrix) + self.thermal.update_thermal_matrix(self.matrix) def test_upload_thermal_matrix_fail(self): with requests_mock.Mocker() as mocker: @@ -133,7 +133,7 @@ def test_upload_thermal_matrix_fail(self): match=f"Could not upload matrix for cluster {self.thermal.name} inside area {self.area.name}: " + self.antares_web_description_msg, ): - self.thermal.upload_thermal_matrix(self.matrix) + self.thermal.update_thermal_matrix(self.matrix) def test_read_thermals(self): json_thermal = [ diff --git a/tests/integration/test_web_client.py b/tests/integration/test_web_client.py index 98fb5661..b87513ed 100644 --- a/tests/integration/test_web_client.py +++ b/tests/integration/test_web_client.py @@ -465,6 +465,11 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): assert battery_fr.id not in study.get_areas().get(area_be.id).get_st_storages() # tests uploading thermal and renewable matrices + thermal_fr_matrix = pd.DataFrame(data=[[0]]) + thermal_fr.update_thermal_matrix(thermal_fr_matrix) + + actual_thermal_matrix = thermal_fr.get_series_matrix() + actual_thermal_matrix.equals(thermal_fr_matrix) # tests area deletion error with pytest.raises( From fe5456dc8c5682c352479fe49af2caeeb961c537 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Fri, 24 Jan 2025 10:40:37 +0100 Subject: [PATCH 12/19] feat(api): included integration tests and correcting minor errors --- tests/integration/test_web_client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration/test_web_client.py b/tests/integration/test_web_client.py index b87513ed..69005726 100644 --- a/tests/integration/test_web_client.py +++ b/tests/integration/test_web_client.py @@ -466,7 +466,9 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): # tests uploading thermal and renewable matrices thermal_fr_matrix = pd.DataFrame(data=[[0]]) + renewable_fr_matrix = pd.DataFrame(data=[[0]]) thermal_fr.update_thermal_matrix(thermal_fr_matrix) + renewable_fr.update_renewable_matrix(renewable_fr_matrix) actual_thermal_matrix = thermal_fr.get_series_matrix() actual_thermal_matrix.equals(thermal_fr_matrix) From 13ef31dea9b1a8df953def2697b067efdc20bb4d Mon Sep 17 00:00:00 2001 From: wahadameh Date: Fri, 24 Jan 2025 13:36:59 +0100 Subject: [PATCH 13/19] feat(api): included integration tests and correcting minor errors --- tests/integration/test_web_client.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/integration/test_web_client.py b/tests/integration/test_web_client.py index 69005726..578e7b41 100644 --- a/tests/integration/test_web_client.py +++ b/tests/integration/test_web_client.py @@ -452,6 +452,15 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): study.delete_link(link_de_fr) assert link_de_fr.id not in study.get_links() + # tests uploading thermal and renewable matrices + thermal_fr_matrix = pd.DataFrame(data=[[0]]) + renewable_fr_matrix = pd.DataFrame(data=[[0]]) + thermal_fr.update_thermal_matrix(thermal_fr_matrix) + renewable_fr.update_renewable_matrix(renewable_fr_matrix) + + actual_thermal_matrix = thermal_fr.get_series_matrix() + actual_thermal_matrix.equals(thermal_fr_matrix) + # tests thermal cluster deletion area_be.delete_thermal_cluster(thermal_be) assert area_be.get_thermals() == {} @@ -464,15 +473,6 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): area_fr.delete_st_storage(battery_fr) assert battery_fr.id not in study.get_areas().get(area_be.id).get_st_storages() - # tests uploading thermal and renewable matrices - thermal_fr_matrix = pd.DataFrame(data=[[0]]) - renewable_fr_matrix = pd.DataFrame(data=[[0]]) - thermal_fr.update_thermal_matrix(thermal_fr_matrix) - renewable_fr.update_renewable_matrix(renewable_fr_matrix) - - actual_thermal_matrix = thermal_fr.get_series_matrix() - actual_thermal_matrix.equals(thermal_fr_matrix) - # tests area deletion error with pytest.raises( AreaDeletionError, From f807670ce9c83d5a207c26bb9a6697941d79266f Mon Sep 17 00:00:00 2001 From: wahadameh Date: Mon, 27 Jan 2025 13:31:13 +0100 Subject: [PATCH 14/19] feat(api): included integration tests and correcting minor errors --- src/antares/craft/exceptions/exceptions.py | 4 ++-- src/antares/craft/model/area.py | 9 +++++---- src/antares/craft/model/binding_constraint.py | 13 ++++++++----- src/antares/craft/model/cluster.py | 4 ++-- src/antares/craft/model/craft_base_model.py | 8 ++++++++ src/antares/craft/model/hydro.py | 4 ++-- src/antares/craft/model/link.py | 6 +++--- src/antares/craft/model/output.py | 4 ++-- src/antares/craft/model/settings/adequacy_patch.py | 5 +++-- .../craft/model/settings/advanced_parameters.py | 5 +++-- src/antares/craft/model/settings/general.py | 6 +++--- src/antares/craft/model/settings/optimization.py | 5 +++-- .../craft/model/settings/playlist_parameters.py | 7 ++++--- src/antares/craft/model/settings/study_settings.py | 5 +++-- .../craft/model/settings/thematic_trimming.py | 4 ++-- src/antares/craft/model/settings/time_series.py | 7 ++++--- src/antares/craft/model/simulation.py | 7 ++++--- src/antares/craft/model/st_storage.py | 4 ++-- .../craft/service/api_services/renewable_api.py | 4 ++-- .../craft/service/api_services/thermal_api.py | 4 ++-- src/antares/craft/tools/all_optional_meta.py | 5 +++-- src/antares/craft/tools/contents_tool.py | 6 +++--- src/antares/craft/tools/ini_tool.py | 8 ++++---- src/antares/craft/tools/model_tools.py | 9 ++++----- .../services/api_services/test_renewable_api.py | 4 ++-- .../services/api_services/test_thermal_api.py | 4 ++-- tests/antares/tools/test_ini_tool.py | 8 ++++---- 27 files changed, 89 insertions(+), 70 deletions(-) create mode 100644 src/antares/craft/model/craft_base_model.py diff --git a/src/antares/craft/exceptions/exceptions.py b/src/antares/craft/exceptions/exceptions.py index 48165d94..04648d99 100644 --- a/src/antares/craft/exceptions/exceptions.py +++ b/src/antares/craft/exceptions/exceptions.py @@ -264,7 +264,7 @@ def __init__(self, area_name: str, cluster_name: str, matrix_name: str, message: super().__init__(self.message) -class ThermalMatrixUploadError(Exception): +class ThermalMatrixUpdateError(Exception): def __init__(self, area_name: str, cluster_name: str, message: str) -> None: self.message = f"Could not upload matrix for cluster {cluster_name} inside area {area_name}: " + message super().__init__(self.message) @@ -276,7 +276,7 @@ def __init__(self, area_name: str, renewable_name: str, message: str) -> None: super().__init__(self.message) -class RenewableMatrixUploadError(Exception): +class RenewableMatrixUpdateError(Exception): def __init__(self, area_name: str, renewable_name: str, message: str) -> None: self.message = f"Could not upload matrix for cluster {renewable_name} inside area {area_name}: " + message super().__init__(self.message) diff --git a/src/antares/craft/model/area.py b/src/antares/craft/model/area.py index 3742895e..cb0ff9e9 100644 --- a/src/antares/craft/model/area.py +++ b/src/antares/craft/model/area.py @@ -22,6 +22,7 @@ import pandas as pd from antares.craft.model.commons import FilterOption, sort_filter_values +from antares.craft.model.craft_base_model import CraftBaseModel from antares.craft.model.hydro import Hydro, HydroMatrixName, HydroProperties from antares.craft.model.renewable import RenewableCluster, RenewableClusterProperties from antares.craft.model.st_storage import STStorage, STStorageProperties @@ -29,7 +30,7 @@ from antares.craft.tools.alias_generators import to_space from antares.craft.tools.all_optional_meta import all_optional_model from antares.craft.tools.contents_tool import EnumIgnoreCase, transform_name_to_id -from pydantic import BaseModel, computed_field +from pydantic import computed_field from pydantic.alias_generators import to_camel @@ -45,7 +46,7 @@ class AdequacyPatchMode(EnumIgnoreCase): VIRTUAL = "virtual" -class DefaultAreaProperties(BaseModel, extra="forbid", populate_by_name=True): +class DefaultAreaProperties(CraftBaseModel, extra="forbid", populate_by_name=True): """ DTO for updating area properties """ @@ -111,7 +112,7 @@ def yield_area_properties(self) -> AreaProperties: return AreaProperties.model_validate(self.model_dump(mode="json", exclude=excludes)) -class AreaUi(BaseModel, extra="forbid", populate_by_name=True, alias_generator=to_camel): +class AreaUi(CraftBaseModel, extra="forbid", populate_by_name=True, alias_generator=to_camel): """ DTO for updating area UI """ @@ -128,7 +129,7 @@ class AreaUi(BaseModel, extra="forbid", populate_by_name=True, alias_generator=t layer_color: Optional[Dict[int, str]] = None -class AreaUiLocal(BaseModel): +class AreaUiLocal(CraftBaseModel): """ DTO for updating area UI locally in the ini files """ diff --git a/src/antares/craft/model/binding_constraint.py b/src/antares/craft/model/binding_constraint.py index 667415f0..6bc26e2e 100644 --- a/src/antares/craft/model/binding_constraint.py +++ b/src/antares/craft/model/binding_constraint.py @@ -15,9 +15,10 @@ import pandas as pd +from antares.craft.model.craft_base_model import CraftBaseModel from antares.craft.tools.all_optional_meta import all_optional_model from antares.craft.tools.contents_tool import EnumIgnoreCase, transform_name_to_id -from pydantic import BaseModel, Field, model_validator +from pydantic import Field, model_validator from pydantic.alias_generators import to_camel @@ -40,7 +41,7 @@ class ConstraintMatrixName(Enum): GREATER_TERM = "gt" -class TermOperators(BaseModel): +class TermOperators(CraftBaseModel): weight: Optional[float] = None offset: Optional[int] = None @@ -53,7 +54,7 @@ def weight_offset(self) -> str: return weight_offset -class LinkData(BaseModel): +class LinkData(CraftBaseModel): """ DTO for a constraint term on a link between two areas. """ @@ -62,7 +63,7 @@ class LinkData(BaseModel): area2: str -class ClusterData(BaseModel): +class ClusterData(CraftBaseModel): """ DTO for a constraint term on a cluster in an area. """ @@ -91,7 +92,9 @@ def generate_id(cls, data: Union[Dict[str, str], LinkData, ClusterData]) -> str: return ".".join((data.area.lower(), data.cluster.lower())) -class DefaultBindingConstraintProperties(BaseModel, extra="forbid", populate_by_name=True, alias_generator=to_camel): +class DefaultBindingConstraintProperties( + CraftBaseModel, extra="forbid", populate_by_name=True, alias_generator=to_camel +): """Default properties for binding constraints Attributes: diff --git a/src/antares/craft/model/cluster.py b/src/antares/craft/model/cluster.py index bab9649e..3be7106f 100644 --- a/src/antares/craft/model/cluster.py +++ b/src/antares/craft/model/cluster.py @@ -12,11 +12,11 @@ from typing import Optional -from pydantic import BaseModel +from antares.craft.model.craft_base_model import CraftBaseModel from pydantic.alias_generators import to_camel -class ClusterProperties(BaseModel, extra="forbid", populate_by_name=True, alias_generator=to_camel): +class ClusterProperties(CraftBaseModel, extra="forbid", populate_by_name=True, alias_generator=to_camel): """ Common properties for thermal and renewable clusters """ diff --git a/src/antares/craft/model/craft_base_model.py b/src/antares/craft/model/craft_base_model.py new file mode 100644 index 00000000..36d36e0b --- /dev/null +++ b/src/antares/craft/model/craft_base_model.py @@ -0,0 +1,8 @@ +from pydantic import BaseModel + + +class CraftBaseModel(BaseModel): + pass + # def __init__(self, /, **data: Any): + # super().__init__(**data) + # self.model_dump(warnings=False) diff --git a/src/antares/craft/model/hydro.py b/src/antares/craft/model/hydro.py index 44418ed9..960d7950 100644 --- a/src/antares/craft/model/hydro.py +++ b/src/antares/craft/model/hydro.py @@ -15,8 +15,8 @@ import pandas as pd +from antares.craft.model.craft_base_model import CraftBaseModel from antares.craft.tools.all_optional_meta import all_optional_model -from pydantic import BaseModel from pydantic.alias_generators import to_camel @@ -32,7 +32,7 @@ class HydroMatrixName(Enum): COMMON_CREDIT_MODULATIONS = "creditmodulations" -class DefaultHydroProperties(BaseModel, extra="forbid", populate_by_name=True, alias_generator=to_camel): +class DefaultHydroProperties(CraftBaseModel, extra="forbid", populate_by_name=True, alias_generator=to_camel): """ Properties of hydro system read from the configuration files. diff --git a/src/antares/craft/model/link.py b/src/antares/craft/model/link.py index b7f00141..bcecc30b 100644 --- a/src/antares/craft/model/link.py +++ b/src/antares/craft/model/link.py @@ -16,10 +16,10 @@ import pandas as pd from antares.craft.model.commons import FilterOption, sort_filter_values +from antares.craft.model.craft_base_model import CraftBaseModel from antares.craft.tools.alias_generators import to_kebab from antares.craft.tools.all_optional_meta import all_optional_model from antares.craft.tools.contents_tool import transform_name_to_id -from pydantic import BaseModel class TransmissionCapacities(Enum): @@ -43,7 +43,7 @@ class LinkStyle(Enum): DOT_DASH = "dotdash" -class DefaultLinkProperties(BaseModel, extra="forbid", populate_by_name=True, alias_generator=to_kebab): +class DefaultLinkProperties(CraftBaseModel, extra="forbid", populate_by_name=True, alias_generator=to_kebab): """ DTO for updating link properties """ @@ -98,7 +98,7 @@ def yield_link_properties(self) -> LinkProperties: return LinkProperties.model_validate(self.model_dump(mode="json", exclude=excludes)) -class DefaultLinkUi(BaseModel, extra="forbid", populate_by_name=True, alias_generator=to_kebab): +class DefaultLinkUi(CraftBaseModel, extra="forbid", populate_by_name=True, alias_generator=to_kebab): """ DTO for updating link UI """ diff --git a/src/antares/craft/model/output.py b/src/antares/craft/model/output.py index 2046daf8..eb458fb7 100644 --- a/src/antares/craft/model/output.py +++ b/src/antares/craft/model/output.py @@ -14,7 +14,7 @@ import pandas as pd -from pydantic import BaseModel +from antares.craft.model.craft_base_model import CraftBaseModel class MCIndAreas(Enum): @@ -49,7 +49,7 @@ class Frequency(Enum): ANNUAL = "annual" -class AggregationEntry(BaseModel): +class AggregationEntry(CraftBaseModel): """ Represents an entry for aggregation queries diff --git a/src/antares/craft/model/settings/adequacy_patch.py b/src/antares/craft/model/settings/adequacy_patch.py index 6159c1d1..b6c46754 100644 --- a/src/antares/craft/model/settings/adequacy_patch.py +++ b/src/antares/craft/model/settings/adequacy_patch.py @@ -12,8 +12,9 @@ from enum import Enum +from antares.craft.model.craft_base_model import CraftBaseModel from antares.craft.tools.all_optional_meta import all_optional_model -from pydantic import BaseModel, ConfigDict, Field +from pydantic import ConfigDict, Field from pydantic.alias_generators import to_camel @@ -22,7 +23,7 @@ class PriceTakingOrder(Enum): LOAD = "Load" -class DefaultAdequacyPatchParameters(BaseModel, populate_by_name=True, alias_generator=to_camel): +class DefaultAdequacyPatchParameters(CraftBaseModel, populate_by_name=True, alias_generator=to_camel): model_config = ConfigDict(use_enum_values=True) # version 830 diff --git a/src/antares/craft/model/settings/advanced_parameters.py b/src/antares/craft/model/settings/advanced_parameters.py index b145ef1f..5c44a06e 100644 --- a/src/antares/craft/model/settings/advanced_parameters.py +++ b/src/antares/craft/model/settings/advanced_parameters.py @@ -13,10 +13,11 @@ from enum import Enum from typing import Any, Optional +from antares.craft.model.craft_base_model import CraftBaseModel from antares.craft.model.settings.general import OutputChoices from antares.craft.tools.alias_generators import to_kebab from antares.craft.tools.all_optional_meta import all_optional_model -from pydantic import BaseModel, ConfigDict, Field, model_validator +from pydantic import ConfigDict, Field, model_validator from pydantic.alias_generators import to_camel from typing_extensions import Self @@ -70,7 +71,7 @@ class RenewableGenerationModeling(Enum): CLUSTERS = "clusters" -class DefaultAdvancedParameters(BaseModel, alias_generator=to_camel): +class DefaultAdvancedParameters(CraftBaseModel, alias_generator=to_camel): model_config = ConfigDict(use_enum_values=True) # Advanced parameters diff --git a/src/antares/craft/model/settings/general.py b/src/antares/craft/model/settings/general.py index 7871c45e..b6f14b6e 100644 --- a/src/antares/craft/model/settings/general.py +++ b/src/antares/craft/model/settings/general.py @@ -9,10 +9,10 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. - +from antares.craft.model.craft_base_model import CraftBaseModel from antares.craft.tools.all_optional_meta import all_optional_model from antares.craft.tools.contents_tool import EnumIgnoreCase -from pydantic import BaseModel, ConfigDict, Field +from pydantic import ConfigDict, Field from pydantic.alias_generators import to_camel @@ -69,7 +69,7 @@ class OutputFormat(EnumIgnoreCase): ZIP = "zip-files" -class DefaultGeneralParameters(BaseModel, extra="forbid", populate_by_name=True, alias_generator=to_camel): +class DefaultGeneralParameters(CraftBaseModel, extra="forbid", populate_by_name=True, alias_generator=to_camel): model_config = ConfigDict(use_enum_values=True) mode: Mode = Field(default=Mode.ECONOMY, validate_default=True) diff --git a/src/antares/craft/model/settings/optimization.py b/src/antares/craft/model/settings/optimization.py index 8154786f..b79981d2 100644 --- a/src/antares/craft/model/settings/optimization.py +++ b/src/antares/craft/model/settings/optimization.py @@ -13,8 +13,9 @@ from enum import Enum from typing import Union +from antares.craft.model.craft_base_model import CraftBaseModel from antares.craft.tools.all_optional_meta import all_optional_model -from pydantic import BaseModel, ConfigDict, Field +from pydantic import ConfigDict, Field from pydantic.alias_generators import to_camel @@ -51,7 +52,7 @@ class ExportMPS(Enum): TRUE = True -class DefaultOptimizationParameters(BaseModel, alias_generator=to_camel): +class DefaultOptimizationParameters(CraftBaseModel, alias_generator=to_camel): model_config = ConfigDict(use_enum_values=True) simplex_optimization_range: SimplexOptimizationRange = Field( diff --git a/src/antares/craft/model/settings/playlist_parameters.py b/src/antares/craft/model/settings/playlist_parameters.py index 95acc96f..0b7e9b99 100644 --- a/src/antares/craft/model/settings/playlist_parameters.py +++ b/src/antares/craft/model/settings/playlist_parameters.py @@ -12,15 +12,16 @@ from typing import Any -from pydantic import BaseModel, Field, ValidationError, field_validator, model_serializer, model_validator +from antares.craft.model.craft_base_model import CraftBaseModel +from pydantic import Field, ValidationError, field_validator, model_serializer, model_validator -class PlaylistData(BaseModel): +class PlaylistData(CraftBaseModel): status: bool = True weight: float = 1.0 -class PlaylistParameters(BaseModel): +class PlaylistParameters(CraftBaseModel): """ Parameters for playlists. diff --git a/src/antares/craft/model/settings/study_settings.py b/src/antares/craft/model/settings/study_settings.py index d243f942..8afb3e4e 100644 --- a/src/antares/craft/model/settings/study_settings.py +++ b/src/antares/craft/model/settings/study_settings.py @@ -11,6 +11,7 @@ # This file is part of the Antares project. from typing import Optional +from antares.craft.model.craft_base_model import CraftBaseModel from antares.craft.model.settings.adequacy_patch import AdequacyPatchParametersLocal, DefaultAdequacyPatchParameters from antares.craft.model.settings.advanced_parameters import AdvancedParametersLocal, DefaultAdvancedParameters from antares.craft.model.settings.general import DefaultGeneralParameters, GeneralParametersLocal @@ -23,10 +24,10 @@ from antares.craft.model.settings.time_series import DefaultTimeSeriesParameters, TimeSeriesParametersLocal from antares.craft.tools.all_optional_meta import all_optional_model from antares.craft.tools.ini_tool import get_ini_fields_for_ini -from pydantic import BaseModel, model_serializer +from pydantic import model_serializer -class DefaultStudySettings(BaseModel): +class DefaultStudySettings(CraftBaseModel): general_parameters: DefaultGeneralParameters = DefaultGeneralParameters() # These parameters are listed under the [variables selection] section in the .ini file. # They are required if thematic-trimming is set to true. diff --git a/src/antares/craft/model/settings/thematic_trimming.py b/src/antares/craft/model/settings/thematic_trimming.py index 8d1460fd..9e326000 100644 --- a/src/antares/craft/model/settings/thematic_trimming.py +++ b/src/antares/craft/model/settings/thematic_trimming.py @@ -11,12 +11,12 @@ # This file is part of the Antares project. from enum import Enum +from antares.craft.model.craft_base_model import CraftBaseModel from antares.craft.tools.all_optional_meta import all_optional_model -from pydantic import BaseModel from pydantic.alias_generators import to_camel -class DefaultThematicTrimmingParameters(BaseModel, alias_generator=to_camel): +class DefaultThematicTrimmingParameters(CraftBaseModel, alias_generator=to_camel): """ This class manages the configuration of result filtering in a simulation. diff --git a/src/antares/craft/model/settings/time_series.py b/src/antares/craft/model/settings/time_series.py index 19381518..cd7df6bd 100644 --- a/src/antares/craft/model/settings/time_series.py +++ b/src/antares/craft/model/settings/time_series.py @@ -13,9 +13,10 @@ from enum import Enum from typing import Optional +from antares.craft.model.craft_base_model import CraftBaseModel from antares.craft.tools.all_optional_meta import all_optional_model from antares.craft.tools.model_tools import filter_out_empty_model_fields -from pydantic import BaseModel, Field +from pydantic import Field from pydantic.alias_generators import to_camel @@ -24,7 +25,7 @@ class SeasonCorrelation(Enum): ANNUAL = "annual" -class _DefaultParameters(BaseModel, alias_generator=to_camel): +class _DefaultParameters(CraftBaseModel, alias_generator=to_camel): stochastic_ts_status: bool = False number: int = 1 refresh: bool = False @@ -45,7 +46,7 @@ class _ParametersLocal(_DefaultParameters, populate_by_name=True): field_name: str = Field(exclude=True) -class DefaultTimeSeriesParameters(BaseModel, alias_generator=to_camel): +class DefaultTimeSeriesParameters(CraftBaseModel, alias_generator=to_camel): load: _DefaultParameters = _DefaultParameters() hydro: _DefaultParameters = _DefaultParameters() thermal: _DefaultParameters = _DefaultParameters() diff --git a/src/antares/craft/model/simulation.py b/src/antares/craft/model/simulation.py index 3faefc6e..5533a51d 100644 --- a/src/antares/craft/model/simulation.py +++ b/src/antares/craft/model/simulation.py @@ -13,7 +13,8 @@ from enum import Enum from typing import Any, Optional -from pydantic import BaseModel, Field +from antares.craft.model.craft_base_model import CraftBaseModel +from pydantic import Field class Solver(Enum): @@ -22,7 +23,7 @@ class Solver(Enum): SIRIUS = "sirius" -class AntaresSimulationParameters(BaseModel): +class AntaresSimulationParameters(CraftBaseModel): solver: Solver = Solver.SIRIUS nb_cpu: Optional[int] = None unzip_output: bool = Field(alias="auto_unzip", default=True) @@ -58,7 +59,7 @@ def from_str(input: str) -> "JobStatus": return JobStatus.__getitem__(input.upper()) -class Job(BaseModel): +class Job(CraftBaseModel): job_id: str status: JobStatus output_id: Optional[str] = None diff --git a/src/antares/craft/model/st_storage.py b/src/antares/craft/model/st_storage.py index edd3bc65..4b6d43f8 100644 --- a/src/antares/craft/model/st_storage.py +++ b/src/antares/craft/model/st_storage.py @@ -15,9 +15,9 @@ import pandas as pd +from antares.craft.model.craft_base_model import CraftBaseModel from antares.craft.tools.all_optional_meta import all_optional_model from antares.craft.tools.contents_tool import transform_name_to_id -from pydantic import BaseModel from pydantic.alias_generators import to_camel @@ -42,7 +42,7 @@ class STStorageMatrixName(Enum): INFLOWS = "inflows" -class DefaultSTStorageProperties(BaseModel, extra="forbid", populate_by_name=True, alias_generator=to_camel): +class DefaultSTStorageProperties(CraftBaseModel, extra="forbid", populate_by_name=True, alias_generator=to_camel): """ Properties of a short-term storage system read from the configuration files. diff --git a/src/antares/craft/service/api_services/renewable_api.py b/src/antares/craft/service/api_services/renewable_api.py index 63e5d267..7097e995 100644 --- a/src/antares/craft/service/api_services/renewable_api.py +++ b/src/antares/craft/service/api_services/renewable_api.py @@ -20,7 +20,7 @@ from antares.craft.exceptions.exceptions import ( APIError, RenewableMatrixDownloadError, - RenewableMatrixUploadError, + RenewableMatrixUpdateError, RenewablePropertiesUpdateError, ) from antares.craft.model.renewable import RenewableCluster, RenewableClusterProperties @@ -69,7 +69,7 @@ def update_renewable_matrix(self, renewable_cluster: RenewableCluster, matrix: p upload_series(self._base_url, self.study_id, self._wrapper, matrix, path.as_posix()) except APIError as e: - raise RenewableMatrixUploadError(renewable_cluster.area_id, renewable_cluster.id, e.message) from e + raise RenewableMatrixUpdateError(renewable_cluster.area_id, renewable_cluster.id, e.message) from e def get_renewable_matrix(self, cluster_id: str, area_id: str) -> pd.DataFrame: try: diff --git a/src/antares/craft/service/api_services/thermal_api.py b/src/antares/craft/service/api_services/thermal_api.py index aa14b421..9f925972 100644 --- a/src/antares/craft/service/api_services/thermal_api.py +++ b/src/antares/craft/service/api_services/thermal_api.py @@ -20,7 +20,7 @@ from antares.craft.exceptions.exceptions import ( APIError, ThermalMatrixDownloadError, - ThermalMatrixUploadError, + ThermalMatrixUpdateError, ThermalPropertiesUpdateError, ) from antares.craft.model.thermal import ThermalCluster, ThermalClusterMatrixName, ThermalClusterProperties @@ -68,7 +68,7 @@ def update_thermal_matrix(self, thermal_cluster: ThermalCluster, matrix: pd.Data try: upload_series(self._base_url, self.study_id, self._wrapper, matrix, path.as_posix()) except APIError as e: - raise ThermalMatrixUploadError(thermal_cluster.area_id, thermal_cluster.name, e.message) from e + raise ThermalMatrixUpdateError(thermal_cluster.area_id, thermal_cluster.name, e.message) from e def get_thermal_matrix(self, thermal_cluster: ThermalCluster, ts_name: ThermalClusterMatrixName) -> pd.DataFrame: try: diff --git a/src/antares/craft/tools/all_optional_meta.py b/src/antares/craft/tools/all_optional_meta.py index 9aa394e3..d691586b 100644 --- a/src/antares/craft/tools/all_optional_meta.py +++ b/src/antares/craft/tools/all_optional_meta.py @@ -13,9 +13,10 @@ import copy import typing as t -from pydantic import BaseModel, create_model +from antares.craft.model.craft_base_model import CraftBaseModel +from pydantic import create_model -ModelClass = t.TypeVar("ModelClass", bound=BaseModel) +ModelClass = t.TypeVar("ModelClass", bound=CraftBaseModel) def all_optional_model(model: t.Type[ModelClass]) -> t.Type[ModelClass]: diff --git a/src/antares/craft/tools/contents_tool.py b/src/antares/craft/tools/contents_tool.py index 98963681..ed6344b0 100644 --- a/src/antares/craft/tools/contents_tool.py +++ b/src/antares/craft/tools/contents_tool.py @@ -17,8 +17,8 @@ from pathlib import Path from typing import Any, Dict, Optional +from antares.craft.model.craft_base_model import CraftBaseModel from antares.craft.tools.custom_raw_config_parser import CustomRawConfigParser -from pydantic import BaseModel # Invalid chars was taken from Antares Simulator (C++). _sub_invalid_chars = re.compile(r"[^a-zA-Z0-9_(),& -]+").sub @@ -86,12 +86,12 @@ def _missing_(cls, value: object) -> Optional["EnumIgnoreCase"]: return None -class AreaUiResponse(BaseModel): +class AreaUiResponse(CraftBaseModel): """ Utility class to convert the AntaresWebResponse to Antares-Craft object. """ - class MapResponse(BaseModel): + class MapResponse(CraftBaseModel): color_r: int color_g: int color_b: int diff --git a/src/antares/craft/tools/ini_tool.py b/src/antares/craft/tools/ini_tool.py index abeecaf7..dc62ce4a 100644 --- a/src/antares/craft/tools/ini_tool.py +++ b/src/antares/craft/tools/ini_tool.py @@ -14,9 +14,9 @@ from pathlib import Path from typing import Any, Iterable, Optional, Union +from antares.craft.model.craft_base_model import CraftBaseModel from antares.craft.tools.custom_raw_config_parser import CustomRawConfigParser from antares.craft.tools.model_tools import filter_out_empty_model_fields -from pydantic import BaseModel class InitializationFilesTypes(Enum): @@ -250,13 +250,13 @@ def _filter_out_empty_list_entries(list_of_entries: list[Any]) -> list: return output_dict -def get_ini_fields_for_ini(model: BaseModel) -> dict: +def get_ini_fields_for_ini(model: CraftBaseModel) -> dict: """ - Creates a dictionary of the property `ini_fields` from a `BaseModel` object that contains the merged dictionaries + Creates a dictionary of the property `ini_fields` from a `CraftBaseModel` object that contains the merged dictionaries of all the `ini_fields` properties. Args: - model (BaseModel): A `BaseModel` object containing other objects. + model (CraftBaseModel): A `CraftBaseModel` object containing other objects. Returns: dict[str, Any]: A dictionary of the property `ini_fields` from the contained objects. diff --git a/src/antares/craft/tools/model_tools.py b/src/antares/craft/tools/model_tools.py index d50ab8c1..e5fbf1d8 100644 --- a/src/antares/craft/tools/model_tools.py +++ b/src/antares/craft/tools/model_tools.py @@ -9,16 +9,15 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. +from antares.craft.model.craft_base_model import CraftBaseModel -from pydantic import BaseModel - -def filter_out_empty_model_fields(model: BaseModel) -> list[str]: +def filter_out_empty_model_fields(model: CraftBaseModel) -> list[str]: """ - Creates a list of field names filtering out empty fields from `BaseModel` objects. + Creates a list of field names filtering out empty fields from `CraftBaseModel` objects. Args: - model (BaseModel): Model to filter. + model (CraftBaseModel): Model to filter. Returns: list[str]: List of field names. diff --git a/tests/antares/services/api_services/test_renewable_api.py b/tests/antares/services/api_services/test_renewable_api.py index 3fee4803..6fc47aa0 100644 --- a/tests/antares/services/api_services/test_renewable_api.py +++ b/tests/antares/services/api_services/test_renewable_api.py @@ -18,7 +18,7 @@ from antares.craft.api_conf.api_conf import APIconf from antares.craft.exceptions.exceptions import ( RenewableMatrixDownloadError, - RenewableMatrixUploadError, + RenewableMatrixUpdateError, RenewablePropertiesUpdateError, ) from antares.craft.model.area import Area @@ -111,7 +111,7 @@ def test_upload_renewable_matrices_fail(self): ) mocker.post(url, json={"description": self.antares_web_description_msg}, status_code=404) with pytest.raises( - RenewableMatrixUploadError, + RenewableMatrixUpdateError, match=f"Could not upload matrix for cluster {self.renewable.name} inside area {self.area.name}: " + self.antares_web_description_msg, ): diff --git a/tests/antares/services/api_services/test_thermal_api.py b/tests/antares/services/api_services/test_thermal_api.py index b1258a9b..2feaf5a1 100644 --- a/tests/antares/services/api_services/test_thermal_api.py +++ b/tests/antares/services/api_services/test_thermal_api.py @@ -18,7 +18,7 @@ from antares.craft.api_conf.api_conf import APIconf from antares.craft.exceptions.exceptions import ( ThermalMatrixDownloadError, - ThermalMatrixUploadError, + ThermalMatrixUpdateError, ThermalPropertiesUpdateError, ) from antares.craft.model.area import Area @@ -129,7 +129,7 @@ def test_upload_thermal_matrix_fail(self): ) mocker.post(url, json={"description": self.antares_web_description_msg}, status_code=404) with pytest.raises( - ThermalMatrixUploadError, + ThermalMatrixUpdateError, match=f"Could not upload matrix for cluster {self.thermal.name} inside area {self.area.name}: " + self.antares_web_description_msg, ): diff --git a/tests/antares/tools/test_ini_tool.py b/tests/antares/tools/test_ini_tool.py index bc17e003..e431f0a1 100644 --- a/tests/antares/tools/test_ini_tool.py +++ b/tests/antares/tools/test_ini_tool.py @@ -9,8 +9,8 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. +from antares.craft.model.craft_base_model import CraftBaseModel from antares.craft.tools.ini_tool import get_ini_fields_for_ini, merge_dicts_for_ini -from pydantic import BaseModel class TestExtraFunctions: @@ -42,7 +42,7 @@ def test_merging_three_simple_dicts(self): def test_get_ini_fields(self): # Given - class ChildObjectOne(BaseModel): + class ChildObjectOne(CraftBaseModel): general: str = "test one" test_a: str = "abc" @@ -50,7 +50,7 @@ class ChildObjectOne(BaseModel): def ini_fields(self): return {"general": {"test": self.general, "test_a": self.test_a}} - class ChildObjectTwo(BaseModel): + class ChildObjectTwo(CraftBaseModel): general: str = "test two" test_b: str = "def" @@ -58,7 +58,7 @@ class ChildObjectTwo(BaseModel): def ini_fields(self): return {"general": {"test": self.general, "test_b": self.test_b}} - class ParentObject(BaseModel): + class ParentObject(CraftBaseModel): child_one: ChildObjectOne = ChildObjectOne() child_two: ChildObjectTwo = ChildObjectTwo() From 4e06e3b318371d6588a3d7e3d8c2647e61045269 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Mon, 27 Jan 2025 14:20:23 +0100 Subject: [PATCH 15/19] feat(api): creating CraftBaseModel class --- src/antares/craft/model/craft_base_model.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/antares/craft/model/craft_base_model.py b/src/antares/craft/model/craft_base_model.py index 36d36e0b..5d549830 100644 --- a/src/antares/craft/model/craft_base_model.py +++ b/src/antares/craft/model/craft_base_model.py @@ -1,8 +1,17 @@ -from pydantic import BaseModel +# Copyright (c) 2024, RTE (https://www.rte-france.com) +# +# See AUTHORS.txt +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# SPDX-License-Identifier: MPL-2.0 +# +# This file is part of the Antares project. + +from pydantic import BaseModel, ConfigDict class CraftBaseModel(BaseModel): - pass - # def __init__(self, /, **data: Any): - # super().__init__(**data) - # self.model_dump(warnings=False) + model_config = ConfigDict(hide_input_in_errors=True, ignored_types=(str, set)) From e6458867b87750d5fe1139bc3d26f85123102b17 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Mon, 27 Jan 2025 16:32:43 +0100 Subject: [PATCH 16/19] fix(api): trying to fix warning problems --- src/antares/craft/model/craft_base_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/antares/craft/model/craft_base_model.py b/src/antares/craft/model/craft_base_model.py index 5d549830..21fa907f 100644 --- a/src/antares/craft/model/craft_base_model.py +++ b/src/antares/craft/model/craft_base_model.py @@ -14,4 +14,4 @@ class CraftBaseModel(BaseModel): - model_config = ConfigDict(hide_input_in_errors=True, ignored_types=(str, set)) + model_config = ConfigDict(arbitrary_types_allowed=True) From 11ee5f2c0804645a2a1bf0b77dbca10294020e27 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Tue, 28 Jan 2025 16:11:22 +0100 Subject: [PATCH 17/19] changed pydantic version --- pyproject.toml | 2 +- requirements.txt | 2 +- src/antares/craft/model/craft_base_model.py | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bfbecc18..73b0b128 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ dependencies = [ "configparser~=5.0.2", "numpy~=1.26.4", "pandas~=2.2.2", - "pydantic~=2.7.1", + "pydantic~=2.8.2", "requests~=2.32.0" ] classifiers = [ diff --git a/requirements.txt b/requirements.txt index 48cd6c73..68d07918 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ configparser~=5.0.2 numpy~=1.26.4 pandas~=2.2.2 pandas-stubs~=2.2.2 -pydantic~=2.7.1 +pydantic~=2.8.2 pytest~=7.2.1 python-dateutil~=2.9.0 requests~=2.32.0 diff --git a/src/antares/craft/model/craft_base_model.py b/src/antares/craft/model/craft_base_model.py index 21fa907f..4838f08e 100644 --- a/src/antares/craft/model/craft_base_model.py +++ b/src/antares/craft/model/craft_base_model.py @@ -9,9 +9,16 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. +from typing import Any from pydantic import BaseModel, ConfigDict class CraftBaseModel(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True) + + def model_dump(self, **kwargs) -> dict[str, Any]: + return super().model_dump(serialize_as_any=True, **kwargs) + + def model_dump_json(self, **kwargs) -> str: + return super().model_dump_json(serialize_as_any=True, **kwargs) From 8e65c3f0cdbbe9674aba056836649537b798bc01 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Tue, 28 Jan 2025 16:13:50 +0100 Subject: [PATCH 18/19] changed pydantic version --- src/antares/craft/model/craft_base_model.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/antares/craft/model/craft_base_model.py b/src/antares/craft/model/craft_base_model.py index 4838f08e..1a430ea4 100644 --- a/src/antares/craft/model/craft_base_model.py +++ b/src/antares/craft/model/craft_base_model.py @@ -16,9 +16,3 @@ class CraftBaseModel(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True) - - def model_dump(self, **kwargs) -> dict[str, Any]: - return super().model_dump(serialize_as_any=True, **kwargs) - - def model_dump_json(self, **kwargs) -> str: - return super().model_dump_json(serialize_as_any=True, **kwargs) From 27a631f07509bb5d5c5ac6c984cc02c5f20f3aa6 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Tue, 28 Jan 2025 16:14:37 +0100 Subject: [PATCH 19/19] changed pydantic version --- src/antares/craft/model/craft_base_model.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/antares/craft/model/craft_base_model.py b/src/antares/craft/model/craft_base_model.py index 1a430ea4..21fa907f 100644 --- a/src/antares/craft/model/craft_base_model.py +++ b/src/antares/craft/model/craft_base_model.py @@ -9,7 +9,6 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. -from typing import Any from pydantic import BaseModel, ConfigDict