From bcdf9616a69268c9e07c723ababf644269902c82 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Tue, 26 Nov 2024 14:53:05 +0100 Subject: [PATCH 01/33] added read_thermal_clusters/storages/renewables methods and realized unit testing --- src/antares/service/api_services/area_api.py | 49 +++++-------- .../service/api_services/renewable_api.py | 19 +++++ .../service/api_services/st_storage_api.py | 16 +++++ .../service/api_services/thermal_api.py | 19 +++++ src/antares/service/base_services.py | 12 ++++ .../service/local_services/renewable_local.py | 5 +- .../local_services/st_storage_local.py | 5 +- .../service/local_services/thermal_local.py | 5 +- .../services/api_services/test_area_api.py | 1 - .../api_services/test_renewable_api.py | 43 +++++++++++ .../api_services/test_st_storage_api.py | 45 ++++++++++++ .../services/api_services/test_thermal_api.py | 72 +++++++++++++++++++ 12 files changed, 256 insertions(+), 35 deletions(-) diff --git a/src/antares/service/api_services/area_api.py b/src/antares/service/api_services/area_api.py index e597b2a6..059308b4 100644 --- a/src/antares/service/api_services/area_api.py +++ b/src/antares/service/api_services/area_api.py @@ -557,48 +557,35 @@ def read_areas(self) -> List[Area]: base_api_url = f"{self._base_url}/studies/{self.study_id}/areas" ui_url = "ui=true" - url_thermal = "clusters/thermal" - url_renewable = "clusters/renewable" - url_st_storage = "storages" url_properties_form = "properties/form" json_resp = self._wrapper.get(base_api_url + "?" + ui_url).json() for area in json_resp: - thermals = dict() - renewables = dict() - st_storage = dict() + dict_renewables = dict() + dict_thermals = dict() + dict_st_storage = dict() area_url = base_api_url + "/" + f"{area}/" - json_thermal = self._wrapper.get(area_url + url_thermal).json() - json_renewable = self._wrapper.get(area_url + url_renewable).json() - json_st_storage = self._wrapper.get(area_url + url_st_storage).json() json_properties = self._wrapper.get(area_url + url_properties_form).json() ui_response = self.craft_ui(f"{base_api_url}?type=AREA&{ui_url}", area) - for thermal in json_thermal: - id_therm = thermal.pop("id") - name = thermal.pop("name") + assert self.renewable_service is not None + assert self.thermal_service is not None + assert self.storage_service is not None - thermal_props = ThermalClusterProperties(**thermal) - therm_cluster = ThermalCluster(self.thermal_service, area, name, thermal_props) - thermals.update({id_therm: therm_cluster}) + renewables = self.renewable_service.read_renewables(area_url) + thermals = self.thermal_service.read_thermal_clusters(area_url) + st_storages = self.storage_service.read_st_storages(area_url) - for renewable in json_renewable: - id_renew = renewable.pop("id") - name = renewable.pop("name") + for renewable in renewables: + dict_renewables.update({renewable.id: renewable}) - renew_props = RenewableClusterProperties(**renewable) - renew_cluster = RenewableCluster(self.renewable_service, area, name, renew_props) - renewables.update({id_renew: renew_cluster}) + for thermal in thermals: + dict_thermals.update({thermal.id: thermal}) - for storage in json_st_storage: - id_storage = storage.pop("id") - name = storage.pop("name") - - storage_props = STStorageProperties(**storage) - st_storage_cl = STStorage(self.storage_service, area, name, storage_props) - st_storage.update({id_storage: st_storage_cl}) + for st_storage in st_storages: + dict_st_storage.update({st_storage.id: st_storage}) area_obj = Area( area, @@ -606,9 +593,9 @@ def read_areas(self) -> List[Area]: self.storage_service, self.thermal_service, self.renewable_service, - renewables=renewables, - thermals=thermals, - st_storages=st_storage, + renewables=dict_renewables, + thermals=dict_thermals, + st_storages=dict_st_storage, properties=json_properties, ui=ui_response, ) diff --git a/src/antares/service/api_services/renewable_api.py b/src/antares/service/api_services/renewable_api.py index 199aba5c..46e37807 100644 --- a/src/antares/service/api_services/renewable_api.py +++ b/src/antares/service/api_services/renewable_api.py @@ -11,6 +11,7 @@ # This file is part of the Antares project. from pathlib import PurePosixPath +from typing import List import pandas as pd @@ -63,3 +64,21 @@ def get_renewable_matrix(self, renewable: RenewableCluster) -> pd.DataFrame: return get_matrix(f"{self._base_url}/studies/{self.study_id}/raw?path={path}", self._wrapper) except APIError as e: raise RenewableMatrixDownloadError(renewable.area_id, renewable.name, e.message) from e + + def read_renewables( + self, + area_id: str, + ) -> List[RenewableCluster]: + json_renewables = self._wrapper.get(area_id + "clusters/renewable").json() + + renewables = [] + + for renewable in json_renewables: + renewable_id = renewable.pop("id") + renewable_name = renewable.pop("name") + + renewable_props = RenewableClusterProperties(**renewable) + renewable_cluster = RenewableCluster(self.config, renewable_id, renewable_name, renewable_props) + renewables.append(renewable_cluster) + + return renewables diff --git a/src/antares/service/api_services/st_storage_api.py b/src/antares/service/api_services/st_storage_api.py index 0879daf6..b3a270cf 100644 --- a/src/antares/service/api_services/st_storage_api.py +++ b/src/antares/service/api_services/st_storage_api.py @@ -9,6 +9,7 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. +from typing import List import pandas as pd @@ -73,3 +74,18 @@ def get_storage_matrix(self, storage: STStorage, ts_name: STStorageMatrixName) - except APIError as e: raise STStorageMatrixDownloadError(storage.area_id, storage.id, ts_name.value, e.message) from e return dataframe + + def read_st_storages(self, area_id: str) -> List[STStorage]: + json_storage = self._wrapper.get(area_id + "storages").json() + print(json_storage) + storages = [] + + for storage in json_storage: + storage_id = storage.pop("id") + storage_name = storage.pop("name") + + storage_properties = STStorageProperties(**storage) + st_storage = STStorage(self.config, storage_id, storage_name, storage_properties) + storages.append(st_storage) + + return storages diff --git a/src/antares/service/api_services/thermal_api.py b/src/antares/service/api_services/thermal_api.py index 674e791a..0433ea5a 100644 --- a/src/antares/service/api_services/thermal_api.py +++ b/src/antares/service/api_services/thermal_api.py @@ -11,6 +11,7 @@ # This file is part of the Antares project. from pathlib import PurePosixPath +from typing import List import pandas as pd @@ -66,3 +67,21 @@ def get_thermal_matrix(self, thermal_cluster: ThermalCluster, ts_name: ThermalCl raise ThermalMatrixDownloadError( thermal_cluster.area_id, thermal_cluster.name, ts_name.value, e.message ) from e + + def read_thermal_clusters( + self, + area_id: str, + ) -> List[ThermalCluster]: + json_thermal = self._wrapper.get(area_id + "clusters/thermal").json() + + thermals = [] + + for thermal in json_thermal: + thermal_id = thermal.pop("id") + thermal_name = thermal.pop("name") + + thermal_props = ThermalClusterProperties(**thermal) + thermal_cluster = ThermalCluster(self, thermal_id, thermal_name, thermal_props) + thermals.append(thermal_cluster) + + return thermals diff --git a/src/antares/service/base_services.py b/src/antares/service/base_services.py index a700144f..7acdc134 100644 --- a/src/antares/service/base_services.py +++ b/src/antares/service/base_services.py @@ -360,6 +360,10 @@ def get_thermal_matrix(self, thermal_cluster: ThermalCluster, ts_name: ThermalCl """ pass + @abstractmethod + def read_thermal_clusters(self, area_id: str) -> List[ThermalCluster]: + pass + class BaseBindingConstraintService(ABC): binding_constraints: dict[str, BindingConstraint] @@ -505,6 +509,10 @@ def get_renewable_matrix( """ pass + @abstractmethod + def read_renewables(self, area_id: str) -> List[RenewableCluster]: + pass + class BaseShortTermStorageService(ABC): @abstractmethod @@ -517,3 +525,7 @@ def update_st_storage_properties( properties: new properties. Only registered fields will be updated. """ pass + + @abstractmethod + def read_st_storages(self, area_id: str) -> List[STStorage]: + pass diff --git a/src/antares/service/local_services/renewable_local.py b/src/antares/service/local_services/renewable_local.py index 07ad1b05..4e64a4c2 100644 --- a/src/antares/service/local_services/renewable_local.py +++ b/src/antares/service/local_services/renewable_local.py @@ -10,7 +10,7 @@ # # This file is part of the Antares project. -from typing import Any +from typing import Any, List import pandas as pd @@ -35,3 +35,6 @@ def get_renewable_matrix( renewable: RenewableCluster, ) -> pd.DataFrame: raise NotImplementedError + + def read_renewables(self, area_id: str) -> List[RenewableCluster]: + raise NotImplementedError diff --git a/src/antares/service/local_services/st_storage_local.py b/src/antares/service/local_services/st_storage_local.py index be7c4da6..8738eb63 100644 --- a/src/antares/service/local_services/st_storage_local.py +++ b/src/antares/service/local_services/st_storage_local.py @@ -10,7 +10,7 @@ # # This file is part of the Antares project. -from typing import Any +from typing import Any, List from antares.config.local_configuration import LocalConfiguration from antares.model.st_storage import STStorage, STStorageProperties @@ -27,3 +27,6 @@ def update_st_storage_properties( self, st_storage: STStorage, properties: STStorageProperties ) -> STStorageProperties: raise NotImplementedError + + def read_st_storages(self, area_id: str) -> List[STStorage]: + raise NotImplementedError diff --git a/src/antares/service/local_services/thermal_local.py b/src/antares/service/local_services/thermal_local.py index f9b46627..d1ce96f7 100644 --- a/src/antares/service/local_services/thermal_local.py +++ b/src/antares/service/local_services/thermal_local.py @@ -10,7 +10,7 @@ # # This file is part of the Antares project. -from typing import Any +from typing import Any, List import pandas as pd @@ -32,3 +32,6 @@ def update_thermal_properties( def get_thermal_matrix(self, thermal_cluster: ThermalCluster, ts_name: ThermalClusterMatrixName) -> pd.DataFrame: raise NotImplementedError + + def read_thermal_clusters(self, area_id: str) -> List[ThermalCluster]: + raise NotImplementedError diff --git a/tests/antares/services/api_services/test_area_api.py b/tests/antares/services/api_services/test_area_api.py index 7eaba8e6..11c51e41 100644 --- a/tests/antares/services/api_services/test_area_api.py +++ b/tests/antares/services/api_services/test_area_api.py @@ -433,7 +433,6 @@ def test_read_areas(self): area_api = AreaApiService(self.api, study_id_test) actual_area_list = area_api.read_areas() area_ui = area_api.craft_ui(url + "?type=AREA&ui=true", "zone") - thermal_id = json_thermal[0].pop("id") thermal_name = json_thermal[0].pop("name") renewable_id = json_renewable[0].pop("id") diff --git a/tests/antares/services/api_services/test_renewable_api.py b/tests/antares/services/api_services/test_renewable_api.py index 28d86720..6fbdcff0 100644 --- a/tests/antares/services/api_services/test_renewable_api.py +++ b/tests/antares/services/api_services/test_renewable_api.py @@ -19,6 +19,8 @@ from antares.exceptions.exceptions import RenewableMatrixDownloadError, RenewablePropertiesUpdateError from antares.model.area import Area from antares.model.renewable import RenewableCluster, RenewableClusterProperties +from antares.service.api_services.area_api import AreaApiService +from antares.service.api_services.renewable_api import RenewableApiService from antares.service.service_factory import ServiceFactory @@ -86,3 +88,44 @@ def test_get_renewable_matrices_fails(self): f": {self.antares_web_description_msg}", ): self.renewable.get_renewable_matrix() + + def test_read_renewables(self): + json_renewable = [ + { + "id": "test_renouvelable", + "group": "Solar Thermal", + "name": "test_renouvelable", + "enabled": "true", + "unitCount": 1, + "nominalCapacity": 0, + "tsInterpretation": "power-generation", + } + ] + + study_id_test = "248bbb99-c909-47b7-b239-01f6f6ae7de7" + area_id = "zone" + url = f"https://antares-web-recette.rte-france.com/api/v1/studies/{study_id_test}/areas/{area_id}/" + + with requests_mock.Mocker() as mocker: + mocker.get(url + "clusters/renewable", json=json_renewable) + area_api = AreaApiService(self.api, study_id_test) + renewable_api = RenewableApiService(self.api, study_id_test) + + actual_renewable_list = renewable_api.read_renewables(url) + + renewable_id = json_renewable[0].pop("id") + renewable_name = json_renewable[0].pop("name") + + renewable_props = RenewableClusterProperties(**json_renewable[0]) + renewable = RenewableCluster(area_api.renewable_service, renewable_id, renewable_name, renewable_props) + + expected_renewable_list = [renewable] + + assert len(actual_renewable_list) == 1 + assert len(actual_renewable_list) == len(expected_renewable_list) + + expected_renewable = expected_renewable_list[0] + actual_renewable = actual_renewable_list[0] + + assert expected_renewable.id == actual_renewable.id + assert expected_renewable.name == actual_renewable.name diff --git a/tests/antares/services/api_services/test_st_storage_api.py b/tests/antares/services/api_services/test_st_storage_api.py index e665b12a..774c99be 100644 --- a/tests/antares/services/api_services/test_st_storage_api.py +++ b/tests/antares/services/api_services/test_st_storage_api.py @@ -23,6 +23,8 @@ ) from antares.model.area import Area from antares.model.st_storage import STStorage, STStorageProperties +from antares.service.api_services.area_api import AreaApiService +from antares.service.api_services.st_storage_api import ShortTermStorageApiService from antares.service.service_factory import ServiceFactory @@ -113,3 +115,46 @@ def test_upload_storage_matrix_fails(self): f" {self.antares_web_description_msg}", ): self.storage.upload_storage_inflows(self.matrix) + + def test_read_st_storages(self): + json_storage = [ + { + "id": "test_storage", + "group": "Pondage", + "name": "test_storage", + "injectionNominalCapacity": 0, + "withdrawalNominalCapacity": 0, + "reservoirCapacity": 0, + "efficiency": 1, + "initialLevel": 0.5, + "initialLevelOptim": "false", + "enabled": "true", + } + ] + study_id_test = "248bbb99-c909-47b7-b239-01f6f6ae7de7" + area_id = "zone" + url = f"https://antares-web-recette.rte-france.com/api/v1/studies/{study_id_test}/areas/{area_id}/" + + with requests_mock.Mocker() as mocker: + mocker.get(url + "storages", json=json_storage) + area_api = AreaApiService(self.api, study_id_test) + storage_api = ShortTermStorageApiService(self.api, self.study_id) + + actual_storage_list = storage_api.read_st_storages(url) + + storage_id = json_storage[0].pop("id") + storage_name = json_storage[0].pop("name") + + storage_props = STStorageProperties(**json_storage[0]) + st_storage = STStorage(area_api.storage_service, storage_id, storage_name, storage_props) + + expected_storage_list = [st_storage] + + assert len(actual_storage_list) == 1 + assert len(actual_storage_list) == len(expected_storage_list) + + expected_st_storage = expected_storage_list[0] + actual_st_storage = actual_storage_list[0] + + assert expected_st_storage.id == actual_st_storage.id + assert expected_st_storage.name == actual_st_storage.name diff --git a/tests/antares/services/api_services/test_thermal_api.py b/tests/antares/services/api_services/test_thermal_api.py index 22ab05b3..33c13d5f 100644 --- a/tests/antares/services/api_services/test_thermal_api.py +++ b/tests/antares/services/api_services/test_thermal_api.py @@ -20,6 +20,8 @@ from antares.model.area import Area from antares.model.study import Study from antares.model.thermal import ThermalCluster, ThermalClusterMatrixName, ThermalClusterProperties +from antares.service.api_services.area_api import AreaApiService +from antares.service.api_services.thermal_api import ThermalApiService from antares.service.service_factory import ServiceFactory @@ -104,3 +106,73 @@ def test_get_thermal_matrices_fails(self, thermal_matrix_set): f" inside area {self.area.id}: {self.antares_web_description_msg}", ): getattr(self.thermal, matrix_method)() + + def test_read_thermals(self): + json_thermal = [ + { + "id": "therm_un", + "group": "Gas", + "name": "therm_un", + "enabled": "true", + "unitCount": 1, + "nominalCapacity": 0, + "genTs": "use global", + "minStablePower": 0, + "minUpTime": 1, + "minDownTime": 1, + "mustRun": "false", + "spinning": 0, + "volatilityForced": 0, + "volatilityPlanned": 0, + "lawForced": "uniform", + "lawPlanned": "uniform", + "marginalCost": 0, + "spreadCost": 0, + "fixedCost": 0, + "startupCost": 0, + "marketBidCost": 0, + "co2": 0, + "nh3": 0, + "so2": 0, + "nox": 0, + "pm25": 0, + "pm5": 0, + "pm10": 0, + "nmvoc": 0, + "op1": 0, + "op2": 0, + "op3": 0, + "op4": 0, + "op5": 0, + "costGeneration": "SetManually", + "efficiency": 100, + "variableOMCost": 0, + } + ] + study_id_test = "248bbb99-c909-47b7-b239-01f6f6ae7de7" + area_id = "zone" + url = f"https://antares-web-recette.rte-france.com/api/v1/studies/{study_id_test}/areas/{area_id}/" + + with requests_mock.Mocker() as mocker: + mocker.get(url + "clusters/thermal", json=json_thermal) + area_api = AreaApiService(self.api, study_id_test) + thermal_api = ThermalApiService(self.api, study_id_test) + + actual_thermal_list = thermal_api.read_thermal_clusters(url) + + thermal_id = json_thermal[0].pop("id") + thermal_name = json_thermal[0].pop("name") + + thermal_props = ThermalClusterProperties(**json_thermal[0]) + thermal = ThermalCluster(area_api.thermal_service, thermal_id, thermal_name, thermal_props) + + expected_thermal_list = [thermal] + + assert len(actual_thermal_list) == 1 + assert len(actual_thermal_list) == len(expected_thermal_list) + + expected_thermal = expected_thermal_list[0] + actual_thermal = actual_thermal_list[0] + + assert expected_thermal.id == actual_thermal.id + assert expected_thermal.name == actual_thermal.name From d31bba13b9095c28c18408ddaf6002f13b9af9a6 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Tue, 26 Nov 2024 15:16:03 +0100 Subject: [PATCH 02/33] added read_thermal_clusters/storages/renewables methods and realized unit testing --- tests/antares/services/api_services/test_area_api.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/antares/services/api_services/test_area_api.py b/tests/antares/services/api_services/test_area_api.py index 11c51e41..ca59e4b0 100644 --- a/tests/antares/services/api_services/test_area_api.py +++ b/tests/antares/services/api_services/test_area_api.py @@ -32,6 +32,7 @@ from antares.model.st_storage import STStorage, STStorageProperties from antares.model.thermal import ThermalCluster, ThermalClusterProperties from antares.service.api_services.area_api import AreaApiService +from antares.service.api_services.renewable_api import RenewableApiService from antares.service.service_factory import ServiceFactory @@ -431,6 +432,9 @@ def test_read_areas(self): mocker.get(url_properties_form, json=json_properties) area_api = AreaApiService(self.api, study_id_test) + area_api.thermal_service = ServiceFactory(self.api, study_id_test).create_thermal_service() + area_api.renewable_service = ServiceFactory(self.api, study_id_test).create_renewable_service() + area_api.storage_service = ServiceFactory(self.api, study_id_test).create_st_storage_service() actual_area_list = area_api.read_areas() area_ui = area_api.craft_ui(url + "?type=AREA&ui=true", "zone") thermal_id = json_thermal[0].pop("id") From e3c50307c921f3b850ab886ec79d7c16556dac5e Mon Sep 17 00:00:00 2001 From: wahadameh Date: Wed, 27 Nov 2024 10:45:49 +0100 Subject: [PATCH 03/33] added read_thermal_clusters/storages/renewables methods and realized unit testing --- src/antares/service/api_services/area_api.py | 14 ++----- .../service/api_services/st_storage_api.py | 1 - .../services/api_services/test_area_api.py | 31 -------------- .../api_services/test_renewable_api.py | 9 +---- .../api_services/test_st_storage_api.py | 9 +---- .../services/api_services/test_thermal_api.py | 40 +------------------ 6 files changed, 9 insertions(+), 95 deletions(-) diff --git a/src/antares/service/api_services/area_api.py b/src/antares/service/api_services/area_api.py index 059308b4..12a92b8d 100644 --- a/src/antares/service/api_services/area_api.py +++ b/src/antares/service/api_services/area_api.py @@ -561,9 +561,6 @@ def read_areas(self) -> List[Area]: json_resp = self._wrapper.get(base_api_url + "?" + ui_url).json() for area in json_resp: - dict_renewables = dict() - dict_thermals = dict() - dict_st_storage = dict() area_url = base_api_url + "/" + f"{area}/" json_properties = self._wrapper.get(area_url + url_properties_form).json() @@ -578,14 +575,9 @@ def read_areas(self) -> List[Area]: thermals = self.thermal_service.read_thermal_clusters(area_url) st_storages = self.storage_service.read_st_storages(area_url) - for renewable in renewables: - dict_renewables.update({renewable.id: renewable}) - - for thermal in thermals: - dict_thermals.update({thermal.id: thermal}) - - for st_storage in st_storages: - dict_st_storage.update({st_storage.id: st_storage}) + dict_renewables = {renewable.id: renewable for renewable in renewables} + dict_thermals = {thermal.id: thermal for thermal in thermals} + dict_st_storage = {storage.id: storage for storage in st_storages} area_obj = Area( area, diff --git a/src/antares/service/api_services/st_storage_api.py b/src/antares/service/api_services/st_storage_api.py index b3a270cf..7ff97123 100644 --- a/src/antares/service/api_services/st_storage_api.py +++ b/src/antares/service/api_services/st_storage_api.py @@ -77,7 +77,6 @@ def get_storage_matrix(self, storage: STStorage, ts_name: STStorageMatrixName) - def read_st_storages(self, area_id: str) -> List[STStorage]: json_storage = self._wrapper.get(area_id + "storages").json() - print(json_storage) storages = [] for storage in json_storage: diff --git a/tests/antares/services/api_services/test_area_api.py b/tests/antares/services/api_services/test_area_api.py index ca59e4b0..05823eb1 100644 --- a/tests/antares/services/api_services/test_area_api.py +++ b/tests/antares/services/api_services/test_area_api.py @@ -355,37 +355,6 @@ def test_read_areas(self): "enabled": "true", "unitCount": 1, "nominalCapacity": 0, - "genTs": "use global", - "minStablePower": 0, - "minUpTime": 1, - "minDownTime": 1, - "mustRun": "false", - "spinning": 0, - "volatilityForced": 0, - "volatilityPlanned": 0, - "lawForced": "uniform", - "lawPlanned": "uniform", - "marginalCost": 0, - "spreadCost": 0, - "fixedCost": 0, - "startupCost": 0, - "marketBidCost": 0, - "co2": 0, - "nh3": 0, - "so2": 0, - "nox": 0, - "pm25": 0, - "pm5": 0, - "pm10": 0, - "nmvoc": 0, - "op1": 0, - "op2": 0, - "op3": 0, - "op4": 0, - "op5": 0, - "costGeneration": "SetManually", - "efficiency": 100, - "variableOMCost": 0, } ] json_renewable = [ diff --git a/tests/antares/services/api_services/test_renewable_api.py b/tests/antares/services/api_services/test_renewable_api.py index 6fbdcff0..6796ff17 100644 --- a/tests/antares/services/api_services/test_renewable_api.py +++ b/tests/antares/services/api_services/test_renewable_api.py @@ -104,7 +104,7 @@ def test_read_renewables(self): study_id_test = "248bbb99-c909-47b7-b239-01f6f6ae7de7" area_id = "zone" - url = f"https://antares-web-recette.rte-france.com/api/v1/studies/{study_id_test}/areas/{area_id}/" + url = f"https://antares.com/api/v1/studies/{study_id_test}/areas/{area_id}/" with requests_mock.Mocker() as mocker: mocker.get(url + "clusters/renewable", json=json_renewable) @@ -117,14 +117,9 @@ def test_read_renewables(self): renewable_name = json_renewable[0].pop("name") renewable_props = RenewableClusterProperties(**json_renewable[0]) - renewable = RenewableCluster(area_api.renewable_service, renewable_id, renewable_name, renewable_props) - - expected_renewable_list = [renewable] + expected_renewable = RenewableCluster(area_api.renewable_service, renewable_id, renewable_name, renewable_props) assert len(actual_renewable_list) == 1 - assert len(actual_renewable_list) == len(expected_renewable_list) - - expected_renewable = expected_renewable_list[0] actual_renewable = actual_renewable_list[0] assert expected_renewable.id == actual_renewable.id diff --git a/tests/antares/services/api_services/test_st_storage_api.py b/tests/antares/services/api_services/test_st_storage_api.py index 774c99be..1e9674c2 100644 --- a/tests/antares/services/api_services/test_st_storage_api.py +++ b/tests/antares/services/api_services/test_st_storage_api.py @@ -133,7 +133,7 @@ def test_read_st_storages(self): ] study_id_test = "248bbb99-c909-47b7-b239-01f6f6ae7de7" area_id = "zone" - url = f"https://antares-web-recette.rte-france.com/api/v1/studies/{study_id_test}/areas/{area_id}/" + url = f"https://antares.com/api/v1/studies/{study_id_test}/areas/{area_id}/" with requests_mock.Mocker() as mocker: mocker.get(url + "storages", json=json_storage) @@ -146,14 +146,9 @@ def test_read_st_storages(self): storage_name = json_storage[0].pop("name") storage_props = STStorageProperties(**json_storage[0]) - st_storage = STStorage(area_api.storage_service, storage_id, storage_name, storage_props) - - expected_storage_list = [st_storage] + expected_st_storage = STStorage(area_api.storage_service, storage_id, storage_name, storage_props) assert len(actual_storage_list) == 1 - assert len(actual_storage_list) == len(expected_storage_list) - - expected_st_storage = expected_storage_list[0] actual_st_storage = actual_storage_list[0] assert expected_st_storage.id == actual_st_storage.id diff --git a/tests/antares/services/api_services/test_thermal_api.py b/tests/antares/services/api_services/test_thermal_api.py index 33c13d5f..cbe9599c 100644 --- a/tests/antares/services/api_services/test_thermal_api.py +++ b/tests/antares/services/api_services/test_thermal_api.py @@ -116,42 +116,11 @@ def test_read_thermals(self): "enabled": "true", "unitCount": 1, "nominalCapacity": 0, - "genTs": "use global", - "minStablePower": 0, - "minUpTime": 1, - "minDownTime": 1, - "mustRun": "false", - "spinning": 0, - "volatilityForced": 0, - "volatilityPlanned": 0, - "lawForced": "uniform", - "lawPlanned": "uniform", - "marginalCost": 0, - "spreadCost": 0, - "fixedCost": 0, - "startupCost": 0, - "marketBidCost": 0, - "co2": 0, - "nh3": 0, - "so2": 0, - "nox": 0, - "pm25": 0, - "pm5": 0, - "pm10": 0, - "nmvoc": 0, - "op1": 0, - "op2": 0, - "op3": 0, - "op4": 0, - "op5": 0, - "costGeneration": "SetManually", - "efficiency": 100, - "variableOMCost": 0, } ] study_id_test = "248bbb99-c909-47b7-b239-01f6f6ae7de7" area_id = "zone" - url = f"https://antares-web-recette.rte-france.com/api/v1/studies/{study_id_test}/areas/{area_id}/" + url = f"https://antares.com/api/v1/studies/{study_id_test}/areas/{area_id}/" with requests_mock.Mocker() as mocker: mocker.get(url + "clusters/thermal", json=json_thermal) @@ -164,14 +133,9 @@ def test_read_thermals(self): thermal_name = json_thermal[0].pop("name") thermal_props = ThermalClusterProperties(**json_thermal[0]) - thermal = ThermalCluster(area_api.thermal_service, thermal_id, thermal_name, thermal_props) - - expected_thermal_list = [thermal] + expected_thermal = ThermalCluster(area_api.thermal_service, thermal_id, thermal_name, thermal_props) assert len(actual_thermal_list) == 1 - assert len(actual_thermal_list) == len(expected_thermal_list) - - expected_thermal = expected_thermal_list[0] actual_thermal = actual_thermal_list[0] assert expected_thermal.id == actual_thermal.id From e06f11881df2f19ec8c58bb61c528f25900f702d Mon Sep 17 00:00:00 2001 From: wahadameh Date: Wed, 27 Nov 2024 10:47:08 +0100 Subject: [PATCH 04/33] added read_thermal_clusters/storages/renewables methods and realized unit testing --- src/antares/service/api_services/area_api.py | 1 - tests/antares/services/api_services/test_area_api.py | 2 -- tests/antares/services/api_services/test_renewable_api.py | 4 +++- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/antares/service/api_services/area_api.py b/src/antares/service/api_services/area_api.py index 12a92b8d..bd5628f9 100644 --- a/src/antares/service/api_services/area_api.py +++ b/src/antares/service/api_services/area_api.py @@ -561,7 +561,6 @@ def read_areas(self) -> List[Area]: json_resp = self._wrapper.get(base_api_url + "?" + ui_url).json() for area in json_resp: - area_url = base_api_url + "/" + f"{area}/" json_properties = self._wrapper.get(area_url + url_properties_form).json() diff --git a/tests/antares/services/api_services/test_area_api.py b/tests/antares/services/api_services/test_area_api.py index 05823eb1..58752724 100644 --- a/tests/antares/services/api_services/test_area_api.py +++ b/tests/antares/services/api_services/test_area_api.py @@ -32,7 +32,6 @@ from antares.model.st_storage import STStorage, STStorageProperties from antares.model.thermal import ThermalCluster, ThermalClusterProperties from antares.service.api_services.area_api import AreaApiService -from antares.service.api_services.renewable_api import RenewableApiService from antares.service.service_factory import ServiceFactory @@ -437,7 +436,6 @@ def test_read_areas(self): assert len(actual_area_list) == 1 actual_area = actual_area_list[0] - # assert actual_area == expected_area by performing various tests assert actual_area.id == expected_area.id assert actual_area.name == expected_area.name actual_thermals = actual_area.get_thermals() diff --git a/tests/antares/services/api_services/test_renewable_api.py b/tests/antares/services/api_services/test_renewable_api.py index 6796ff17..5a38721a 100644 --- a/tests/antares/services/api_services/test_renewable_api.py +++ b/tests/antares/services/api_services/test_renewable_api.py @@ -117,7 +117,9 @@ def test_read_renewables(self): renewable_name = json_renewable[0].pop("name") renewable_props = RenewableClusterProperties(**json_renewable[0]) - expected_renewable = RenewableCluster(area_api.renewable_service, renewable_id, renewable_name, renewable_props) + expected_renewable = RenewableCluster( + area_api.renewable_service, renewable_id, renewable_name, renewable_props + ) assert len(actual_renewable_list) == 1 actual_renewable = actual_renewable_list[0] From d581ddb96e86b334d3c9a446d9cba7b4df6f39a7 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Wed, 27 Nov 2024 14:42:29 +0100 Subject: [PATCH 05/33] added read_thermal_clusters/storages/renewables methods and realized unit testing --- src/antares/service/api_services/area_api.py | 6 +++--- src/antares/service/api_services/renewable_api.py | 2 +- src/antares/service/api_services/st_storage_api.py | 2 +- src/antares/service/api_services/thermal_api.py | 2 +- tests/antares/services/api_services/test_renewable_api.py | 2 +- tests/antares/services/api_services/test_st_storage_api.py | 4 ++-- tests/antares/services/api_services/test_thermal_api.py | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/antares/service/api_services/area_api.py b/src/antares/service/api_services/area_api.py index bd5628f9..a3644e96 100644 --- a/src/antares/service/api_services/area_api.py +++ b/src/antares/service/api_services/area_api.py @@ -570,9 +570,9 @@ def read_areas(self) -> List[Area]: assert self.thermal_service is not None assert self.storage_service is not None - renewables = self.renewable_service.read_renewables(area_url) - thermals = self.thermal_service.read_thermal_clusters(area_url) - st_storages = self.storage_service.read_st_storages(area_url) + renewables = self.renewable_service.read_renewables(area) + thermals = self.thermal_service.read_thermal_clusters(area) + st_storages = self.storage_service.read_st_storages(area) dict_renewables = {renewable.id: renewable for renewable in renewables} dict_thermals = {thermal.id: thermal for thermal in thermals} diff --git a/src/antares/service/api_services/renewable_api.py b/src/antares/service/api_services/renewable_api.py index 46e37807..c591d588 100644 --- a/src/antares/service/api_services/renewable_api.py +++ b/src/antares/service/api_services/renewable_api.py @@ -69,7 +69,7 @@ def read_renewables( self, area_id: str, ) -> List[RenewableCluster]: - json_renewables = self._wrapper.get(area_id + "clusters/renewable").json() + json_renewables = self._wrapper.get(self._base_url + "/studies/" + self.study_id + "/areas/" + area_id + "/clusters/renewable").json() renewables = [] diff --git a/src/antares/service/api_services/st_storage_api.py b/src/antares/service/api_services/st_storage_api.py index 7ff97123..183ddc3f 100644 --- a/src/antares/service/api_services/st_storage_api.py +++ b/src/antares/service/api_services/st_storage_api.py @@ -76,7 +76,7 @@ def get_storage_matrix(self, storage: STStorage, ts_name: STStorageMatrixName) - return dataframe def read_st_storages(self, area_id: str) -> List[STStorage]: - json_storage = self._wrapper.get(area_id + "storages").json() + json_storage = self._wrapper.get(self._base_url + "/studies/" + self.study_id + "/areas/" + area_id + "/storages").json() storages = [] for storage in json_storage: diff --git a/src/antares/service/api_services/thermal_api.py b/src/antares/service/api_services/thermal_api.py index 0433ea5a..d7c5a264 100644 --- a/src/antares/service/api_services/thermal_api.py +++ b/src/antares/service/api_services/thermal_api.py @@ -72,7 +72,7 @@ def read_thermal_clusters( self, area_id: str, ) -> List[ThermalCluster]: - json_thermal = self._wrapper.get(area_id + "clusters/thermal").json() + json_thermal = self._wrapper.get(self._base_url + "/studies/" + self.study_id + "/areas/" + area_id + "/clusters/thermal").json() thermals = [] diff --git a/tests/antares/services/api_services/test_renewable_api.py b/tests/antares/services/api_services/test_renewable_api.py index 5a38721a..915be408 100644 --- a/tests/antares/services/api_services/test_renewable_api.py +++ b/tests/antares/services/api_services/test_renewable_api.py @@ -111,7 +111,7 @@ def test_read_renewables(self): area_api = AreaApiService(self.api, study_id_test) renewable_api = RenewableApiService(self.api, study_id_test) - actual_renewable_list = renewable_api.read_renewables(url) + actual_renewable_list = renewable_api.read_renewables(area_id) renewable_id = json_renewable[0].pop("id") renewable_name = json_renewable[0].pop("name") diff --git a/tests/antares/services/api_services/test_st_storage_api.py b/tests/antares/services/api_services/test_st_storage_api.py index 1e9674c2..d8f5cfa4 100644 --- a/tests/antares/services/api_services/test_st_storage_api.py +++ b/tests/antares/services/api_services/test_st_storage_api.py @@ -138,9 +138,9 @@ def test_read_st_storages(self): with requests_mock.Mocker() as mocker: mocker.get(url + "storages", json=json_storage) area_api = AreaApiService(self.api, study_id_test) - storage_api = ShortTermStorageApiService(self.api, self.study_id) + storage_api = ShortTermStorageApiService(self.api, study_id_test) - actual_storage_list = storage_api.read_st_storages(url) + actual_storage_list = storage_api.read_st_storages(area_id) storage_id = json_storage[0].pop("id") storage_name = json_storage[0].pop("name") diff --git a/tests/antares/services/api_services/test_thermal_api.py b/tests/antares/services/api_services/test_thermal_api.py index cbe9599c..b28bd33d 100644 --- a/tests/antares/services/api_services/test_thermal_api.py +++ b/tests/antares/services/api_services/test_thermal_api.py @@ -127,7 +127,7 @@ def test_read_thermals(self): area_api = AreaApiService(self.api, study_id_test) thermal_api = ThermalApiService(self.api, study_id_test) - actual_thermal_list = thermal_api.read_thermal_clusters(url) + actual_thermal_list = thermal_api.read_thermal_clusters(area_id) thermal_id = json_thermal[0].pop("id") thermal_name = json_thermal[0].pop("name") From 2ab7da7ff284dc5fba13f957fd76b2d6df4a726c Mon Sep 17 00:00:00 2001 From: wahadameh Date: Wed, 27 Nov 2024 14:44:20 +0100 Subject: [PATCH 06/33] added read_thermal_clusters/storages/renewables methods and realized unit testing --- src/antares/service/api_services/renewable_api.py | 4 +++- src/antares/service/api_services/st_storage_api.py | 4 +++- src/antares/service/api_services/thermal_api.py | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/antares/service/api_services/renewable_api.py b/src/antares/service/api_services/renewable_api.py index c591d588..61a794c5 100644 --- a/src/antares/service/api_services/renewable_api.py +++ b/src/antares/service/api_services/renewable_api.py @@ -69,7 +69,9 @@ def read_renewables( self, area_id: str, ) -> List[RenewableCluster]: - json_renewables = self._wrapper.get(self._base_url + "/studies/" + self.study_id + "/areas/" + area_id + "/clusters/renewable").json() + json_renewables = self._wrapper.get( + self._base_url + "/studies/" + self.study_id + "/areas/" + area_id + "/clusters/renewable" + ).json() renewables = [] diff --git a/src/antares/service/api_services/st_storage_api.py b/src/antares/service/api_services/st_storage_api.py index 183ddc3f..3d5e16c3 100644 --- a/src/antares/service/api_services/st_storage_api.py +++ b/src/antares/service/api_services/st_storage_api.py @@ -76,7 +76,9 @@ def get_storage_matrix(self, storage: STStorage, ts_name: STStorageMatrixName) - return dataframe def read_st_storages(self, area_id: str) -> List[STStorage]: - json_storage = self._wrapper.get(self._base_url + "/studies/" + self.study_id + "/areas/" + area_id + "/storages").json() + json_storage = self._wrapper.get( + self._base_url + "/studies/" + self.study_id + "/areas/" + area_id + "/storages" + ).json() storages = [] for storage in json_storage: diff --git a/src/antares/service/api_services/thermal_api.py b/src/antares/service/api_services/thermal_api.py index d7c5a264..59eeba07 100644 --- a/src/antares/service/api_services/thermal_api.py +++ b/src/antares/service/api_services/thermal_api.py @@ -72,7 +72,9 @@ def read_thermal_clusters( self, area_id: str, ) -> List[ThermalCluster]: - json_thermal = self._wrapper.get(self._base_url + "/studies/" + self.study_id + "/areas/" + area_id + "/clusters/thermal").json() + json_thermal = self._wrapper.get( + self._base_url + "/studies/" + self.study_id + "/areas/" + area_id + "/clusters/thermal" + ).json() thermals = [] From 6906b17825b2d2e5242057210c27ab428e1246f1 Mon Sep 17 00:00:00 2001 From: MartinBelthle Date: Tue, 26 Nov 2024 17:24:18 +0100 Subject: [PATCH 07/33] feat(gh): add release gh action (#14) --- .github/workflows/publish.yml | 42 +++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..75fc1a45 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,42 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Publish Python 🐍 distributions 📦 to PyPI + +on: + release: + types: [ published ] + +permissions: + contents: read + +jobs: + deploy: + + runs-on: ubuntu-22.04 + environment: PyPi + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install build + - name: Build Python 🐍 packages + run: python -m build + - name: Publish distribution 📦 to PyPI + # Upload packages only on a tagged commit + if: startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_TOKEN }} \ No newline at end of file From 603cfe7f6d21a5fe930919df4cb750b2bf42aee9 Mon Sep 17 00:00:00 2001 From: MartinBelthle Date: Tue, 26 Nov 2024 17:26:44 +0100 Subject: [PATCH 08/33] feat(ci): use tox (#12) --- .github/workflows/ci.yml | 53 ++-- .github/workflows/coverage.yml | 48 ++++ .github/workflows/license_header.yml | 25 -- src/antares/model/load.py | 28 +-- src/antares/model/solar.py | 28 +-- src/antares/model/study.py | 4 +- src/antares/model/wind.py | 28 +-- .../service/local_services/area_local.py | 40 ++- .../binding_constraint_local.py | 14 +- src/antares/tools/ini_tool.py | 10 +- src/antares/tools/matrix_tool.py | 10 +- src/antares/tools/prepro_folder.py | 112 +++------ src/antares/tools/time_series_tool.py | 82 +------ .../services/local_services/test_area.py | 229 +++++------------- .../services/local_services/test_study.py | 162 +++---------- tests/antares/tools/conftest.py | 28 --- tests/antares/tools/test_time_series_tool.py | 129 ---------- tox.ini | 9 +- 18 files changed, 250 insertions(+), 789 deletions(-) create mode 100644 .github/workflows/coverage.yml delete mode 100644 .github/workflows/license_header.yml delete mode 100644 tests/antares/tools/conftest.py delete mode 100644 tests/antares/tools/test_time_series_tool.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bb73193c..855f10ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,13 @@ on: jobs: ci: - runs-on: ubuntu-20.04 + runs-on: ${{ matrix.os }} + + strategy: + max-parallel: 9 + matrix: + os: [ windows-latest, ubuntu-20.04, ubuntu-22.04 ] + steps: - name: Checkout uses: actions/checkout@v4 @@ -14,44 +20,17 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: 3.9 - cache: pip - cache-dependency-path: | - requirements.txt - requirements-dev.txt + python-version: 3.11 - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -r requirements.txt -r requirements-dev.txt - - - name: Check formatting - run: ruff format --check src tests - - - name: Check typing - run: | - python -m mypy - - - name: Test - run: | - pytest --cov src --cov-report xml tests/antares + pip install tox~=4.21.2 + pip install tox-uv~=1.11.3 - - name: Archive code coverage results - uses: actions/upload-artifact@v4 - with: - name: python-code-coverage-report - path: coverage.xml + - name: Performs Ubuntu tests + if: matrix.os != 'windows-latest' + run: tox -p - sonarcloud: - runs-on: ubuntu-20.04 - needs: [ci] - steps: - - uses: actions/checkout@v4 - - name: Download python coverage report - uses: actions/download-artifact@v4 - with: - name: python-code-coverage-report - - name: SonarCloud Scan - uses: sonarsource/sonarcloud-github-action@v2.3.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + - name: Performs Windows tests + if: matrix.os == 'windows-latest' + run: tox -e 3.9-test diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 00000000..d8b8fac7 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,48 @@ +name: Coverage +on: + push: + branches: + - "**" + +jobs: + coverage: + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.11 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox~=4.21.2 + pip install tox-uv~=1.11.3 + + - name: Performs coverage + run: tox -e coverage + + - name: Archive code coverage results + uses: actions/upload-artifact@v4 + with: + name: python-code-coverage-report + path: coverage.xml + + sonarcloud: + runs-on: ubuntu-22.04 + needs: [coverage] + + steps: + - uses: actions/checkout@v4 + - name: Download python coverage report + uses: actions/download-artifact@v4 + with: + name: python-code-coverage-report + - name: SonarCloud Scan + uses: sonarsource/sonarcloud-github-action@v2.3.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/license_header.yml b/.github/workflows/license_header.yml deleted file mode 100644 index e1dd3b97..00000000 --- a/.github/workflows/license_header.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: check license headers -on: - push: - branches: - - "**" - -jobs: - check-license-headers: - runs-on: ubuntu-20.04 - steps: - - name: Checkout github repo (+ download lfs dependencies) - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: 3.9 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install click - - name: Check licenses header - run: | - python license_checker_and_adder.py --path=../src/ --action=check-strict - python license_checker_and_adder.py --path=../tests/ --action=check-strict - working-directory: scripts diff --git a/src/antares/model/load.py b/src/antares/model/load.py index 49a681ce..45df850f 100644 --- a/src/antares/model/load.py +++ b/src/antares/model/load.py @@ -9,32 +9,10 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. -from pathlib import Path -from typing import Optional -import pandas as pd -from antares.tools.prepro_folder import PreproFolder -from antares.tools.time_series_tool import TimeSeries, TimeSeriesFile +from antares.tools.time_series_tool import TimeSeries -class Load: - def __init__( - self, - time_series: pd.DataFrame = pd.DataFrame([]), - local_file: Optional[TimeSeriesFile] = None, - study_path: Optional[Path] = None, - area_id: Optional[str] = None, - ) -> None: - self._time_series = TimeSeries(time_series, local_file) - self._prepro = ( - PreproFolder(folder="load", study_path=study_path, area_id=area_id) if study_path and area_id else None - ) - - @property - def time_series(self) -> TimeSeries: - return self._time_series - - @property - def prepro(self) -> Optional[PreproFolder]: - return self._prepro +class Load(TimeSeries): + pass diff --git a/src/antares/model/solar.py b/src/antares/model/solar.py index fece9ea5..57aca133 100644 --- a/src/antares/model/solar.py +++ b/src/antares/model/solar.py @@ -9,32 +9,10 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. -from pathlib import Path -from typing import Optional -import pandas as pd -from antares.tools.prepro_folder import PreproFolder -from antares.tools.time_series_tool import TimeSeries, TimeSeriesFile +from antares.tools.time_series_tool import TimeSeries -class Solar: - def __init__( - self, - time_series: pd.DataFrame = pd.DataFrame([]), - local_file: Optional[TimeSeriesFile] = None, - study_path: Optional[Path] = None, - area_id: Optional[str] = None, - ) -> None: - self._time_series = TimeSeries(time_series, local_file) - self._prepro = ( - PreproFolder(folder="solar", study_path=study_path, area_id=area_id) if study_path and area_id else None - ) - - @property - def time_series(self) -> TimeSeries: - return self._time_series - - @property - def prepro(self) -> Optional[PreproFolder]: - return self._prepro +class Solar(TimeSeries): + pass diff --git a/src/antares/model/study.py b/src/antares/model/study.py index cce4ed40..50d019b4 100644 --- a/src/antares/model/study.py +++ b/src/antares/model/study.py @@ -269,8 +269,8 @@ def delete(self, children: bool = False) -> None: def _verify_study_already_exists(study_directory: Path) -> None: - if os.path.exists(study_directory): - raise FileExistsError(f"Study {study_directory} already exists.") + if study_directory.exists(): + raise FileExistsError(f"Study {study_directory.name} already exists.") def _create_directory_structure(study_path: Path) -> None: diff --git a/src/antares/model/wind.py b/src/antares/model/wind.py index ee02fab6..6ccd6a81 100644 --- a/src/antares/model/wind.py +++ b/src/antares/model/wind.py @@ -9,32 +9,10 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. -from pathlib import Path -from typing import Optional -import pandas as pd -from antares.tools.prepro_folder import PreproFolder -from antares.tools.time_series_tool import TimeSeries, TimeSeriesFile +from antares.tools.time_series_tool import TimeSeries -class Wind: - def __init__( - self, - time_series: pd.DataFrame = pd.DataFrame([]), - local_file: Optional[TimeSeriesFile] = None, - study_path: Optional[Path] = None, - area_id: Optional[str] = None, - ) -> None: - self._time_series = TimeSeries(time_series, local_file) - self._prepro = ( - PreproFolder(folder="wind", study_path=study_path, area_id=area_id) if study_path and area_id else None - ) - - @property - def time_series(self) -> TimeSeries: - return self._time_series - - @property - def prepro(self) -> Optional[PreproFolder]: - return self._prepro +class Wind(TimeSeries): + pass diff --git a/src/antares/service/local_services/area_local.py b/src/antares/service/local_services/area_local.py index cda51143..efcd5a21 100644 --- a/src/antares/service/local_services/area_local.py +++ b/src/antares/service/local_services/area_local.py @@ -37,7 +37,8 @@ BaseThermalService, ) from antares.tools.ini_tool import IniFile, IniFileTypes -from antares.tools.time_series_tool import TimeSeriesFile, TimeSeriesFileType +from antares.tools.prepro_folder import PreproFolder +from antares.tools.time_series_tool import TimeSeriesFileType def _sets_ini_content() -> ConfigParser: @@ -124,10 +125,13 @@ def create_renewable_cluster( def create_load(self, area: Area, series: Optional[pd.DataFrame]) -> Load: series = series if series is not None else pd.DataFrame([]) - local_file = TimeSeriesFile( - TimeSeriesFileType.LOAD, self.config.study_path, area_id=area.id, time_series=series - ) - return Load(time_series=series, local_file=local_file, study_path=self.config.study_path, area_id=area.id) + self._write_timeseries(series, TimeSeriesFileType.LOAD, area.id) + PreproFolder.LOAD.save(self.config.study_path, area.id) + return Load(time_series=series) + + def _write_timeseries(self, series: pd.DataFrame, ts_file_type: TimeSeriesFileType, area_id: str) -> None: + file_path = self.config.study_path.joinpath(ts_file_type.value.format(area_id=area_id)) + series.to_csv(file_path, sep="\t", header=False, index=False, encoding="utf-8") def create_st_storage( self, area_id: str, st_storage_name: str, properties: Optional[STStorageProperties] = None @@ -149,31 +153,25 @@ def create_st_storage( def create_wind(self, area: Area, series: Optional[pd.DataFrame]) -> Wind: series = series if series is not None else pd.DataFrame([]) - local_file = TimeSeriesFile( - TimeSeriesFileType.WIND, self.config.study_path, area_id=area.id, time_series=series - ) - return Wind(time_series=series, local_file=local_file, study_path=self.config.study_path, area_id=area.id) + self._write_timeseries(series, TimeSeriesFileType.WIND, area.id) + PreproFolder.WIND.save(self.config.study_path, area.id) + return Wind(time_series=series) def create_reserves(self, area: Area, series: Optional[pd.DataFrame]) -> Reserves: series = series if series is not None else pd.DataFrame([]) - local_file = TimeSeriesFile( - TimeSeriesFileType.RESERVES, self.config.study_path, area_id=area.id, time_series=series - ) - return Reserves(series, local_file) + self._write_timeseries(series, TimeSeriesFileType.RESERVES, area.id) + return Reserves(series) def create_solar(self, area: Area, series: Optional[pd.DataFrame]) -> Solar: series = series if series is not None else pd.DataFrame([]) - local_file = TimeSeriesFile( - TimeSeriesFileType.SOLAR, self.config.study_path, area_id=area.id, time_series=series - ) - return Solar(time_series=series, local_file=local_file, study_path=self.config.study_path, area_id=area.id) + self._write_timeseries(series, TimeSeriesFileType.SOLAR, area.id) + PreproFolder.SOLAR.save(self.config.study_path, area.id) + return Solar(time_series=series) def create_misc_gen(self, area: Area, series: Optional[pd.DataFrame]) -> MiscGen: series = series if series is not None else pd.DataFrame([]) - local_file = TimeSeriesFile( - TimeSeriesFileType.MISC_GEN, self.config.study_path, area_id=area.id, time_series=series - ) - return MiscGen(series, local_file) + self._write_timeseries(series, TimeSeriesFileType.MISC_GEN, area.id) + return MiscGen(series) def create_hydro( self, diff --git a/src/antares/service/local_services/binding_constraint_local.py b/src/antares/service/local_services/binding_constraint_local.py index e0b99e23..0254b25c 100644 --- a/src/antares/service/local_services/binding_constraint_local.py +++ b/src/antares/service/local_services/binding_constraint_local.py @@ -26,7 +26,8 @@ ) from antares.service.base_services import BaseBindingConstraintService from antares.tools.ini_tool import IniFile, IniFileTypes -from antares.tools.time_series_tool import TimeSeries, TimeSeriesFile, TimeSeriesFileType +from antares.tools.matrix_tool import df_save +from antares.tools.time_series_tool import TimeSeriesFileType class BindingConstraintLocalService(BaseBindingConstraintService): @@ -35,7 +36,6 @@ def __init__(self, config: LocalConfiguration, study_name: str, **kwargs: Any) - self.config = config self.study_name = study_name self.ini_file = IniFile(self.config.study_path, IniFileTypes.BINDING_CONSTRAINTS_INI) - self._time_series: dict[str, TimeSeries] = {} self.binding_constraints = {} def create_binding_constraint( @@ -90,10 +90,8 @@ def _store_time_series( file_types = [TimeSeriesFileType.BINDING_CONSTRAINT_EQUAL] for ts, ts_id, file_type in zip(time_series, time_series_ids, file_types): - self._time_series[ts_id] = TimeSeries( - ts, - TimeSeriesFile(file_type, self.config.study_path, constraint_id=constraint.id.lower(), time_series=ts), - ) + matrix_path = self.config.study_path.joinpath(file_type.value.format(constraint_id=constraint.id)) + df_save(ts, matrix_path) @staticmethod def _check_if_empty_ts(time_step: BindingConstraintFrequency, time_series: Optional[pd.DataFrame]) -> pd.DataFrame: @@ -108,10 +106,6 @@ def _write_binding_constraint_ini(self) -> None: self.ini_file.ini_dict = binding_constraints_ini_content self.ini_file.write_ini_file() - @property - def time_series(self) -> dict[str, TimeSeries]: - return self._time_series - def add_constraint_terms(self, constraint: BindingConstraint, terms: list[ConstraintTerm]) -> list[ConstraintTerm]: new_terms = constraint.local_properties.terms | { term.id: term for term in terms if term.id not in constraint.get_terms() diff --git a/src/antares/tools/ini_tool.py b/src/antares/tools/ini_tool.py index 4fde8689..4f3d418e 100644 --- a/src/antares/tools/ini_tool.py +++ b/src/antares/tools/ini_tool.py @@ -13,7 +13,7 @@ from enum import Enum from pathlib import Path -from typing import Any, Optional, Union, overload +from typing import Any, Optional, Union from pydantic import BaseModel @@ -105,12 +105,6 @@ def ini_path(self) -> Path: """Ini path""" return self._full_path - @overload - def add_section(self, section: Path) -> None: ... - - @overload - def add_section(self, section: dict[str, dict[str, str]]) -> None: ... - def add_section(self, section: Any) -> None: if isinstance(section, dict): self._ini_contents.read_dict(section) @@ -118,7 +112,7 @@ def add_section(self, section: Any) -> None: with section.open() as ini_file: self._ini_contents.read_file(ini_file) else: - raise TypeError("Only dict or Path are allowed") + raise TypeError(f"Only dict or Path are allowed, received {type(section)}") def update_from_ini_file(self) -> None: if not self._full_path.is_file(): diff --git a/src/antares/tools/matrix_tool.py b/src/antares/tools/matrix_tool.py index 897764fe..e4753051 100644 --- a/src/antares/tools/matrix_tool.py +++ b/src/antares/tools/matrix_tool.py @@ -9,7 +9,7 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. - +from pathlib import Path from typing import Dict import pandas as pd @@ -28,3 +28,11 @@ def prepare_args_replace_matrix(series: pd.DataFrame, series_path: str) -> Dict: matrix = series.to_numpy().tolist() body = {"target": series_path, "matrix": matrix} return {"action": "replace_matrix", "args": body} + + +def df_save(df: pd.DataFrame, path: Path) -> None: + df.to_csv(path, sep="\t", header=False, index=False, encoding="utf-8") + + +def df_read(path: Path) -> pd.DataFrame: + return pd.read_csv(path, sep="\t", header=None) diff --git a/src/antares/tools/prepro_folder.py b/src/antares/tools/prepro_folder.py index 79df4d19..4cb48af1 100644 --- a/src/antares/tools/prepro_folder.py +++ b/src/antares/tools/prepro_folder.py @@ -9,88 +9,42 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. - +from enum import Enum from pathlib import Path import numpy as np import pandas as pd from antares.tools.ini_tool import IniFile, IniFileTypes -from antares.tools.time_series_tool import TimeSeries, TimeSeriesFile, TimeSeriesFileType - - -class PreproFolder: - def __init__(self, folder: str, study_path: Path, area_id: str) -> None: - folders = ["load", "solar", "wind"] - if folder not in folders: - raise ValueError(f"Folder must be one of the following: {', '.join(folders[:-1])}, and {folders[-1]}") - if folder == "solar": - settings = IniFileTypes.SOLAR_SETTINGS_INI - conversion = TimeSeriesFileType.SOLAR_CONVERSION - data = TimeSeriesFileType.SOLAR_DATA - k = TimeSeriesFileType.SOLAR_K - translation = TimeSeriesFileType.SOLAR_TRANSLATION - elif folder == "wind": - settings = IniFileTypes.WIND_SETTINGS_INI - conversion = TimeSeriesFileType.WIND_CONVERSION - data = TimeSeriesFileType.WIND_DATA - k = TimeSeriesFileType.WIND_K - translation = TimeSeriesFileType.WIND_TRANSLATION - elif folder == "load": - settings = IniFileTypes.LOAD_SETTINGS_INI - conversion = TimeSeriesFileType.LOAD_CONVERSION - data = TimeSeriesFileType.LOAD_DATA - k = TimeSeriesFileType.LOAD_K - translation = TimeSeriesFileType.LOAD_TRANSLATION - - self._settings = IniFile(study_path, settings, area_id) - self._conversion = TimeSeries( - ConversionFile().data, - TimeSeriesFile(conversion, study_path, area_id=area_id, time_series=ConversionFile().data), - ) - self._data = TimeSeries( - DataFile().data, TimeSeriesFile(data, study_path, area_id=area_id, time_series=DataFile().data) - ) - self._k = TimeSeries( - pd.DataFrame([]), TimeSeriesFile(k, study_path, area_id=area_id, time_series=pd.DataFrame([])) - ) - self._translation = TimeSeries( - pd.DataFrame([]), - TimeSeriesFile(translation, study_path, area_id=area_id, time_series=pd.DataFrame([])), - ) - - @property - def settings(self) -> IniFile: - return self._settings - - @property - def conversion(self) -> TimeSeries: - return self._conversion - - @property - def data(self) -> TimeSeries: - return self._data - - @property - def k(self) -> TimeSeries: - return self._k - - @property - def translation(self) -> TimeSeries: - return self._translation - - -class ConversionFile: - def __init__(self) -> None: - self.data = pd.DataFrame([[-9999999980506447872, 0, 9999999980506447872], [0, 0, 0]]) - - -class DataFile: - def __init__(self) -> None: - default_data = pd.DataFrame(np.ones([12, 6])) - default_data[2] = 0 - self._data = default_data.astype(int) - - @property - def data(self) -> pd.DataFrame: - return self._data +from antares.tools.matrix_tool import df_save +from antares.tools.time_series_tool import TimeSeriesFileType + + +class PreproFolder(Enum): + LOAD = "load" + SOLAR = "solar" + WIND = "wind" + + def save(self, study_path: Path, area_id: str) -> None: + IniFile(study_path, IniFileTypes.__getitem__(f"{self.value.upper()}_SETTINGS_INI"), area_id) + + conversion = TimeSeriesFileType.__getitem__(f"{self.value.upper()}_CONVERSION").value.format(area_id=area_id) + conversion_path = study_path.joinpath(conversion) + conversion_matrix = pd.DataFrame([[-9999999980506447872, 0, 9999999980506447872], [0, 0, 0]]) + df_save(conversion_matrix, conversion_path) + + data = TimeSeriesFileType.__getitem__(f"{self.value.upper()}_DATA").value.format(area_id=area_id) + data_matrix = pd.DataFrame(np.ones([12, 6]), dtype=int) + data_matrix[2] = 0 + data_path = study_path.joinpath(data) + df_save(data_matrix, data_path) + + k = TimeSeriesFileType.__getitem__(f"{self.value.upper()}_K").value.format(area_id=area_id) + k_path = study_path.joinpath(k) + k_matrix = pd.DataFrame([]) + df_save(k_matrix, k_path) + + translation = TimeSeriesFileType.__getitem__(f"{self.value.upper()}_TRANSLATION").value.format(area_id=area_id) + translation_path = study_path.joinpath(translation) + translation_matrix = pd.DataFrame([]) + df_save(translation_matrix, translation_path) diff --git a/src/antares/tools/time_series_tool.py b/src/antares/tools/time_series_tool.py index 74682a67..10dde334 100644 --- a/src/antares/tools/time_series_tool.py +++ b/src/antares/tools/time_series_tool.py @@ -11,8 +11,6 @@ # This file is part of the Antares project. from enum import Enum -from pathlib import Path -from typing import Optional import pandas as pd @@ -52,95 +50,17 @@ class TimeSeriesFileType(Enum): WIND_TRANSLATION = "input/wind/prepro/{area_id}/translation.txt" -class TimeSeriesFile: - """ - Handling time series files reading and writing locally. - - Time series are stored without headers in tab separated files, encoded with UTF-8. - - Args: - ts_file_type: Type of time series file using the class TimeSeriesFileType. - study_path: `Path` to the study directory. - area_id: Area ID for file paths that use the area's id in their path - constraint_id: Constraint ID for file paths that use the binding constraint's id in their path - time_series: The actual timeseries as a pandas DataFrame. - - Raises: - ValueError if the TimeSeriesFileType needs an area_id and none is provided. - """ - - def __init__( - self, - ts_file_type: TimeSeriesFileType, - study_path: Path, - *, - area_id: Optional[str] = None, - constraint_id: Optional[str] = None, - time_series: Optional[pd.DataFrame] = None, - ) -> None: - if "{area_id}" in ts_file_type.value and area_id is None: - raise ValueError("area_id is required for this file type.") - if "{constraint_id}" in ts_file_type.value and constraint_id is None: - raise ValueError("constraint_id is required for this file type.") - - self.file_path = study_path / ( - ts_file_type.value - if not (area_id or constraint_id) - else ts_file_type.value.format(area_id=area_id, constraint_id=constraint_id) - ) - - if self.file_path.is_file() and time_series is not None: - raise ValueError(f"File {self.file_path} already exists and a time series was provided.") - elif self.file_path.is_file() and time_series is None: - self._time_series = pd.read_csv(self.file_path, sep="\t", header=None, index_col=None, encoding="utf-8") - else: - self._time_series = time_series if time_series is not None else pd.DataFrame([]) - self._write_file() - - @property - def time_series(self) -> pd.DataFrame: - return self._time_series - - @time_series.setter - def time_series(self, time_series: pd.DataFrame) -> None: - self._time_series = time_series - self._write_file() - - def _write_file(self) -> None: - self.file_path.parent.mkdir(parents=True, exist_ok=True) - self._time_series.to_csv(self.file_path, sep="\t", header=False, index=False, encoding="utf-8") - - class TimeSeries: """ A time series for use in Antares Args: time_series: Pandas DataFrame containing the time series. - local_file: TimeSeriesFile to store the time series if the study is local. """ - def __init__( - self, time_series: pd.DataFrame = pd.DataFrame([]), local_file: Optional[TimeSeriesFile] = None - ) -> None: + def __init__(self, time_series: pd.DataFrame = pd.DataFrame([])) -> None: self._time_series = time_series - self._local_file = local_file @property def time_series(self) -> pd.DataFrame: return self._time_series - - @time_series.setter - def time_series(self, time_series: pd.DataFrame) -> None: - self._time_series = time_series - if self._local_file is not None: - self._local_file.time_series = time_series - - @property - def local_file(self) -> Optional[TimeSeriesFile]: - return self._local_file - - @local_file.setter - def local_file(self, local_file: TimeSeriesFile) -> None: - self._local_file = local_file - self._time_series = local_file.time_series diff --git a/tests/antares/services/local_services/test_area.py b/tests/antares/services/local_services/test_area.py index f2f2bf5b..d200482a 100644 --- a/tests/antares/services/local_services/test_area.py +++ b/tests/antares/services/local_services/test_area.py @@ -10,12 +10,16 @@ # # This file is part of the Antares project. +import typing as t + from configparser import ConfigParser from io import StringIO +from pathlib import Path import numpy as np import pandas as pd +from antares.config.local_configuration import LocalConfiguration from antares.model.hydro import Hydro from antares.model.renewable import ( RenewableCluster, @@ -754,7 +758,6 @@ def test_settings_ini_exists(self, area_fr, fr_wind): # Then assert expected_ini_path.exists() assert expected_ini_path.is_file() - assert expected_ini_path == fr_wind.prepro.settings.ini_path def test_conversion_txt_exists(self, area_fr, fr_wind): # Given @@ -765,9 +768,8 @@ def test_conversion_txt_exists(self, area_fr, fr_wind): # Then assert expected_file_path.exists() assert expected_file_path.is_file() - assert fr_wind.prepro.conversion.local_file.file_path == expected_file_path - def test_conversion_txt_has_correct_default_values(self, area_fr, fr_wind): + def test_conversion_txt_has_correct_default_values(self, local_study, fr_wind): # Given expected_file_contents = """-9999999980506447872\t0\t9999999980506447872 0\t0\t0 @@ -776,13 +778,13 @@ def test_conversion_txt_has_correct_default_values(self, area_fr, fr_wind): expected_file_data = pd.read_csv(StringIO(expected_file_contents), sep="\t", header=None).astype(str) # When - with fr_wind.prepro.conversion.local_file.file_path.open("r") as fr_wind_file: - actual_file_contents = fr_wind_file.read() - actual_file_data = fr_wind.prepro.conversion.time_series.astype(str) + local_config = t.cast(LocalConfiguration, local_study.service.config) + study_path = local_config.study_path + actual_file_path = study_path.joinpath(Path("input") / "wind" / "prepro" / "fr" / "conversion.txt") + actual_data = pd.read_csv(actual_file_path, sep="\t", header=None, dtype=str) # Then - assert actual_file_data.equals(expected_file_data) - assert actual_file_contents == expected_file_contents + assert actual_data.equals(expected_file_data) def test_data_txt_exists(self, area_fr, fr_wind): # Given @@ -793,33 +795,19 @@ def test_data_txt_exists(self, area_fr, fr_wind): # Then assert expected_file_path.exists() assert expected_file_path.is_file() - assert fr_wind.prepro.data.local_file.file_path == expected_file_path - def test_data_txt_has_correct_default_values(self, area_fr, fr_wind): + def test_data_txt_has_correct_default_values(self, local_study, fr_wind): # Given - expected_file_contents = """1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -""" - expected_file_data = pd.read_csv(StringIO(expected_file_contents), sep="\t", header=None) - - # When - with fr_wind.prepro.data.local_file.file_path.open("r") as fr_wind_file: - actual_file_contents = fr_wind_file.read() - actual_file_data = fr_wind.prepro.data.time_series + expected_file_data = pd.DataFrame(np.ones([12, 6]), dtype=int) + expected_file_data[2] = 0 # Then - assert actual_file_data.equals(expected_file_data) - assert actual_file_contents == expected_file_contents + local_config = t.cast(LocalConfiguration, local_study.service.config) + study_path = local_config.study_path + actual_file_path = study_path.joinpath(Path("input") / "wind" / "prepro" / "fr" / "data.txt") + actual_data = pd.read_csv(actual_file_path, sep="\t", header=None, dtype=int) + # For some reason the equality check fails on windows, so we check it in a different way + assert actual_data.to_dict() == expected_file_data.to_dict() def test_k_txt_exists(self, area_fr, fr_wind): # Given @@ -830,18 +818,13 @@ def test_k_txt_exists(self, area_fr, fr_wind): # Then assert expected_file_path.exists() assert expected_file_path.is_file() - assert fr_wind.prepro.k.local_file.file_path == expected_file_path - def test_k_txt_is_empty_by_default(self, area_fr, fr_wind): - # Given - expected_file_contents = """""" - - # When - with fr_wind.prepro.k.local_file.file_path.open("r") as fr_wind_file: - actual_file_contents = fr_wind_file.read() - - # Then - assert actual_file_contents == expected_file_contents + def test_k_and_translation_txt_is_empty_by_default(self, local_study, fr_wind): + local_config = t.cast(LocalConfiguration, local_study.service.config) + study_path = local_config.study_path + for file in ["k", "translation"]: + actual_file_path = study_path.joinpath(Path("input") / "wind" / "prepro" / "fr" / f"{file}.txt") + assert actual_file_path.read_text() == "" def test_translation_txt_exists(self, area_fr, fr_wind): # Given @@ -852,18 +835,6 @@ def test_translation_txt_exists(self, area_fr, fr_wind): # Then assert expected_file_path.exists() assert expected_file_path.is_file() - assert fr_wind.prepro.translation.local_file.file_path == expected_file_path - - def test_translation_txt_is_empty_by_default(self, area_fr, fr_wind): - # Given - expected_file_contents = """""" - - # When - with fr_wind.prepro.translation.local_file.file_path.open("r") as fr_wind_file: - actual_file_contents = fr_wind_file.read() - - # Then - assert actual_file_contents == expected_file_contents class TestCreateSolar: @@ -909,7 +880,6 @@ def test_settings_ini_exists(self, area_fr, fr_solar): # Then assert expected_ini_path.exists() assert expected_ini_path.is_file() - assert expected_ini_path == fr_solar.prepro.settings.ini_path def test_conversion_txt_exists(self, area_fr, fr_solar): # Given @@ -920,9 +890,8 @@ def test_conversion_txt_exists(self, area_fr, fr_solar): # Then assert expected_file_path.exists() assert expected_file_path.is_file() - assert fr_solar.prepro.conversion.local_file.file_path == expected_file_path - def test_conversion_txt_has_correct_default_values(self, area_fr, fr_solar): + def test_conversion_txt_has_correct_default_values(self, local_study, fr_solar): # Given expected_file_contents = """-9999999980506447872\t0\t9999999980506447872 0\t0\t0 @@ -931,13 +900,13 @@ def test_conversion_txt_has_correct_default_values(self, area_fr, fr_solar): expected_file_data = pd.read_csv(StringIO(expected_file_contents), sep="\t", header=None).astype(str) # When - with fr_solar.prepro.conversion.local_file.file_path.open("r") as fr_solar_file: - actual_file_contents = fr_solar_file.read() - actual_file_data = fr_solar.prepro.conversion.time_series.astype(str) + local_config = t.cast(LocalConfiguration, local_study.service.config) + study_path = local_config.study_path + actual_file_path = study_path.joinpath(Path("input") / "solar" / "prepro" / "fr" / "conversion.txt") + actual_data = pd.read_csv(actual_file_path, sep="\t", header=None, dtype=str) # Then - assert actual_file_data.equals(expected_file_data) - assert actual_file_contents == expected_file_contents + assert actual_data.equals(expected_file_data) def test_data_txt_exists(self, area_fr, fr_solar): # Given @@ -948,33 +917,19 @@ def test_data_txt_exists(self, area_fr, fr_solar): # Then assert expected_file_path.exists() assert expected_file_path.is_file() - assert fr_solar.prepro.data.local_file.file_path == expected_file_path - def test_data_txt_has_correct_default_values(self, area_fr, fr_solar): + def test_data_txt_has_correct_default_values(self, local_study, fr_solar): # Given - expected_file_contents = """1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -""" - expected_file_data = pd.read_csv(StringIO(expected_file_contents), sep="\t", header=None) - - # When - with fr_solar.prepro.data.local_file.file_path.open("r") as fr_solar_file: - actual_file_contents = fr_solar_file.read() - actual_file_data = fr_solar.prepro.data.time_series + expected_file_data = pd.DataFrame(np.ones([12, 6]), dtype=int) + expected_file_data[2] = 0 # Then - assert actual_file_data.equals(expected_file_data) - assert actual_file_contents == expected_file_contents + local_config = t.cast(LocalConfiguration, local_study.service.config) + study_path = local_config.study_path + actual_file_path = study_path.joinpath(Path("input") / "solar" / "prepro" / "fr" / "data.txt") + actual_data = pd.read_csv(actual_file_path, sep="\t", header=None, dtype=int) + # For some reason the equality check fails on windows, so we check it in a different way + assert actual_data.to_dict() == expected_file_data.to_dict() def test_k_txt_exists(self, area_fr, fr_solar): # Given @@ -985,18 +940,13 @@ def test_k_txt_exists(self, area_fr, fr_solar): # Then assert expected_file_path.exists() assert expected_file_path.is_file() - assert fr_solar.prepro.k.local_file.file_path == expected_file_path - def test_k_txt_is_empty_by_default(self, area_fr, fr_solar): - # Given - expected_file_contents = """""" - - # When - with fr_solar.prepro.k.local_file.file_path.open("r") as fr_solar_file: - actual_file_contents = fr_solar_file.read() - - # Then - assert actual_file_contents == expected_file_contents + def test_k_and_translation_txt_is_empty_by_default(self, local_study, fr_solar): + local_config = t.cast(LocalConfiguration, local_study.service.config) + study_path = local_config.study_path + for file in ["k", "translation"]: + actual_file_path = study_path.joinpath(Path("input") / "solar" / "prepro" / "fr" / f"{file}.txt") + assert actual_file_path.read_text() == "" def test_translation_txt_exists(self, area_fr, fr_solar): # Given @@ -1008,18 +958,6 @@ def test_translation_txt_exists(self, area_fr, fr_solar): # Then assert expected_file_path.exists() assert expected_file_path.is_file() - assert fr_solar.prepro.translation.local_file.file_path == expected_file_path - - def test_translation_txt_is_empty_by_default(self, area_fr, fr_solar): - # Given - expected_file_contents = """""" - - # When - with fr_solar.prepro.translation.local_file.file_path.open("r") as fr_solar_file: - actual_file_contents = fr_solar_file.read() - - # Then - assert actual_file_contents == expected_file_contents class TestCreateLoad: @@ -1065,7 +1003,6 @@ def test_settings_ini_exists(self, area_fr, fr_load): # Then assert expected_ini_path.exists() assert expected_ini_path.is_file() - assert expected_ini_path == fr_load.prepro.settings.ini_path def test_conversion_txt_exists(self, area_fr, fr_load): # Given @@ -1076,9 +1013,8 @@ def test_conversion_txt_exists(self, area_fr, fr_load): # Then assert expected_file_path.exists() assert expected_file_path.is_file() - assert fr_load.prepro.conversion.local_file.file_path == expected_file_path - def test_conversion_txt_has_correct_default_values(self, area_fr, fr_load): + def test_conversion_txt_has_correct_default_values(self, local_study, fr_load): # Given expected_file_contents = """-9999999980506447872\t0\t9999999980506447872 0\t0\t0 @@ -1087,13 +1023,13 @@ def test_conversion_txt_has_correct_default_values(self, area_fr, fr_load): expected_file_data = pd.read_csv(StringIO(expected_file_contents), sep="\t", header=None).astype(str) # When - with fr_load.prepro.conversion.local_file.file_path.open("r") as fr_load_file: - actual_file_contents = fr_load_file.read() - actual_file_data = fr_load.prepro.conversion.time_series.astype(str) + local_config = t.cast(LocalConfiguration, local_study.service.config) + study_path = local_config.study_path + actual_file_path = study_path.joinpath(Path("input") / "load" / "prepro" / "fr" / "conversion.txt") + actual_data = pd.read_csv(actual_file_path, sep="\t", header=None, dtype=str) # Then - assert actual_file_data.equals(expected_file_data) - assert actual_file_contents == expected_file_contents + assert actual_data.equals(expected_file_data) def test_data_txt_exists(self, area_fr, fr_load): # Given @@ -1104,33 +1040,19 @@ def test_data_txt_exists(self, area_fr, fr_load): # Then assert expected_file_path.exists() assert expected_file_path.is_file() - assert fr_load.prepro.data.local_file.file_path == expected_file_path - def test_data_txt_has_correct_default_values(self, area_fr, fr_load): + def test_data_txt_has_correct_default_values(self, local_study, fr_load): # Given - expected_file_contents = """1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -1\t1\t0\t1\t1\t1 -""" - expected_file_data = pd.read_csv(StringIO(expected_file_contents), sep="\t", header=None) - - # When - with fr_load.prepro.data.local_file.file_path.open("r") as fr_load_file: - actual_file_contents = fr_load_file.read() - actual_file_data = fr_load.prepro.data.time_series + expected_file_data = pd.DataFrame(np.ones([12, 6]), dtype=int) + expected_file_data[2] = 0 # Then - assert actual_file_data.equals(expected_file_data) - assert actual_file_contents == expected_file_contents + local_config = t.cast(LocalConfiguration, local_study.service.config) + study_path = local_config.study_path + actual_file_path = study_path.joinpath(Path("input") / "load" / "prepro" / "fr" / "data.txt") + actual_data = pd.read_csv(actual_file_path, sep="\t", header=None, dtype=int) + # For some reason the equality check fails on windows, so we check it in a different way + assert actual_data.to_dict() == expected_file_data.to_dict() def test_k_txt_exists(self, area_fr, fr_load): # Given @@ -1141,18 +1063,6 @@ def test_k_txt_exists(self, area_fr, fr_load): # Then assert expected_file_path.exists() assert expected_file_path.is_file() - assert fr_load.prepro.k.local_file.file_path == expected_file_path - - def test_k_txt_is_empty_by_default(self, area_fr, fr_load): - # Given - expected_file_contents = """""" - - # When - with fr_load.prepro.k.local_file.file_path.open("r") as fr_load_file: - actual_file_contents = fr_load_file.read() - - # Then - assert actual_file_contents == expected_file_contents def test_translation_txt_exists(self, area_fr, fr_load): # Given @@ -1163,18 +1073,13 @@ def test_translation_txt_exists(self, area_fr, fr_load): # Then assert expected_file_path.exists() assert expected_file_path.is_file() - assert fr_load.prepro.translation.local_file.file_path == expected_file_path - def test_translation_txt_is_empty_by_default(self, area_fr, fr_load): - # Given - expected_file_contents = """""" - - # When - with fr_load.prepro.translation.local_file.file_path.open("r") as fr_load_file: - actual_file_contents = fr_load_file.read() - - # Then - assert actual_file_contents == expected_file_contents + def test_k_and_translation_txt_is_empty_by_default(self, local_study, fr_load): + local_config = t.cast(LocalConfiguration, local_study.service.config) + study_path = local_config.study_path + for file in ["k", "translation"]: + actual_file_path = study_path.joinpath(Path("input") / "load" / "prepro" / "fr" / f"{file}.txt") + assert actual_file_path.read_text() == "" class TestReadArea: diff --git a/tests/antares/services/local_services/test_study.py b/tests/antares/services/local_services/test_study.py index c0f82f56..445490a0 100644 --- a/tests/antares/services/local_services/test_study.py +++ b/tests/antares/services/local_services/test_study.py @@ -15,6 +15,7 @@ import logging import os import time +import typing as t from configparser import ConfigParser from pathlib import Path @@ -84,7 +85,6 @@ from antares.service.local_services.st_storage_local import ShortTermStorageLocalService from antares.service.local_services.thermal_local import ThermalLocalService from antares.tools.ini_tool import IniFileTypes -from antares.tools.time_series_tool import TimeSeriesFileType class TestCreateStudy: @@ -154,22 +154,15 @@ def test_study_antares_content(self, monkeypatch, tmp_path): # Then assert actual_content == antares_content - def test_verify_study_already_exists_error(self, monkeypatch, tmp_path, caplog): + def test_verify_study_already_exists_error(self, tmp_path): # Given study_name = "studyTest" version = "850" - - def mock_verify_study_already_exists(study_directory): - raise FileExistsError(f"Failed to create study. Study {study_directory} already exists") - - monkeypatch.setattr("antares.model.study._verify_study_already_exists", mock_verify_study_already_exists) + (tmp_path / study_name).mkdir(parents=True, exist_ok=True) # When - with caplog.at_level(logging.ERROR): - with pytest.raises( - FileExistsError, match=f"Failed to create study. Study {tmp_path}/{study_name} already exists" - ): - create_study_local(study_name, version, LocalConfiguration(tmp_path, study_name)) + with pytest.raises(FileExistsError, match=f"Study {study_name} already exists"): + create_study_local(study_name, version, LocalConfiguration(tmp_path, study_name)) def test_solar_correlation_ini_exists(self, local_study_with_hydro): # Given @@ -1562,10 +1555,7 @@ def test_area_ui_ini_content(self, tmp_path, local_study): # Then assert actual_content == ui_ini_content - def test_create_area_with_custom_error(self, monkeypatch, caplog, local_study): - # Given - caplog.set_level(logging.INFO) - + def test_create_area_with_custom_error(self, monkeypatch, local_study): def mock_error_in_sets_ini(): raise CustomError("An error occurred while processing area can not be created") @@ -2327,68 +2317,6 @@ def test_constraint_term_with_offset_and_ini_have_correct_values( assert actual_ini_content == expected_ini_contents - def test_binding_constraint_with_timeseries_stores_ts_file(self, local_study_with_hydro): - # Given - ts_matrix = pd.DataFrame(np.zeros([365 * 24, 2])) - - # When - constraints = { - "lesser": - # Less than timeseries - local_study_with_hydro.create_binding_constraint( - name="test constraint - less", - properties=BindingConstraintProperties( - operator=BindingConstraintOperator.LESS, - ), - less_term_matrix=ts_matrix, - ), - "equal": - # Equal timeseries - local_study_with_hydro.create_binding_constraint( - name="test constraint - equal", - properties=BindingConstraintProperties( - operator=BindingConstraintOperator.EQUAL, - ), - equal_term_matrix=ts_matrix, - ), - "greater": - # Greater than timeseries - local_study_with_hydro.create_binding_constraint( - name="test constraint - greater", - properties=BindingConstraintProperties( - operator=BindingConstraintOperator.GREATER, - ), - greater_term_matrix=ts_matrix, - ), - "both": - # Greater than timeseries - local_study_with_hydro.create_binding_constraint( - name="test constraint - both", - properties=BindingConstraintProperties( - operator=BindingConstraintOperator.BOTH, - ), - less_term_matrix=ts_matrix, - greater_term_matrix=ts_matrix, - ), - } - - # Then - assert local_study_with_hydro._binding_constraints_service.time_series[ - f"{constraints['lesser'].id.lower()}_lt" - ].local_file.file_path.is_file() - assert local_study_with_hydro._binding_constraints_service.time_series[ - f"{constraints['equal'].id.lower()}_eq" - ].local_file.file_path.is_file() - assert local_study_with_hydro._binding_constraints_service.time_series[ - f"{constraints['greater'].id.lower()}_gt" - ].local_file.file_path.is_file() - assert local_study_with_hydro._binding_constraints_service.time_series[ - f"{constraints['both'].id.lower()}_lt" - ].local_file.file_path.is_file() - assert local_study_with_hydro._binding_constraints_service.time_series[ - f"{constraints['both'].id.lower()}_gt" - ].local_file.file_path.is_file() - def test_binding_constraints_have_correct_default_time_series(self, test_constraint, local_study_with_constraint): # Given expected_time_series_hourly = pd.DataFrame(np.zeros([365 * 24 + 24, 1])) @@ -2411,74 +2339,50 @@ def test_binding_constraints_have_correct_default_time_series(self, test_constra operator=BindingConstraintOperator.BOTH, time_step=BindingConstraintFrequency.HOURLY ), ) - expected_pre_created_ts_file = ( - local_study_with_constraint.service.config.study_path - / TimeSeriesFileType.BINDING_CONSTRAINT_LESS.value.format(constraint_id=test_constraint.id) - ) - - # When - with local_study_with_constraint._binding_constraints_service.time_series[ - f"{test_constraint.id}_lt" - ].local_file.file_path.open("r") as pre_created_file: - actual_time_series_pre_created = pd.read_csv(pre_created_file, header=None) - with local_study_with_constraint._binding_constraints_service.time_series[ - "test greater_gt" - ].local_file.file_path.open("r") as greater_file: - actual_time_series_greater = pd.read_csv(greater_file, header=None) - with local_study_with_constraint._binding_constraints_service.time_series[ - "test equal_eq" - ].local_file.file_path.open("r") as equal_file: - actual_time_series_equal = pd.read_csv(equal_file, header=None) - with local_study_with_constraint._binding_constraints_service.time_series[ - "test both_gt" - ].local_file.file_path.open("r") as both_greater_file: - actual_time_series_both_greater = pd.read_csv(both_greater_file, header=None) - with local_study_with_constraint._binding_constraints_service.time_series[ - "test both_lt" - ].local_file.file_path.open("r") as both_lesser_file: - actual_time_series_both_lesser = pd.read_csv(both_lesser_file, header=None) # Then - # Verify that file names are created correctly - assert ( - local_study_with_constraint._binding_constraints_service.time_series[ - f"{test_constraint.id}_lt" - ].local_file.file_path - == expected_pre_created_ts_file - ) - # Verify that default file contents are the correct and expected - assert actual_time_series_pre_created.equals(expected_time_series_hourly) + local_config = t.cast(LocalConfiguration, local_study_with_constraint.service.config) + study_path = local_config.study_path + + actual_file_path = study_path.joinpath(Path("input") / "bindingconstraints" / "test greater_gt.txt") + actual_time_series_greater = pd.read_csv(actual_file_path, sep="\t", header=None, dtype=float) assert actual_time_series_greater.equals(expected_time_series_daily_weekly) + + actual_file_path = study_path.joinpath(Path("input") / "bindingconstraints" / "test equal_eq.txt") + actual_time_series_equal = pd.read_csv(actual_file_path, sep="\t", header=None, dtype=float) assert actual_time_series_equal.equals(expected_time_series_daily_weekly) - assert actual_time_series_both_greater.equals(expected_time_series_hourly) + + actual_file_path = study_path.joinpath(Path("input") / "bindingconstraints" / f"{test_constraint.id}_lt.txt") + actual_time_series_pre_created = pd.read_csv(actual_file_path, sep="\t", header=None, dtype=float) + assert actual_time_series_pre_created.equals(expected_time_series_hourly) + + actual_file_path = study_path.joinpath(Path("input") / "bindingconstraints" / "test both_lt.txt") + actual_time_series_both_lesser = pd.read_csv(actual_file_path, sep="\t", header=None, dtype=float) assert actual_time_series_both_lesser.equals(expected_time_series_hourly) - def test_submitted_time_series_is_saved(self, local_study_with_constraint): + actual_file_path = study_path.joinpath(Path("input") / "bindingconstraints" / "test both_gt.txt") + actual_time_series_both_greater = pd.read_csv(actual_file_path, sep="\t", header=None, dtype=float) + assert actual_time_series_both_greater.equals(expected_time_series_hourly) + + def test_submitted_time_series_is_saved(self, local_study): # Given expected_time_series = pd.DataFrame(np.ones([3, 1])) - local_study_with_constraint.create_binding_constraint( - name="test time series", + bc_name = "test time series" + local_study.create_binding_constraint( + name=bc_name, properties=BindingConstraintProperties( operator=BindingConstraintOperator.GREATER, time_step=BindingConstraintFrequency.HOURLY ), greater_term_matrix=expected_time_series, ) - expected_file_contents = """1.0 -1.0 -1.0 -""" - # When - with local_study_with_constraint._binding_constraints_service.time_series[ - "test time series_gt" - ].local_file.file_path.open("r") as time_series_file: - actual_time_series = pd.read_csv(time_series_file, header=None) - time_series_file.seek(0) - actual_file_contents = time_series_file.read() + local_config = t.cast(LocalConfiguration, local_study.service.config) + study_path = local_config.study_path + actual_file_path = study_path.joinpath(Path("input") / "bindingconstraints" / f"{bc_name}_gt.txt") + actual_time_series = pd.read_csv(actual_file_path, sep="\t", header=None, dtype=float) # Then assert actual_time_series.equals(expected_time_series) - assert actual_file_contents == expected_file_contents def test_updating_binding_constraint_properties_updates_local(self, local_study_with_constraint, test_constraint): # Given diff --git a/tests/antares/tools/conftest.py b/tests/antares/tools/conftest.py deleted file mode 100644 index ccbb86ae..00000000 --- a/tests/antares/tools/conftest.py +++ /dev/null @@ -1,28 +0,0 @@ -# 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. - -import pytest - -import numpy as np -import pandas as pd - -from antares.tools.time_series_tool import TimeSeriesFile, TimeSeriesFileType - - -@pytest.fixture -def time_series_data(): - return pd.DataFrame(np.zeros([2, 3])) - - -@pytest.fixture -def time_series_file(tmp_path, time_series_data): - return TimeSeriesFile(TimeSeriesFileType.RESERVES, tmp_path, area_id="test", time_series=time_series_data) diff --git a/tests/antares/tools/test_time_series_tool.py b/tests/antares/tools/test_time_series_tool.py deleted file mode 100644 index e52bf512..00000000 --- a/tests/antares/tools/test_time_series_tool.py +++ /dev/null @@ -1,129 +0,0 @@ -# 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. - -import pytest - -import numpy as np -import pandas as pd - -from antares.tools.time_series_tool import TimeSeries, TimeSeriesFile, TimeSeriesFileType - - -class TestTimeSeries: - def test_empty_ts_is_dataframe(self): - # Given - time_series = TimeSeries() - - assert isinstance(time_series.time_series, pd.DataFrame) - assert time_series.time_series.empty - assert time_series.time_series.equals(pd.DataFrame([])) - - def test_time_series_can_be_set(self, time_series_data): - # Given - time_series = TimeSeries() - expected_time_series = pd.DataFrame(np.zeros(time_series_data.shape)) - - # When - time_series.time_series = time_series_data - - # Then - assert time_series.time_series.equals(expected_time_series) - - def test_time_series_can_have_file(self, time_series_file): - # Given - time_series = TimeSeries() - - # When - time_series.local_file = time_series_file - - # Then - assert time_series.local_file.file_path.is_file() - - def test_time_series_can_update_file(self, time_series_file, time_series_data): - # Given - time_series = TimeSeries() - expected_file_content = pd.DataFrame(np.zeros(time_series_data.shape)) - update_file_content = pd.DataFrame(np.ones(time_series_data.shape)) - - # When - time_series.local_file = time_series_file - - # Then - assert time_series.time_series.equals(expected_file_content) - - # When - time_series.time_series = update_file_content - - # Then - actual_file_content = pd.read_csv( - time_series.local_file.file_path, sep="\t", header=None, index_col=None, encoding="utf-8" - ) - assert actual_file_content.equals(update_file_content) - - -class TestTimeSeriesFile: - def test_time_series_file_can_be_set(self, time_series_file, time_series_data): - # Given - time_series = TimeSeries() - - # When - time_series.local_file = time_series_file - - # Then - assert time_series.time_series.equals(time_series_data) - assert time_series_file.file_path.is_file() - assert time_series.local_file is not None - - def test_time_series_file_time_series_can_be_updated(self, time_series_file, time_series_data): - # Given - time_series = TimeSeries(pd.DataFrame(np.ones([2, 3]))) - - # When - time_series_file.time_series = time_series.time_series - - with pytest.raises(AssertionError): - assert time_series_file.time_series.equals(time_series_data) - # assert time_series.local_file.file_path.is_file() - assert time_series_file.time_series.equals(time_series.time_series) - - def test_no_area_provided_gives_error(self, tmp_path, time_series_data): - # Given - with pytest.raises(ValueError, match="area_id is required for this file type."): - TimeSeriesFile(ts_file_type=TimeSeriesFileType.RESERVES, study_path=tmp_path, time_series=time_series_data) - - def test_file_exists_time_series_provided_gives_error(self, tmp_path, time_series_data): - # Given - time_series = TimeSeries(time_series_data) - file_name = TimeSeriesFileType.RESERVES.value.format(area_id="test") - - # When - (tmp_path / file_name).parent.mkdir(exist_ok=True, parents=True) - time_series.time_series.to_csv(tmp_path / file_name, sep="\t", header=False, index=False, encoding="utf-8") - - # Then - with pytest.raises( - ValueError, match=f"File {tmp_path / file_name} already exists and a time series was provided." - ): - TimeSeriesFile(TimeSeriesFileType.RESERVES, tmp_path, area_id="test", time_series=time_series.time_series) - - def test_file_exists_no_time_series_provided(self, tmp_path, time_series_data): - # Given - time_series = TimeSeries(time_series_data) - file_name = tmp_path / TimeSeriesFileType.RESERVES.value.format(area_id="test") - - # When - file_name.parent.mkdir(exist_ok=True, parents=True) - time_series.time_series.to_csv(file_name, sep="\t", header=False, index=False, encoding="utf-8") - time_series_file = TimeSeriesFile(TimeSeriesFileType.RESERVES, tmp_path, area_id="test") - - # Then - assert time_series_file.time_series.equals(time_series_data) diff --git a/tox.ini b/tox.ini index 6398bed4..dd5616bb 100644 --- a/tox.ini +++ b/tox.ini @@ -1,17 +1,22 @@ [tox] env_list = - py3.{9,10,11,12}-test + py3.{9,10,12}-test lint [testenv] deps = -r requirements-dev.txt -[testenv:py3.{9,10,11,12}-test] +[testenv:py3.{9,10,12}-test] description = run the tests with pytest commands = pytest tests/antares {posargs} +[testenv:coverage] +description = Run tests with coverage (with Python 3.11 inside the CI) +commands = + pytest --cov src --cov-report xml tests/antares + [testenv:lint] description = linting with ruff skip_install = True From ab74da8402b77bb4c531e856501869abc698d2e7 Mon Sep 17 00:00:00 2001 From: MartinBelthle Date: Tue, 26 Nov 2024 18:00:08 +0100 Subject: [PATCH 09/33] chore(release): prepare first release (#15) --- README.md | 39 +++---------------------- docs/CHANGELOG.md | 4 +++ docs/developer.md | 35 ++++++++++++++++++++++ docs/usage.md | 43 ++++++++++++++++++++++++++++ pyproject.toml | 7 ++--- sonar-project.properties | 4 +-- tests/integration/test_web_client.py | 5 +--- 7 files changed, 92 insertions(+), 45 deletions(-) create mode 100644 docs/CHANGELOG.md create mode 100644 docs/developer.md create mode 100644 docs/usage.md diff --git a/README.md b/README.md index d3b49718..07633e24 100644 --- a/README.md +++ b/README.md @@ -8,39 +8,8 @@ antares studies. This project only supports antares studies with a version v8.8 or higher. -## developers -### install dev requirements +**Table of Contents** -Install dev requirements with `pip install -r requirements-dev.txt` - -### linting and formatting - -To reformat your code, use this command line: `ruff check src/ tests/ --fix && ruff format src/ tests/` - -### typechecking - -To typecheck your code, use this command line: `mypy` - -### integration testing - -To launch integration tests you'll need an AntaresWebDesktop instance on your local env (at least the v.2.17.3, -**currently running in 2.17.5**). -To install it, download it from the last [Antares Web release](https://github.com/AntaresSimulatorTeam/AntaREST/releases) -(inside the assets list). -Then, unzip it at the root of this repository and rename the folder `AntaresWebDesktop`. -*NB*: The expected folder structure is the following: `antares_craft/AntaresWebDesktop/config.yaml` - -### tox -To use [tox](https://tox.wiki/) to run unit tests in multiple python versions at the same time as linting and formatting -with ruff and typing with mypy: -1) As the dev requirements include [uv](https://docs.astral.sh/uv/) and `tox-uv` there is no need to install python -versions, `uv` will do this for you. -2) Use `tox -p` to run the environments in parallel to save time, this will create virtual environment with the -necessary python versions the first time you run tox. - -### mkdocs -Smallest beginning of `mkdocs` included more as proof of concept than anything, theme and logo copied from [Antares -Simulator](https://github.com/AntaresSimulatorTeam/Antares_Simulator). -1) To preview the docs on your local machine run `mkdocs serve`. -2) To build the static site for publishing for example on [Read the Docs](https://readthedocs.io) use `mkdocs build`. -3) To flesh out the documentation see [mkdoc guides](https://www.mkdocs.org/user-guide/). +- [usage](docs/usage.md) +- [development](docs/developer.md) +- [Changelog](docs/CHANGELOG.md) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100644 index 00000000..54b47843 --- /dev/null +++ b/docs/CHANGELOG.md @@ -0,0 +1,4 @@ +v0.1.0 (2024-11-26) +------------------- + +* First release of the project. \ No newline at end of file diff --git a/docs/developer.md b/docs/developer.md new file mode 100644 index 00000000..1d915348 --- /dev/null +++ b/docs/developer.md @@ -0,0 +1,35 @@ +### install dev requirements + +Install dev requirements with `pip install -r requirements-dev.txt` + +### linting and formatting + +To reformat your code, use this command line: `ruff check src/ tests/ --fix && ruff format src/ tests/` + +### typechecking + +To typecheck your code, use this command line: `mypy` + +### integration testing + +To launch integration tests you'll need an AntaresWebDesktop instance on your local env (at least the v.2.17.3, +**currently running in 2.17.5**). +To install it, download it from the last [Antares Web release](https://github.com/AntaresSimulatorTeam/AntaREST/releases) +(inside the assets list). +Then, unzip it at the root of this repository and rename the folder `AntaresWebDesktop`. +*NB*: The expected folder structure is the following: `antares_craft/AntaresWebDesktop/config.yaml` + +### tox +To use [tox](https://tox.wiki/) to run unit tests in multiple python versions at the same time as linting and formatting +with ruff and typing with mypy: +1) As the dev requirements include [uv](https://docs.astral.sh/uv/) and `tox-uv` there is no need to install python +versions, `uv` will do this for you. +2) Use `tox -p` to run the environments in parallel to save time, this will create virtual environment with the +necessary python versions the first time you run tox. + +### mkdocs +Smallest beginning of `mkdocs` included more as proof of concept than anything, theme and logo copied from [Antares +Simulator](https://github.com/AntaresSimulatorTeam/Antares_Simulator). +1) To preview the docs on your local machine run `mkdocs serve`. +2) To build the static site for publishing for example on [Read the Docs](https://readthedocs.io) use `mkdocs build`. +3) To flesh out the documentation see [mkdoc guides](https://www.mkdocs.org/user-guide/). diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 00000000..b9a895bb --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,43 @@ +# Introduction + +With antares-craft you can interact with studies using AntaresWeb API or in local mode. +To interact with AntaresWeb you need a token. + +## AntaresWeb + +### How to create a study + +``` +api_config = APIconf(api_host=antares_web.url, token=your_token, verify=False) +study = create_study_api("antares-craft-test", "880", api_config) +``` + +### How to point to an existing study + +Not handled yet + +## LOCAL + +### How to create a study + + study = create_study_local("your_name", 880, {"local_path": "your_path", "study_name": "your_name"}) + +### How to point to an existing study + +`study = read_study_local(study_path)` + +## Apart from that every operation is the same no matter the environment you're targetting. + +### How to create an area with given properties: + +``` +area_properties = AreaProperties() +area_properties.energy_cost_unsupplied = 10 +study.create_area("fr", area_properties) +``` + +### How to access study areas + +``` +area_list = study.read_areas() +``` \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 4b611495..9251abca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,15 +4,14 @@ build-backend = "setuptools.build_meta" [project] name = "antares_craft" -version = "0.0.1" -description = """Antares Craft python library is currently under construction. When completed it will allow to \ -create, update and read antares studies.""" +version = "0.1.0" +description = """Antares Craft python library under construction. It will allow to create, update and read antares studies.""" readme = "README.md" license = {file = "LICENSE"} authors = [ {name="Sylvain Leclerc"}, {name="Tatiana Vargas"}, - {name="Martin Behlthle"}, + {name="Martin Belthle"}, {name="Sigurd Borge"} ] requires-python = ">=3.9" diff --git a/sonar-project.properties b/sonar-project.properties index 72ec04f6..a4f7b575 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,7 +1,7 @@ -sonar.projectVersion=0.1.5 +sonar.projectVersion=0.1.0 sonar.organization=antaressimulatorteam sonar.projectKey=AntaresSimulatorTeam_antares_craft sonar.sources=src sonar.language=python sonar.python.coverage.reportPaths=coverage.xml -sonar.python.version=3.9 \ No newline at end of file +sonar.python.version=3.11 \ No newline at end of file diff --git a/tests/integration/test_web_client.py b/tests/integration/test_web_client.py index 69ddde87..ea54ec51 100644 --- a/tests/integration/test_web_client.py +++ b/tests/integration/test_web_client.py @@ -32,7 +32,6 @@ from antares.model.st_storage import STStorageGroup, STStorageMatrixName, STStorageProperties from antares.model.study import create_study_api from antares.model.thermal import ThermalClusterGroup, ThermalClusterProperties -from antares.service.api_services.area_api import AreaApiService from tests.integration.antares_web_desktop import AntaresWebDesktop @@ -52,8 +51,6 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): study = create_study_api("antares-craft-test", "880", api_config) - area_api = AreaApiService(api_config, study.service.study_id) - # tests area creation with default values area_name = "FR" area_fr = study.create_area(area_name) @@ -196,7 +193,7 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): assert properties.group == STStorageGroup.BATTERY # test reading list of areas - area_list = area_api.read_areas() + area_list = study.read_areas() assert len(area_list) == 3 # asserts areas are sorted by id assert area_list[0].id == area_be.id From 5b8d269f6d1fd1e856a98696603ad299da87d915 Mon Sep 17 00:00:00 2001 From: MartinBelthle Date: Tue, 26 Nov 2024 18:11:55 +0100 Subject: [PATCH 10/33] v0.1.1 (#16) --- docs/CHANGELOG.md | 5 +++++ pyproject.toml | 2 +- sonar-project.properties | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 54b47843..1e6749ee 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,8 @@ +v0.1.1 (2024-11-26) +------------------- + +* update token and bump version to publish on PyPi. + v0.1.0 (2024-11-26) ------------------- diff --git a/pyproject.toml b/pyproject.toml index 9251abca..4a1eb194 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "antares_craft" -version = "0.1.0" +version = "0.1.1" description = """Antares Craft python library under construction. It will allow to create, update and read antares studies.""" readme = "README.md" license = {file = "LICENSE"} diff --git a/sonar-project.properties b/sonar-project.properties index a4f7b575..febe4360 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,4 +1,4 @@ -sonar.projectVersion=0.1.0 +sonar.projectVersion=0.1.1 sonar.organization=antaressimulatorteam sonar.projectKey=AntaresSimulatorTeam_antares_craft sonar.sources=src From 877f53adc78417d41e8af5f06011b99ceb8262c9 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Wed, 27 Nov 2024 16:17:13 +0100 Subject: [PATCH 11/33] integrated integration tests for the reading clusters methods --- tests/integration/test_web_client.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/integration/test_web_client.py b/tests/integration/test_web_client.py index ea54ec51..6ea2529d 100644 --- a/tests/integration/test_web_client.py +++ b/tests/integration/test_web_client.py @@ -32,6 +32,9 @@ from antares.model.st_storage import STStorageGroup, STStorageMatrixName, STStorageProperties from antares.model.study import create_study_api from antares.model.thermal import ThermalClusterGroup, ThermalClusterProperties +from antares.service.api_services.renewable_api import RenewableApiService +from antares.service.api_services.st_storage_api import ShortTermStorageApiService +from antares.service.api_services.thermal_api import ThermalApiService from tests.integration.antares_web_desktop import AntaresWebDesktop @@ -183,6 +186,31 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): assert storage_fr.name == st_storage_name assert storage_fr.id == "cluster_test" + # testing + thermal_service = ThermalApiService(api_config, study_id=study.service.study_id) + renewable_service = RenewableApiService(api_config, study_id=study.service.study_id) + storage_service = ShortTermStorageApiService(api_config, study_id=study.service.study_id) + thermal_list = thermal_service.read_thermal_clusters(area_fr.id) + renewable_list = renewable_service.read_renewables(area_fr.id) + storage_list = storage_service.read_st_storages(area_fr.id) + + assert len(thermal_list) == 2 + assert len(renewable_list) == 2 + assert len(storage_list) == 1 + + actual_thermal_cluster_1 = thermal_list[0] + actual_thermal_cluster_2 = thermal_list[1] + assert actual_thermal_cluster_1.id == thermal_fr.id + assert actual_thermal_cluster_2.id == thermal_value_be.id + + actual_renewable_1 = renewable_list[0] + actual_renewable_2 = renewable_list[1] + assert actual_renewable_1.id == renewable_fr.id + assert actual_renewable_2.id == renewable_onshore.id + + actual_storage = storage_list[0] + assert actual_storage.id == storage_fr.id + # test short term storage creation with properties st_storage_name = "wind_onshore" storage_properties = STStorageProperties(reservoir_capacity=0.5) From bb9c9e4b6f02e6f9b76514bc6e96028ef1b912fc Mon Sep 17 00:00:00 2001 From: wahadameh Date: Thu, 28 Nov 2024 08:59:14 +0100 Subject: [PATCH 12/33] integrated integration tests for the reading clusters methods --- src/antares/service/api_services/renewable_api.py | 5 ++--- src/antares/service/api_services/st_storage_api.py | 5 ++--- src/antares/service/api_services/thermal_api.py | 5 ++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/antares/service/api_services/renewable_api.py b/src/antares/service/api_services/renewable_api.py index 61a794c5..76349800 100644 --- a/src/antares/service/api_services/renewable_api.py +++ b/src/antares/service/api_services/renewable_api.py @@ -69,9 +69,8 @@ def read_renewables( self, area_id: str, ) -> List[RenewableCluster]: - json_renewables = self._wrapper.get( - self._base_url + "/studies/" + self.study_id + "/areas/" + area_id + "/clusters/renewable" - ).json() + url = "self._base_url/studies/self.study_id/areas/area_id/clusters/renewable" + json_renewables = self._wrapper.get(url).json() renewables = [] diff --git a/src/antares/service/api_services/st_storage_api.py b/src/antares/service/api_services/st_storage_api.py index 3d5e16c3..559048af 100644 --- a/src/antares/service/api_services/st_storage_api.py +++ b/src/antares/service/api_services/st_storage_api.py @@ -76,9 +76,8 @@ def get_storage_matrix(self, storage: STStorage, ts_name: STStorageMatrixName) - return dataframe def read_st_storages(self, area_id: str) -> List[STStorage]: - json_storage = self._wrapper.get( - self._base_url + "/studies/" + self.study_id + "/areas/" + area_id + "/storages" - ).json() + url = f"{self._base_url}/studies/{self.study_id}/areas/{area_id}/storages" + json_storage = self._wrapper.get(url).json() storages = [] for storage in json_storage: diff --git a/src/antares/service/api_services/thermal_api.py b/src/antares/service/api_services/thermal_api.py index 59eeba07..483022ce 100644 --- a/src/antares/service/api_services/thermal_api.py +++ b/src/antares/service/api_services/thermal_api.py @@ -72,9 +72,8 @@ def read_thermal_clusters( self, area_id: str, ) -> List[ThermalCluster]: - json_thermal = self._wrapper.get( - self._base_url + "/studies/" + self.study_id + "/areas/" + area_id + "/clusters/thermal" - ).json() + url = f"{self._base_url}/studies/{self.study_id}/areas/{area_id}/clusters/thermal" + json_thermal = self._wrapper.get(url).json() thermals = [] From 2dd6b7a0bb912b3ea6d9497dc76974f5f1913eb3 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Thu, 28 Nov 2024 09:13:28 +0100 Subject: [PATCH 13/33] integrated integration tests for the reading clusters methods --- src/antares/service/api_services/renewable_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/antares/service/api_services/renewable_api.py b/src/antares/service/api_services/renewable_api.py index 76349800..403e1553 100644 --- a/src/antares/service/api_services/renewable_api.py +++ b/src/antares/service/api_services/renewable_api.py @@ -69,7 +69,7 @@ def read_renewables( self, area_id: str, ) -> List[RenewableCluster]: - url = "self._base_url/studies/self.study_id/areas/area_id/clusters/renewable" + url = f"{self._base_url}/studies/{self.study_id}/areas/{area_id}/clusters/renewable" json_renewables = self._wrapper.get(url).json() renewables = [] From 483e4d6f3f57f4198429a275d75e43d83189f557 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Thu, 28 Nov 2024 11:30:22 +0100 Subject: [PATCH 14/33] integrated integration tests for the reading clusters methods --- src/antares/model/area.py | 18 ++++++++++++++++++ src/antares/service/api_services/area_api.py | 6 ++++++ .../service/api_services/renewable_api.py | 2 ++ .../service/api_services/st_storage_api.py | 3 +++ .../service/api_services/thermal_api.py | 2 ++ src/antares/service/base_services.py | 7 +++++++ .../service/local_services/area_local.py | 6 ++++++ tests/integration/test_web_client.py | 11 ++++------- 8 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/antares/model/area.py b/src/antares/model/area.py index a3ea1d4c..e3650490 100644 --- a/src/antares/model/area.py +++ b/src/antares/model/area.py @@ -372,3 +372,21 @@ def create_hydro( hydro = self._area_service.create_hydro(self.id, properties, matrices) self._hydro = hydro return hydro + + def read_st_storages( + self, + area_id: str, + ) -> List[STStorage]: + return self._storage_service.read_st_storages(area_id) + + def read_renewables( + self, + area_id: str, + ) -> List[RenewableCluster]: + return self._renewable_service.read_renewables(area_id) + + def read_thermal_clusters( + self, + area_id: str, + ) -> List[ThermalCluster]: + return self._thermal_service.read_thermal_clusters(area_id) \ No newline at end of file diff --git a/src/antares/service/api_services/area_api.py b/src/antares/service/api_services/area_api.py index a3644e96..bc722352 100644 --- a/src/antares/service/api_services/area_api.py +++ b/src/antares/service/api_services/area_api.py @@ -418,6 +418,12 @@ def create_hydro( return Hydro(self, area_id, properties) + def read_hydro( + self, + area_id: str, + ) -> Hydro: + raise NotImplementedError + def _create_hydro_series(self, area_id: str, matrices: Dict[HydroMatrixName, pd.DataFrame]) -> None: command_body = [] for matrix_name, series in matrices.items(): diff --git a/src/antares/service/api_services/renewable_api.py b/src/antares/service/api_services/renewable_api.py index 403e1553..d0f6fce4 100644 --- a/src/antares/service/api_services/renewable_api.py +++ b/src/antares/service/api_services/renewable_api.py @@ -82,4 +82,6 @@ def read_renewables( renewable_cluster = RenewableCluster(self.config, renewable_id, renewable_name, renewable_props) renewables.append(renewable_cluster) + renewables.sort(key=lambda renewable: renewable.id) + return renewables diff --git a/src/antares/service/api_services/st_storage_api.py b/src/antares/service/api_services/st_storage_api.py index 559048af..35f6f9f6 100644 --- a/src/antares/service/api_services/st_storage_api.py +++ b/src/antares/service/api_services/st_storage_api.py @@ -88,4 +88,7 @@ def read_st_storages(self, area_id: str) -> List[STStorage]: st_storage = STStorage(self.config, storage_id, storage_name, storage_properties) storages.append(st_storage) + + storages.sort(key=lambda storage: storage.id) + return storages diff --git a/src/antares/service/api_services/thermal_api.py b/src/antares/service/api_services/thermal_api.py index 483022ce..d1cefd58 100644 --- a/src/antares/service/api_services/thermal_api.py +++ b/src/antares/service/api_services/thermal_api.py @@ -85,4 +85,6 @@ def read_thermal_clusters( thermal_cluster = ThermalCluster(self, thermal_id, thermal_name, thermal_props) thermals.append(thermal_cluster) + thermals.sort(key=lambda thermal: thermal.id) + return thermals diff --git a/src/antares/service/base_services.py b/src/antares/service/base_services.py index 7acdc134..b6688caa 100644 --- a/src/antares/service/base_services.py +++ b/src/antares/service/base_services.py @@ -212,6 +212,13 @@ def create_hydro( """ pass + @abstractmethod + def read_hydro( + self, + area_id: str, + ) -> Hydro: + pass + @abstractmethod def update_area_properties(self, area: Area, properties: AreaProperties) -> AreaProperties: """ diff --git a/src/antares/service/local_services/area_local.py b/src/antares/service/local_services/area_local.py index efcd5a21..ad4feac9 100644 --- a/src/antares/service/local_services/area_local.py +++ b/src/antares/service/local_services/area_local.py @@ -202,6 +202,12 @@ def create_area( created """ + def read_hydro( + self, + area_id: str, + ) -> Hydro: + raise NotImplementedError + def _line_exists_in_file(file_content: str, line_to_add: str) -> bool: """ Args: diff --git a/tests/integration/test_web_client.py b/tests/integration/test_web_client.py index 6ea2529d..79716917 100644 --- a/tests/integration/test_web_client.py +++ b/tests/integration/test_web_client.py @@ -186,13 +186,10 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): assert storage_fr.name == st_storage_name assert storage_fr.id == "cluster_test" - # testing - thermal_service = ThermalApiService(api_config, study_id=study.service.study_id) - renewable_service = RenewableApiService(api_config, study_id=study.service.study_id) - storage_service = ShortTermStorageApiService(api_config, study_id=study.service.study_id) - thermal_list = thermal_service.read_thermal_clusters(area_fr.id) - renewable_list = renewable_service.read_renewables(area_fr.id) - storage_list = storage_service.read_st_storages(area_fr.id) + # test each list of clusters has the same length and objects by comparing their id + thermal_list = area_fr.read_thermal_clusters(area_fr.id) + renewable_list = area_fr.read_renewables(area_fr.id) + storage_list = area_fr.read_st_storages(area_fr.id) assert len(thermal_list) == 2 assert len(renewable_list) == 2 From d2f4fd9b3c2904a67e02a8040c78530275e75c50 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Thu, 28 Nov 2024 11:38:22 +0100 Subject: [PATCH 15/33] integrated integration tests for the reading clusters methods --- src/antares/model/area.py | 2 +- src/antares/service/api_services/st_storage_api.py | 1 - src/antares/service/local_services/area_local.py | 12 ++++++------ tests/integration/test_web_client.py | 3 --- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/antares/model/area.py b/src/antares/model/area.py index e3650490..edeeb5c1 100644 --- a/src/antares/model/area.py +++ b/src/antares/model/area.py @@ -389,4 +389,4 @@ def read_thermal_clusters( self, area_id: str, ) -> List[ThermalCluster]: - return self._thermal_service.read_thermal_clusters(area_id) \ No newline at end of file + return self._thermal_service.read_thermal_clusters(area_id) diff --git a/src/antares/service/api_services/st_storage_api.py b/src/antares/service/api_services/st_storage_api.py index 35f6f9f6..c5fc6ba5 100644 --- a/src/antares/service/api_services/st_storage_api.py +++ b/src/antares/service/api_services/st_storage_api.py @@ -88,7 +88,6 @@ def read_st_storages(self, area_id: str) -> List[STStorage]: st_storage = STStorage(self.config, storage_id, storage_name, storage_properties) storages.append(st_storage) - storages.sort(key=lambda storage: storage.id) return storages diff --git a/src/antares/service/local_services/area_local.py b/src/antares/service/local_services/area_local.py index ad4feac9..d9bbdf7d 100644 --- a/src/antares/service/local_services/area_local.py +++ b/src/antares/service/local_services/area_local.py @@ -189,6 +189,12 @@ def create_hydro( return Hydro(self, area_id, local_hydro_properties.yield_hydro_properties()) + def read_hydro( + self, + area_id: str, + ) -> Hydro: + raise NotImplementedError + def create_area( self, area_name: str, properties: Optional[AreaProperties] = None, ui: Optional[AreaUi] = None ) -> Area: @@ -202,12 +208,6 @@ def create_area( created """ - def read_hydro( - self, - area_id: str, - ) -> Hydro: - raise NotImplementedError - def _line_exists_in_file(file_content: str, line_to_add: str) -> bool: """ Args: diff --git a/tests/integration/test_web_client.py b/tests/integration/test_web_client.py index 79716917..98c4ca07 100644 --- a/tests/integration/test_web_client.py +++ b/tests/integration/test_web_client.py @@ -32,9 +32,6 @@ from antares.model.st_storage import STStorageGroup, STStorageMatrixName, STStorageProperties from antares.model.study import create_study_api from antares.model.thermal import ThermalClusterGroup, ThermalClusterProperties -from antares.service.api_services.renewable_api import RenewableApiService -from antares.service.api_services.st_storage_api import ShortTermStorageApiService -from antares.service.api_services.thermal_api import ThermalApiService from tests.integration.antares_web_desktop import AntaresWebDesktop From 6ef393c9173d6fc1d3feffec7e1dc1dbff7e2e3e Mon Sep 17 00:00:00 2001 From: wahadameh Date: Thu, 28 Nov 2024 14:34:27 +0100 Subject: [PATCH 16/33] adding hydro data (unit and integration testing) --- src/antares/model/area.py | 6 ++++ src/antares/service/api_services/area_api.py | 7 +++- .../services/api_services/test_area_api.py | 35 +++++++++++++++++++ tests/integration/test_web_client.py | 6 ++++ 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/antares/model/area.py b/src/antares/model/area.py index edeeb5c1..43d23b45 100644 --- a/src/antares/model/area.py +++ b/src/antares/model/area.py @@ -390,3 +390,9 @@ def read_thermal_clusters( area_id: str, ) -> List[ThermalCluster]: return self._thermal_service.read_thermal_clusters(area_id) + + def read_hydro( + self, + area_id: str, + ) -> Hydro: + return self._area_service.read_hydro(area_id) diff --git a/src/antares/service/api_services/area_api.py b/src/antares/service/api_services/area_api.py index bc722352..118ec60f 100644 --- a/src/antares/service/api_services/area_api.py +++ b/src/antares/service/api_services/area_api.py @@ -422,7 +422,12 @@ def read_hydro( self, area_id: str, ) -> Hydro: - raise NotImplementedError + url = f"{self._base_url}/studies/{self.study_id}/areas/{area_id}/hydro/form" + json_hydro = self._wrapper.get(url).json() + + hydro_props = HydroProperties(**json_hydro) + hydro = Hydro(self, area_id, hydro_props) + return hydro def _create_hydro_series(self, area_id: str, matrices: Dict[HydroMatrixName, pd.DataFrame]) -> None: command_body = [] diff --git a/tests/antares/services/api_services/test_area_api.py b/tests/antares/services/api_services/test_area_api.py index 58752724..28e9e972 100644 --- a/tests/antares/services/api_services/test_area_api.py +++ b/tests/antares/services/api_services/test_area_api.py @@ -447,3 +447,38 @@ def test_read_areas(self): assert actual_thermals[thermal_id].name == expected_area.get_thermals()[thermal_id].name assert actual_renewables[renewable_id].name == expected_area.get_renewables()[renewable_id].name assert actual_storages[storage_id].name == expected_area.get_st_storages()[storage_id].name + + def test_read_hydro(self): + study_id_test = "248bbb99-c909-47b7-b239-01f6f6ae7de7" + area_id = "zone" + json_hydro = { + "interDailyBreakdown": 1, + "intraDailyModulation": 24, + "interMonthlyBreakdown": 1, + "reservoir": "false", + "reservoirCapacity": 0, + "followLoad": "true", + "useWater": "false", + "hardBounds": "false", + "initializeReservoirDate": 0, + "useHeuristic": "true", + "powerToLevel": "false", + "useLeeway": "false", + "leewayLow": 1, + "leewayUp": 1, + "pumpingEfficiency": 1 + } + url = f"https://antares.com/api/v1/studies/{study_id_test}/areas/{area_id}/hydro/form" + + with requests_mock.Mocker() as mocker: + mocker.get(url, json=json_hydro) + area_api = AreaApiService(self.api, study_id_test) + hydro_props = HydroProperties(**json_hydro) + + actual_hydro = Hydro(self.api, area_id, hydro_props) + expected_hydro = area_api.read_hydro(area_id) + + assert actual_hydro.area_id == expected_hydro.area_id + assert actual_hydro.properties == expected_hydro.properties + assert actual_hydro.matrices is None + diff --git a/tests/integration/test_web_client.py b/tests/integration/test_web_client.py index 98c4ca07..84ad541b 100644 --- a/tests/integration/test_web_client.py +++ b/tests/integration/test_web_client.py @@ -205,6 +205,12 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): actual_storage = storage_list[0] assert actual_storage.id == storage_fr.id + # test actual_hydro has the same datas (id, properties and matrices) than area_fr hydro + actual_hydro = area_fr.read_hydro(area_fr.id) + assert actual_hydro.area_id == area_fr.id + assert actual_hydro.properties == area_fr.read_hydro(area_fr.id).properties + assert actual_hydro.matrices == area_fr.read_hydro(area_fr.id).matrices + # test short term storage creation with properties st_storage_name = "wind_onshore" storage_properties = STStorageProperties(reservoir_capacity=0.5) From ed5aa63b6d88e166d702cf17be9a542c7c9c82d6 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Thu, 28 Nov 2024 14:39:37 +0100 Subject: [PATCH 17/33] adding hydro data (unit and integration testing) --- tests/antares/services/api_services/test_area_api.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/antares/services/api_services/test_area_api.py b/tests/antares/services/api_services/test_area_api.py index 28e9e972..d0676fab 100644 --- a/tests/antares/services/api_services/test_area_api.py +++ b/tests/antares/services/api_services/test_area_api.py @@ -466,7 +466,7 @@ def test_read_hydro(self): "useLeeway": "false", "leewayLow": 1, "leewayUp": 1, - "pumpingEfficiency": 1 + "pumpingEfficiency": 1, } url = f"https://antares.com/api/v1/studies/{study_id_test}/areas/{area_id}/hydro/form" @@ -481,4 +481,3 @@ def test_read_hydro(self): assert actual_hydro.area_id == expected_hydro.area_id assert actual_hydro.properties == expected_hydro.properties assert actual_hydro.matrices is None - From 0e165765f39aabbfaf249f320138a809ed992213 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Thu, 28 Nov 2024 15:49:21 +0100 Subject: [PATCH 18/33] adding hydro data (unit and integration testing) --- src/antares/model/area.py | 64 ++++++++++++------- src/antares/service/api_services/area_api.py | 3 + .../services/api_services/test_area_api.py | 9 +-- tests/integration/test_web_client.py | 5 +- 4 files changed, 48 insertions(+), 33 deletions(-) diff --git a/src/antares/model/area.py b/src/antares/model/area.py index 87cc7181..d10c8d7d 100644 --- a/src/antares/model/area.py +++ b/src/antares/model/area.py @@ -26,9 +26,14 @@ from antares.model.commons import FilterOption, sort_filter_values from antares.model.hydro import Hydro, HydroMatrixName, HydroProperties +from antares.model.load import Load +from antares.model.misc_gen import MiscGen from antares.model.renewable import RenewableCluster, RenewableClusterProperties +from antares.model.reserves import Reserves +from antares.model.solar import Solar from antares.model.st_storage import STStorage, STStorageProperties from antares.model.thermal import ThermalCluster, ThermalClusterProperties +from antares.model.wind import Wind from antares.tools.alias_generators import to_space from antares.tools.all_optional_meta import all_optional_model from antares.tools.contents_tool import EnumIgnoreCase, transform_name_to_id @@ -202,8 +207,13 @@ def __init__( # type: ignore # TODO: Find a way to avoid circular imports *, renewables: Optional[Dict[str, RenewableCluster]] = None, thermals: Optional[Dict[str, ThermalCluster]] = None, + load: Optional[Load] = None, st_storages: Optional[Dict[str, STStorage]] = None, hydro: Optional[Hydro] = None, + wind: Optional[Wind] = None, + reserves: Optional[Reserves] = None, + solar: Optional[Solar] = None, + misc_gen: Optional[MiscGen] = None, properties: Optional[AreaProperties] = None, ui: Optional[AreaUi] = None, ): @@ -215,8 +225,13 @@ def __init__( # type: ignore # TODO: Find a way to avoid circular imports self._renewable_service = renewable_service self._renewables = renewables or dict() self._thermals = thermals or dict() + self._load = load self._st_storages = st_storages or dict() self._hydro = hydro + self._wind = wind + self._reserves = reserves + self._solar = solar + self._misc_gen = misc_gen self._properties = properties or AreaProperties() self._ui = ui or AreaUi() @@ -279,25 +294,22 @@ def create_renewable_cluster( self._renewables[renewable.id] = renewable return renewable + def create_load(self, series: Optional[pd.DataFrame]) -> Load: + load = self._area_service.create_load(self, series=series) + self._load = load + return load + def create_st_storage(self, st_storage_name: str, properties: Optional[STStorageProperties] = None) -> STStorage: storage = self._area_service.create_st_storage(self.id, st_storage_name, properties) self._st_storages[storage.id] = storage + return storage def get_load_matrix(self) -> pd.DataFrame: return self._area_service.get_load_matrix(self) - def get_wind_matrix(self) -> pd.DataFrame: - return self._area_service.get_wind_matrix(self) - - def get_solar_matrix(self) -> pd.DataFrame: - return self._area_service.get_solar_matrix(self) - - def get_reserves_matrix(self) -> pd.DataFrame: - return self._area_service.get_reserves_matrix(self) - - def get_misc_gen_matrix(self) -> pd.DataFrame: - return self._area_service.get_misc_gen_matrix(self) + def upload_load_matrix(self, load_matrix: pd.DataFrame) -> None: + self._area_service.upload_load_matrix(self, load_matrix) def delete_thermal_clusters(self, thermal_clusters: List[ThermalCluster]) -> None: self._area_service.delete_thermal_clusters(self, thermal_clusters) @@ -331,20 +343,25 @@ def update_ui(self, ui: AreaUi) -> None: new_ui = self._area_service.update_area_ui(self, ui) self._ui = new_ui - def create_load(self, series: pd.DataFrame) -> None: - self._area_service.create_load(self, series=series) - - def create_wind(self, series: pd.DataFrame) -> None: - self._area_service.create_wind(self, series=series) + def create_wind(self, series: Optional[pd.DataFrame]) -> Wind: + wind = self._area_service.create_wind(self, series=series) + self._wind = wind + return wind - def create_reserves(self, series: pd.DataFrame) -> None: - self._area_service.create_reserves(self, series=series) + def create_reserves(self, series: Optional[pd.DataFrame]) -> Reserves: + reserves = self._area_service.create_reserves(self, series=series) + self._reserves = reserves + return reserves - def create_solar(self, series: pd.DataFrame) -> None: - self._area_service.create_solar(self, series=series) + def create_solar(self, series: Optional[pd.DataFrame]) -> Solar: + solar = self._area_service.create_solar(self, series=series) + self._solar = solar + return solar - def create_misc_gen(self, series: pd.DataFrame) -> None: - self._area_service.create_misc_gen(self, series=series) + def create_misc_gen(self, series: Optional[pd.DataFrame]) -> MiscGen: + misc_gen = self._area_service.create_misc_gen(self, series=series) + self._misc_gen = misc_gen + return misc_gen def create_hydro( self, @@ -376,6 +393,5 @@ def read_thermal_clusters( def read_hydro( self, - area_id: str, ) -> Hydro: - return self._area_service.read_hydro(area_id) \ No newline at end of file + return self._area_service.read_hydro(self.id) diff --git a/src/antares/service/api_services/area_api.py b/src/antares/service/api_services/area_api.py index 1597066c..06ee0ba3 100644 --- a/src/antares/service/api_services/area_api.py +++ b/src/antares/service/api_services/area_api.py @@ -124,6 +124,8 @@ def create_area( ui_response = AreaUiResponse.model_validate(json_ui) ui_properties = AreaUi.model_validate(ui_response.to_craft()) + hydro = self.read_hydro(area_id) + except APIError as e: raise AreaCreationError(area_name, e.message) from e @@ -135,6 +137,7 @@ def create_area( self.renewable_service, properties=area_properties, ui=ui_properties, + hydro=hydro, ) def create_thermal_cluster( diff --git a/tests/antares/services/api_services/test_area_api.py b/tests/antares/services/api_services/test_area_api.py index 9c48f3a4..a0cc59d1 100644 --- a/tests/antares/services/api_services/test_area_api.py +++ b/tests/antares/services/api_services/test_area_api.py @@ -344,8 +344,6 @@ def test_read_areas(self): assert actual_storages[storage_id].name == expected_area.get_st_storages()[storage_id].name def test_read_hydro(self): - study_id_test = "248bbb99-c909-47b7-b239-01f6f6ae7de7" - area_id = "zone" json_hydro = { "interDailyBreakdown": 1, "intraDailyModulation": 24, @@ -363,15 +361,14 @@ def test_read_hydro(self): "leewayUp": 1, "pumpingEfficiency": 1, } - url = f"https://antares.com/api/v1/studies/{study_id_test}/areas/{area_id}/hydro/form" + url = f"https://antares.com/api/v1/studies/{self.study_id}/areas/{self.area.id}/hydro/form" with requests_mock.Mocker() as mocker: mocker.get(url, json=json_hydro) - area_api = AreaApiService(self.api, study_id_test) hydro_props = HydroProperties(**json_hydro) - actual_hydro = Hydro(self.api, area_id, hydro_props) - expected_hydro = area_api.read_hydro(area_id) + actual_hydro = Hydro(self.api, self.area.id, hydro_props) + expected_hydro = self.area.read_hydro() assert actual_hydro.area_id == expected_hydro.area_id assert actual_hydro.properties == expected_hydro.properties diff --git a/tests/integration/test_web_client.py b/tests/integration/test_web_client.py index 989b048b..2420ad54 100644 --- a/tests/integration/test_web_client.py +++ b/tests/integration/test_web_client.py @@ -215,10 +215,9 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): assert actual_storage.id == storage_fr.id # test actual_hydro has the same datas (id, properties and matrices) than area_fr hydro - actual_hydro = area_fr.read_hydro(area_fr.id) + actual_hydro = area_fr.read_hydro() assert actual_hydro.area_id == area_fr.id - assert actual_hydro.properties == area_fr.read_hydro(area_fr.id).properties - assert actual_hydro.matrices == area_fr.read_hydro(area_fr.id).matrices + assert actual_hydro.properties == area_fr.hydro.properties # test short term storage creation with properties st_storage_name = "wind_onshore" From 8238314e12cd94cb82ac44eed8b0527bba9d44a4 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Thu, 28 Nov 2024 15:55:25 +0100 Subject: [PATCH 19/33] adding hydro data (unit and integration testing) --- tests/integration/test_web_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_web_client.py b/tests/integration/test_web_client.py index 2420ad54..0dda4366 100644 --- a/tests/integration/test_web_client.py +++ b/tests/integration/test_web_client.py @@ -213,7 +213,7 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): actual_storage = storage_list[0] assert actual_storage.id == storage_fr.id - + # test actual_hydro has the same datas (id, properties and matrices) than area_fr hydro actual_hydro = area_fr.read_hydro() assert actual_hydro.area_id == area_fr.id From 41821a5353b2171f8ea5b328161ae89ade28ccbb Mon Sep 17 00:00:00 2001 From: wahadameh Date: Fri, 29 Nov 2024 09:52:12 +0100 Subject: [PATCH 20/33] fix(api): correcting import problems --- src/antares/model/area.py | 53 +++++++++++---------------------------- 1 file changed, 14 insertions(+), 39 deletions(-) diff --git a/src/antares/model/area.py b/src/antares/model/area.py index d10c8d7d..c8c7b260 100644 --- a/src/antares/model/area.py +++ b/src/antares/model/area.py @@ -26,14 +26,9 @@ from antares.model.commons import FilterOption, sort_filter_values from antares.model.hydro import Hydro, HydroMatrixName, HydroProperties -from antares.model.load import Load -from antares.model.misc_gen import MiscGen from antares.model.renewable import RenewableCluster, RenewableClusterProperties -from antares.model.reserves import Reserves -from antares.model.solar import Solar from antares.model.st_storage import STStorage, STStorageProperties from antares.model.thermal import ThermalCluster, ThermalClusterProperties -from antares.model.wind import Wind from antares.tools.alias_generators import to_space from antares.tools.all_optional_meta import all_optional_model from antares.tools.contents_tool import EnumIgnoreCase, transform_name_to_id @@ -207,13 +202,8 @@ def __init__( # type: ignore # TODO: Find a way to avoid circular imports *, renewables: Optional[Dict[str, RenewableCluster]] = None, thermals: Optional[Dict[str, ThermalCluster]] = None, - load: Optional[Load] = None, st_storages: Optional[Dict[str, STStorage]] = None, hydro: Optional[Hydro] = None, - wind: Optional[Wind] = None, - reserves: Optional[Reserves] = None, - solar: Optional[Solar] = None, - misc_gen: Optional[MiscGen] = None, properties: Optional[AreaProperties] = None, ui: Optional[AreaUi] = None, ): @@ -225,13 +215,8 @@ def __init__( # type: ignore # TODO: Find a way to avoid circular imports self._renewable_service = renewable_service self._renewables = renewables or dict() self._thermals = thermals or dict() - self._load = load self._st_storages = st_storages or dict() self._hydro = hydro - self._wind = wind - self._reserves = reserves - self._solar = solar - self._misc_gen = misc_gen self._properties = properties or AreaProperties() self._ui = ui or AreaUi() @@ -294,11 +279,6 @@ def create_renewable_cluster( self._renewables[renewable.id] = renewable return renewable - def create_load(self, series: Optional[pd.DataFrame]) -> Load: - load = self._area_service.create_load(self, series=series) - self._load = load - return load - def create_st_storage(self, st_storage_name: str, properties: Optional[STStorageProperties] = None) -> STStorage: storage = self._area_service.create_st_storage(self.id, st_storage_name, properties) self._st_storages[storage.id] = storage @@ -343,25 +323,20 @@ def update_ui(self, ui: AreaUi) -> None: new_ui = self._area_service.update_area_ui(self, ui) self._ui = new_ui - def create_wind(self, series: Optional[pd.DataFrame]) -> Wind: - wind = self._area_service.create_wind(self, series=series) - self._wind = wind - return wind - - def create_reserves(self, series: Optional[pd.DataFrame]) -> Reserves: - reserves = self._area_service.create_reserves(self, series=series) - self._reserves = reserves - return reserves - - def create_solar(self, series: Optional[pd.DataFrame]) -> Solar: - solar = self._area_service.create_solar(self, series=series) - self._solar = solar - return solar - - def create_misc_gen(self, series: Optional[pd.DataFrame]) -> MiscGen: - misc_gen = self._area_service.create_misc_gen(self, series=series) - self._misc_gen = misc_gen - return misc_gen + def create_load(self, series: pd.DataFrame) -> None: + self._area_service.create_load(self, series=series) + + def create_wind(self, series: pd.DataFrame) -> None: + self._area_service.create_wind(self, series=series) + + def create_reserves(self, series: pd.DataFrame) -> None: + self._area_service.create_reserves(self, series=series) + + def create_solar(self, series: pd.DataFrame) -> None: + self._area_service.create_solar(self, series=series) + + def create_misc_gen(self, series: pd.DataFrame) -> None: + self._area_service.create_misc_gen(self, series=series) def create_hydro( self, From e402e5b464bd0894c472bbfac8b8f83f5d232513 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Fri, 29 Nov 2024 10:29:42 +0100 Subject: [PATCH 21/33] fix(api): correcting import problems --- src/antares/model/area.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/antares/model/area.py b/src/antares/model/area.py index c8c7b260..e797486e 100644 --- a/src/antares/model/area.py +++ b/src/antares/model/area.py @@ -288,8 +288,17 @@ def create_st_storage(self, st_storage_name: str, properties: Optional[STStorage def get_load_matrix(self) -> pd.DataFrame: return self._area_service.get_load_matrix(self) - def upload_load_matrix(self, load_matrix: pd.DataFrame) -> None: - self._area_service.upload_load_matrix(self, load_matrix) + def get_wind_matrix(self) -> pd.DataFrame: + return self._area_service.get_wind_matrix(self) + + def get_solar_matrix(self) -> pd.DataFrame: + return self._area_service.get_solar_matrix(self) + + def get_reserves_matrix(self) -> pd.DataFrame: + return self._area_service.get_reserves_matrix(self) + + def get_misc_gen_matrix(self) -> pd.DataFrame: + return self._area_service.get_misc_gen_matrix(self) def delete_thermal_clusters(self, thermal_clusters: List[ThermalCluster]) -> None: self._area_service.delete_thermal_clusters(self, thermal_clusters) From 671336f280b1194899549972d66eaea379063b5c Mon Sep 17 00:00:00 2001 From: wahadameh Date: Fri, 29 Nov 2024 13:36:33 +0100 Subject: [PATCH 22/33] fix(api): correcting import problems --- src/antares/service/api_services/area_api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/antares/service/api_services/area_api.py b/src/antares/service/api_services/area_api.py index 06ee0ba3..767b1977 100644 --- a/src/antares/service/api_services/area_api.py +++ b/src/antares/service/api_services/area_api.py @@ -419,6 +419,7 @@ def read_hydro( hydro_props = HydroProperties(**json_hydro) hydro = Hydro(self, area_id, hydro_props) + return hydro def _create_hydro_series(self, area_id: str, matrices: Dict[HydroMatrixName, pd.DataFrame]) -> None: From b56994a17aeabd2c824393670044f0ab0e14ee87 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Fri, 29 Nov 2024 13:48:42 +0100 Subject: [PATCH 23/33] fix(api): correcting import problems --- tests/antares/services/api_services/test_study_api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/antares/services/api_services/test_study_api.py b/tests/antares/services/api_services/test_study_api.py index 28320762..ad28b749 100644 --- a/tests/antares/services/api_services/test_study_api.py +++ b/tests/antares/services/api_services/test_study_api.py @@ -26,6 +26,7 @@ ) from antares.model.area import Area, AreaProperties, AreaUi from antares.model.binding_constraint import BindingConstraint, BindingConstraintProperties +from antares.model.hydro import HydroProperties from antares.model.link import Link, LinkProperties, LinkUi from antares.model.settings.general import GeneralParameters from antares.model.settings.study_settings import StudySettings @@ -108,9 +109,10 @@ def test_create_area_success(self): } mocker.get(url1, json={area_name: area_ui}, status_code=201) url2 = f"{base_url}/studies/{self.study_id}/areas/{area_name}/properties/form" + url3 = f"{base_url}/studies/{self.study_id}/areas/{area_name}/hydro/form" mocker.put(url2, status_code=201) mocker.get(url2, json=AreaProperties().model_dump(), status_code=200) - + mocker.get(url3, json=HydroProperties().model_dump()) area = self.study.create_area(area_name) assert isinstance(area, Area) From d0b8052a306ef4de2650ade34d7714bbf3efc816 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Mon, 2 Dec 2024 15:31:02 +0100 Subject: [PATCH 24/33] feat(api): adding read_study method and unit testing --- src/antares/model/study.py | 16 ++++++ .../services/api_services/test_study_api.py | 50 ++++++++++++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/antares/model/study.py b/src/antares/model/study.py index 50d019b4..9d674779 100644 --- a/src/antares/model/study.py +++ b/src/antares/model/study.py @@ -171,6 +171,22 @@ def _directory_not_exists(local_path: Path) -> None: ) +def read_study_api(api_config: APIconf, study_id: str) -> "Study": + session = api_config.set_up_api_conf() + wrapper = RequestWrapper(session) + url = f"{api_config.get_host()}/api/v1/studies/{study_id}" + json_study = wrapper.get(url).json() + + study_name = json_study.pop("name") + study_version = json_study.pop("version") + json_study.pop("id") + + study_settings = StudySettings(**json_study) + study = Study(study_name, study_version, ServiceFactory(api_config, study_id, study_name), study_settings) + + return study + + class Study: def __init__( self, diff --git a/tests/antares/services/api_services/test_study_api.py b/tests/antares/services/api_services/test_study_api.py index ad28b749..8881e8cc 100644 --- a/tests/antares/services/api_services/test_study_api.py +++ b/tests/antares/services/api_services/test_study_api.py @@ -30,7 +30,7 @@ from antares.model.link import Link, LinkProperties, LinkUi from antares.model.settings.general import GeneralParameters from antares.model.settings.study_settings import StudySettings -from antares.model.study import Study, create_study_api +from antares.model.study import Study, create_study_api, read_study_api from antares.service.service_factory import ServiceFactory @@ -199,3 +199,51 @@ def test_create_binding_constraint_fails(self): match=f"Could not create the binding constraint {constraint_name}: {self.antares_web_description_msg}", ): self.study.create_binding_constraint(name=constraint_name) + + def test_read_study_api(self): + study_id_test = "248bbb99-c909-47b7-b239-01f6f6ae7de7" + url = f"https://antares.com/api/v1/studies/{study_id_test}" + json_study = { + "id": "248bbb99-c909-47b7-b239-01f6f6ae7de7", + "name": "test_read_areas", + "version": 880, + "created": "2024-11-12 14:05:17.323686", + "updated": "2024-11-13 12:57:41.194491", + "type": "rawstudy", + "owner": { + "id": 2, + "name": "test" + }, + "groups": [ + { + "id": "test", + "name": "test" + } + ], + "public_mode": "NONE", + "workspace": "default", + "managed": "true", + "archived": "false", + "horizon": "null", + "scenario": "null", + "status": "null", + "doc": "null", + "folder": "null", + "tags": [] + } + + with requests_mock.Mocker() as mocker: + mocker.get(url, json=json_study) + + actual_study = read_study_api(self.api, study_id_test) + + expected_study_name = json_study.pop("name") + expected_study_id = json_study.pop("id") + expected_study_version = json_study.pop("version") + expected_study_settings = StudySettings(**json_study) + + expected_study = Study(expected_study_name, expected_study_version, ServiceFactory(self.api, expected_study_id, expected_study_name), expected_study_settings) + + assert actual_study.name == expected_study.name + assert actual_study.version == expected_study.version + assert actual_study.service.study_id == expected_study.service.study_id \ No newline at end of file From af39424372ab57157cd039248a7383377302fd73 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Mon, 2 Dec 2024 15:33:45 +0100 Subject: [PATCH 25/33] feat(api): adding read_study method and unit testing --- .../services/api_services/test_study_api.py | 53 +++++++++---------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/tests/antares/services/api_services/test_study_api.py b/tests/antares/services/api_services/test_study_api.py index 8881e8cc..ab17c26d 100644 --- a/tests/antares/services/api_services/test_study_api.py +++ b/tests/antares/services/api_services/test_study_api.py @@ -204,32 +204,24 @@ def test_read_study_api(self): study_id_test = "248bbb99-c909-47b7-b239-01f6f6ae7de7" url = f"https://antares.com/api/v1/studies/{study_id_test}" json_study = { - "id": "248bbb99-c909-47b7-b239-01f6f6ae7de7", - "name": "test_read_areas", - "version": 880, - "created": "2024-11-12 14:05:17.323686", - "updated": "2024-11-13 12:57:41.194491", - "type": "rawstudy", - "owner": { - "id": 2, - "name": "test" - }, - "groups": [ - { - "id": "test", - "name": "test" - } - ], - "public_mode": "NONE", - "workspace": "default", - "managed": "true", - "archived": "false", - "horizon": "null", - "scenario": "null", - "status": "null", - "doc": "null", - "folder": "null", - "tags": [] + "id": "248bbb99-c909-47b7-b239-01f6f6ae7de7", + "name": "test_read_areas", + "version": 880, + "created": "2024-11-12 14:05:17.323686", + "updated": "2024-11-13 12:57:41.194491", + "type": "rawstudy", + "owner": {"id": 2, "name": "test"}, + "groups": [{"id": "test", "name": "test"}], + "public_mode": "NONE", + "workspace": "default", + "managed": "true", + "archived": "false", + "horizon": "null", + "scenario": "null", + "status": "null", + "doc": "null", + "folder": "null", + "tags": [], } with requests_mock.Mocker() as mocker: @@ -242,8 +234,13 @@ def test_read_study_api(self): expected_study_version = json_study.pop("version") expected_study_settings = StudySettings(**json_study) - expected_study = Study(expected_study_name, expected_study_version, ServiceFactory(self.api, expected_study_id, expected_study_name), expected_study_settings) + expected_study = Study( + expected_study_name, + expected_study_version, + ServiceFactory(self.api, expected_study_id, expected_study_name), + expected_study_settings, + ) assert actual_study.name == expected_study.name assert actual_study.version == expected_study.version - assert actual_study.service.study_id == expected_study.service.study_id \ No newline at end of file + assert actual_study.service.study_id == expected_study.service.study_id From e21278d71cefa5366dc4f41eab61b1c2a30587a9 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Mon, 2 Dec 2024 16:08:33 +0100 Subject: [PATCH 26/33] feat(api): adding read_study method, unit testing and integration testing of the method --- src/antares/model/study.py | 2 +- tests/antares/services/api_services/test_study_api.py | 2 +- tests/integration/test_web_client.py | 8 +++++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/antares/model/study.py b/src/antares/model/study.py index 9d674779..5605b383 100644 --- a/src/antares/model/study.py +++ b/src/antares/model/study.py @@ -178,7 +178,7 @@ def read_study_api(api_config: APIconf, study_id: str) -> "Study": json_study = wrapper.get(url).json() study_name = json_study.pop("name") - study_version = json_study.pop("version") + study_version = str(json_study.pop("version")) json_study.pop("id") study_settings = StudySettings(**json_study) diff --git a/tests/antares/services/api_services/test_study_api.py b/tests/antares/services/api_services/test_study_api.py index ab17c26d..3823d623 100644 --- a/tests/antares/services/api_services/test_study_api.py +++ b/tests/antares/services/api_services/test_study_api.py @@ -206,7 +206,7 @@ def test_read_study_api(self): json_study = { "id": "248bbb99-c909-47b7-b239-01f6f6ae7de7", "name": "test_read_areas", - "version": 880, + "version": "880", "created": "2024-11-12 14:05:17.323686", "updated": "2024-11-13 12:57:41.194491", "type": "rawstudy", diff --git a/tests/integration/test_web_client.py b/tests/integration/test_web_client.py index 0dda4366..3891033e 100644 --- a/tests/integration/test_web_client.py +++ b/tests/integration/test_web_client.py @@ -30,7 +30,7 @@ from antares.model.settings.general import GeneralParameters, Mode from antares.model.settings.study_settings import PlaylistParameters, StudySettings from antares.model.st_storage import STStorageGroup, STStorageMatrixName, STStorageProperties -from antares.model.study import create_study_api +from antares.model.study import create_study_api, read_study_api from antares.model.thermal import ThermalClusterGroup, ThermalClusterProperties from tests.integration.antares_web_desktop import AntaresWebDesktop @@ -51,6 +51,12 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): study = create_study_api("antares-craft-test", "880", api_config) + actual_study = read_study_api(api_config, study.service.study_id) + + assert study.service.study_id == actual_study.service.study_id + assert study.name == actual_study.name + assert study.version == actual_study.version + # tests area creation with default values area_name = "FR" area_fr = study.create_area(area_name) From 91d01da99ab1581cbf5472a326b8da13b39cbd71 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Tue, 3 Dec 2024 13:16:37 +0100 Subject: [PATCH 27/33] feat(api): adding read_study method, unit testing and integration testing of the method --- src/antares/model/study.py | 13 ++++++-- .../services/api_services/test_study_api.py | 32 +++++++------------ 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/antares/model/study.py b/src/antares/model/study.py index 3b692057..ae773cb0 100644 --- a/src/antares/model/study.py +++ b/src/antares/model/study.py @@ -174,16 +174,18 @@ def _directory_not_exists(local_path: Path) -> None: def read_study_api(api_config: APIconf, study_id: str) -> "Study": session = api_config.set_up_api_conf() wrapper = RequestWrapper(session) - url = f"{api_config.get_host()}/api/v1/studies/{study_id}" - json_study = wrapper.get(url).json() + base_url = f"{api_config.get_host()}/api/v1" + json_study = wrapper.get(f"{base_url}/studies/{study_id}").json() study_name = json_study.pop("name") study_version = str(json_study.pop("version")) json_study.pop("id") - study_settings = StudySettings(**json_study) + study_settings = _returns_study_settings(base_url, study_id, wrapper, False, None) study = Study(study_name, study_version, ServiceFactory(api_config, study_id, study_name), study_settings) + study.read_areas() + return study @@ -210,6 +212,11 @@ def service(self) -> BaseStudyService: return self._study_service def read_areas(self) -> list[Area]: + area_list = self._area_service.read_areas() + areas = dict() + for area in area_list: + areas.update({area.id: area}) + return self._area_service.read_areas() def get_areas(self) -> MappingProxyType[str, Area]: diff --git a/tests/antares/services/api_services/test_study_api.py b/tests/antares/services/api_services/test_study_api.py index 3823d623..bae548c4 100644 --- a/tests/antares/services/api_services/test_study_api.py +++ b/tests/antares/services/api_services/test_study_api.py @@ -201,33 +201,25 @@ def test_create_binding_constraint_fails(self): self.study.create_binding_constraint(name=constraint_name) def test_read_study_api(self): - study_id_test = "248bbb99-c909-47b7-b239-01f6f6ae7de7" - url = f"https://antares.com/api/v1/studies/{study_id_test}" + base_url = "https://antares.com/api/v1" + url = f"{base_url}/studies/{self.study_id}" + area_url = f"{url}/areas" json_study = { - "id": "248bbb99-c909-47b7-b239-01f6f6ae7de7", + "id": "22c52f44-4c2a-407b-862b-490887f93dd8", "name": "test_read_areas", "version": "880", - "created": "2024-11-12 14:05:17.323686", - "updated": "2024-11-13 12:57:41.194491", - "type": "rawstudy", - "owner": {"id": 2, "name": "test"}, - "groups": [{"id": "test", "name": "test"}], - "public_mode": "NONE", - "workspace": "default", - "managed": "true", - "archived": "false", - "horizon": "null", - "scenario": "null", - "status": "null", - "doc": "null", - "folder": "null", - "tags": [], } + settings = StudySettings() + settings.general_parameters = GeneralParameters(mode="Adequacy") + + config_urls = re.compile(f"https://antares.com/api/v1/studies/{self.study_id}/config/.*") + with requests_mock.Mocker() as mocker: mocker.get(url, json=json_study) - - actual_study = read_study_api(self.api, study_id_test) + mocker.get(config_urls, json={}) + mocker.get(area_url, json={}) + actual_study = read_study_api(self.api, self.study_id) expected_study_name = json_study.pop("name") expected_study_id = json_study.pop("id") From a729713581cb0bb587e3c7830e971f44c936ce43 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Tue, 3 Dec 2024 16:30:01 +0100 Subject: [PATCH 28/33] feat(api): adding read_study method, unit testing and integration testing of the method --- src/antares/model/study.py | 12 +-- src/antares/service/api_services/area_api.py | 1 - .../services/api_services/test_study_api.py | 98 ++++++++++++++++--- tests/integration/test_web_client.py | 14 +-- 4 files changed, 101 insertions(+), 24 deletions(-) diff --git a/src/antares/model/study.py b/src/antares/model/study.py index ae773cb0..e4fc70ee 100644 --- a/src/antares/model/study.py +++ b/src/antares/model/study.py @@ -179,7 +179,6 @@ def read_study_api(api_config: APIconf, study_id: str) -> "Study": study_name = json_study.pop("name") study_version = str(json_study.pop("version")) - json_study.pop("id") study_settings = _returns_study_settings(base_url, study_id, wrapper, False, None) study = Study(study_name, study_version, ServiceFactory(api_config, study_id, study_name), study_settings) @@ -212,12 +211,13 @@ def service(self) -> BaseStudyService: return self._study_service def read_areas(self) -> list[Area]: + """ + Syncronize the internal study object with the object written in an antares study + Returns: + """ area_list = self._area_service.read_areas() - areas = dict() - for area in area_list: - areas.update({area.id: area}) - - return self._area_service.read_areas() + self._areas = {area.id:area for area in area_list} + return area_list def get_areas(self) -> MappingProxyType[str, Area]: return MappingProxyType(dict(sorted(self._areas.items()))) diff --git a/src/antares/service/api_services/area_api.py b/src/antares/service/api_services/area_api.py index 767b1977..52b90552 100644 --- a/src/antares/service/api_services/area_api.py +++ b/src/antares/service/api_services/area_api.py @@ -560,7 +560,6 @@ def read_areas(self) -> List[Area]: base_api_url = f"{self._base_url}/studies/{self.study_id}/areas" ui_url = "ui=true" url_properties_form = "properties/form" - json_resp = self._wrapper.get(base_api_url + "?" + ui_url).json() for area in json_resp: area_url = base_api_url + "/" + f"{area}/" diff --git a/tests/antares/services/api_services/test_study_api.py b/tests/antares/services/api_services/test_study_api.py index bae548c4..430a88fd 100644 --- a/tests/antares/services/api_services/test_study_api.py +++ b/tests/antares/services/api_services/test_study_api.py @@ -11,11 +11,11 @@ # This file is part of the Antares project. +import re + import pytest import requests_mock -import re - from antares.api_conf.api_conf import APIconf from antares.exceptions.exceptions import ( AreaCreationError, @@ -201,38 +201,114 @@ def test_create_binding_constraint_fails(self): self.study.create_binding_constraint(name=constraint_name) def test_read_study_api(self): - base_url = "https://antares.com/api/v1" - url = f"{base_url}/studies/{self.study_id}" - area_url = f"{url}/areas" + json_study = { "id": "22c52f44-4c2a-407b-862b-490887f93dd8", "name": "test_read_areas", "version": "880", } - settings = StudySettings() - settings.general_parameters = GeneralParameters(mode="Adequacy") + json_area = { + "zone": { + "ui": {"x": 0,"y": 0,"color_r": 230,"color_g": 108, "color_b": 44,"layers": "0"}, + "layerX": {"0": 0}, + "layerY": { "0": 0}, + "layerColor": {"0": "230, 108, 44"} + } + } + + json_thermal = [ + { + "id": "therm_un", + "group": "Gas", + "name": "therm_un", + "enabled": "true", + "unitCount": 1, + "nominalCapacity": 0, + } + ] + + json_renewable = [ + { + "id": "test_renouvelable", + "group": "Solar Thermal", + "name": "test_renouvelable", + "enabled": "true", + "unitCount": 1, + "nominalCapacity": 0, + "tsInterpretation": "power-generation", + } + ] + + json_st_storage = [ + { + "id": "test_storage", + "group": "Pondage", + "name": "test_storage", + "injectionNominalCapacity": 0, + "withdrawalNominalCapacity": 0, + "reservoirCapacity": 0, + "efficiency": 1, + "initialLevel": 0.5, + "initialLevelOptim": "false", + "enabled": "true", + } + ] + + json_properties = { + "energyCostUnsupplied": 0, + "energyCostSpilled": 0, + "nonDispatchPower": "true", + "dispatchHydroPower": "true", + "otherDispatchPower": "true", + "filterSynthesis": [ + "weekly", + "daily", + "hourly", + "monthly", + "annual" + ], + "filterByYear": [ + "weekly", + "daily", + "hourly", + "monthly", + "annual" + ], + "adequacyPatchMode": "outside" + } config_urls = re.compile(f"https://antares.com/api/v1/studies/{self.study_id}/config/.*") + base_url = "https://antares.com/api/v1" + url = f"{base_url}/studies/{self.study_id}" + area_url = f"{url}/areas" + area_props_url = f"{area_url}/zone/properties/form" + thermal_url = f"{area_url}/zone/clusters/thermal" + renewable_url = f"{area_url}/zone/clusters/renewable" + storage_url = f"{area_url}/zone/storages" + with requests_mock.Mocker() as mocker: mocker.get(url, json=json_study) mocker.get(config_urls, json={}) - mocker.get(area_url, json={}) + mocker.get(area_url, json=json_area) + mocker.get(area_props_url, json=json_properties) + mocker.get(renewable_url, json=json_renewable) + mocker.get(thermal_url, json=json_thermal) + mocker.get(storage_url, json=json_st_storage) actual_study = read_study_api(self.api, self.study_id) expected_study_name = json_study.pop("name") expected_study_id = json_study.pop("id") expected_study_version = json_study.pop("version") - expected_study_settings = StudySettings(**json_study) expected_study = Study( expected_study_name, expected_study_version, ServiceFactory(self.api, expected_study_id, expected_study_name), - expected_study_settings, + None ) assert actual_study.name == expected_study.name assert actual_study.version == expected_study.version - assert actual_study.service.study_id == expected_study.service.study_id + assert actual_study.service.study_id == expected_study.service.study_id \ No newline at end of file diff --git a/tests/integration/test_web_client.py b/tests/integration/test_web_client.py index 3891033e..181a01c8 100644 --- a/tests/integration/test_web_client.py +++ b/tests/integration/test_web_client.py @@ -51,12 +51,6 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): study = create_study_api("antares-craft-test", "880", api_config) - actual_study = read_study_api(api_config, study.service.study_id) - - assert study.service.study_id == actual_study.service.study_id - assert study.name == actual_study.name - assert study.version == actual_study.version - # tests area creation with default values area_name = "FR" area_fr = study.create_area(area_name) @@ -98,6 +92,14 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): assert area_ui.x == area_ui.x assert area_ui.color_rgb == area_ui.color_rgb + # tests study reading method and comparing ids, name, areas and settings + actual_study = read_study_api(api_config, study.service.study_id) + + assert study.service.study_id == actual_study.service.study_id + assert study.name == actual_study.name + assert study.version == actual_study.version + assert study.get_areas()["fr"].id == actual_study.get_areas()["fr"].id + # tests area creation with properties properties = AreaProperties() properties.energy_cost_spilled = 100 From 7d35ba7116694f95c6bdfe7c54db36db63ac582b Mon Sep 17 00:00:00 2001 From: wahadameh Date: Tue, 3 Dec 2024 16:30:24 +0100 Subject: [PATCH 29/33] feat(api): adding read_study method, unit testing and integration testing of the method --- src/antares/model/study.py | 2 +- .../service/api_services/renewable_api.py | 9 +-- src/antares/service/base_services.py | 4 +- .../service/local_services/area_local.py | 4 +- .../service/local_services/renewable_local.py | 22 ++--- src/antares/tools/matrix_tool.py | 10 ++- .../services/api_services/test_study_api.py | 49 +++++------- .../services/local_services/test_area.py | 80 ++++++++++--------- 8 files changed, 84 insertions(+), 96 deletions(-) diff --git a/src/antares/model/study.py b/src/antares/model/study.py index e4fc70ee..b204c679 100644 --- a/src/antares/model/study.py +++ b/src/antares/model/study.py @@ -216,7 +216,7 @@ def read_areas(self) -> list[Area]: Returns: """ area_list = self._area_service.read_areas() - self._areas = {area.id:area for area in area_list} + self._areas = {area.id: area for area in area_list} return area_list def get_areas(self) -> MappingProxyType[str, Area]: diff --git a/src/antares/service/api_services/renewable_api.py b/src/antares/service/api_services/renewable_api.py index 5ad85fba..234a823a 100644 --- a/src/antares/service/api_services/renewable_api.py +++ b/src/antares/service/api_services/renewable_api.py @@ -53,14 +53,7 @@ def update_renewable_properties( 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" - ) + path = PurePosixPath("input") / "renewables" / "series" / f"{area_id}" / f"{cluster_id}" / "series" return get_matrix(f"{self._base_url}/studies/{self.study_id}/raw?path={path}", self._wrapper) except APIError as e: raise RenewableMatrixDownloadError(area_id, cluster_id, e.message) from e diff --git a/src/antares/service/base_services.py b/src/antares/service/base_services.py index c552828c..da89201c 100644 --- a/src/antares/service/base_services.py +++ b/src/antares/service/base_services.py @@ -521,9 +521,7 @@ def update_renewable_properties( pass @abstractmethod - def get_renewable_matrix( - self, cluster_id: str, area_id: str - ) -> pd.DataFrame: + def get_renewable_matrix(self, cluster_id: str, area_id: str) -> pd.DataFrame: """ Args: cluster_id: renewable cluster id to retrieve matrix diff --git a/src/antares/service/local_services/area_local.py b/src/antares/service/local_services/area_local.py index 22484f05..3ea986d1 100644 --- a/src/antares/service/local_services/area_local.py +++ b/src/antares/service/local_services/area_local.py @@ -320,7 +320,7 @@ def get_load_matrix(self, area: Area) -> pd.DataFrame: return read_timeseries(TimeSeriesFileType.LOAD, self.config.study_path, area_id=area.id) def get_solar_matrix(self, area: Area) -> pd.DataFrame: - return read_timeseries(TimeSeriesFileType.SOLAR, self.config.study_path, area_id=area.id) + return read_timeseries(TimeSeriesFileType.SOLAR, self.config.study_path, area_id=area.id) def get_wind_matrix(self, area: Area) -> pd.DataFrame: return read_timeseries(TimeSeriesFileType.WIND, self.config.study_path, area_id=area.id) @@ -330,7 +330,7 @@ def get_reserves_matrix(self, area: Area) -> pd.DataFrame: def get_misc_gen_matrix(self, area: Area) -> pd.DataFrame: return read_timeseries(TimeSeriesFileType.MISC_GEN, self.config.study_path, area_id=area.id) - + def read_areas(self) -> List[Area]: local_path = self.config.local_path areas_path = local_path / self.study_name / "input" / "areas" diff --git a/src/antares/service/local_services/renewable_local.py b/src/antares/service/local_services/renewable_local.py index 9e8b3001..2b92a678 100644 --- a/src/antares/service/local_services/renewable_local.py +++ b/src/antares/service/local_services/renewable_local.py @@ -33,15 +33,11 @@ def update_renewable_properties( self, renewable_cluster: RenewableCluster, properties: RenewableClusterProperties ) -> RenewableClusterProperties: raise NotImplementedError - - - def get_renewable_matrix( - self, - cluster_id: str, - area_id: str - ) -> pd.DataFrame: - return read_timeseries(TimeSeriesFileType.RENEWABLE_DATA_SERIES, self.config.study_path, area_id=area_id, cluster_id=cluster_id) + def get_renewable_matrix(self, cluster_id: str, area_id: str) -> pd.DataFrame: + return read_timeseries( + TimeSeriesFileType.RENEWABLE_DATA_SERIES, self.config.study_path, area_id=area_id, cluster_id=cluster_id + ) def read_renewables(self, area_id: str) -> List[RenewableCluster]: renewable_dict = IniFile(self.config.study_path, IniFileTypes.RENEWABLES_LIST_INI, area_name=area_id).ini_dict @@ -56,6 +52,12 @@ def read_renewables(self, area_id: str) -> List[RenewableCluster]: nominal_capacity=renewable_dict[renewable_cluster]["nominalcapacity"], ts_interpretation=renewable_dict[renewable_cluster]["ts-interpretation"], ) - renewables_clusters.append(RenewableCluster(renewable_service=self, area_id=area_id, name=renewable_dict[renewable_cluster]["name"], properties=renewable_properties.yield_renewable_cluster_properties())) + renewables_clusters.append( + RenewableCluster( + renewable_service=self, + area_id=area_id, + name=renewable_dict[renewable_cluster]["name"], + properties=renewable_properties.yield_renewable_cluster_properties(), + ) + ) return renewables_clusters - \ No newline at end of file diff --git a/src/antares/tools/matrix_tool.py b/src/antares/tools/matrix_tool.py index 634a4504..abc864a4 100644 --- a/src/antares/tools/matrix_tool.py +++ b/src/antares/tools/matrix_tool.py @@ -42,7 +42,13 @@ def df_read(path: Path) -> pd.DataFrame: return pd.read_csv(path, sep="\t", header=None) -def read_timeseries(ts_file_type: TimeSeriesFileType, study_path: Path, area_id: Optional[str] = None, constraint_id: Optional[str] = None, cluster_id: Optional[str] = None,) -> pd.DataFrame: +def read_timeseries( + ts_file_type: TimeSeriesFileType, + study_path: Path, + area_id: Optional[str] = None, + constraint_id: Optional[str] = None, + cluster_id: Optional[str] = None, +) -> pd.DataFrame: file_path = study_path / ( ts_file_type.value if not (area_id or constraint_id or cluster_id) @@ -53,4 +59,4 @@ def read_timeseries(ts_file_type: TimeSeriesFileType, study_path: Path, area_id: else: _time_series = pd.DataFrame() - return _time_series \ No newline at end of file + return _time_series diff --git a/tests/antares/services/api_services/test_study_api.py b/tests/antares/services/api_services/test_study_api.py index 430a88fd..2b2f24ee 100644 --- a/tests/antares/services/api_services/test_study_api.py +++ b/tests/antares/services/api_services/test_study_api.py @@ -11,11 +11,11 @@ # This file is part of the Antares project. -import re - import pytest import requests_mock +import re + from antares.api_conf.api_conf import APIconf from antares.exceptions.exceptions import ( AreaCreationError, @@ -201,7 +201,6 @@ def test_create_binding_constraint_fails(self): self.study.create_binding_constraint(name=constraint_name) def test_read_study_api(self): - json_study = { "id": "22c52f44-4c2a-407b-862b-490887f93dd8", "name": "test_read_areas", @@ -209,12 +208,12 @@ def test_read_study_api(self): } json_area = { - "zone": { - "ui": {"x": 0,"y": 0,"color_r": 230,"color_g": 108, "color_b": 44,"layers": "0"}, - "layerX": {"0": 0}, - "layerY": { "0": 0}, - "layerColor": {"0": "230, 108, 44"} - } + "zone": { + "ui": {"x": 0, "y": 0, "color_r": 230, "color_g": 108, "color_b": 44, "layers": "0"}, + "layerX": {"0": 0}, + "layerY": {"0": 0}, + "layerColor": {"0": "230, 108, 44"}, + } } json_thermal = [ @@ -256,26 +255,14 @@ def test_read_study_api(self): ] json_properties = { - "energyCostUnsupplied": 0, - "energyCostSpilled": 0, - "nonDispatchPower": "true", - "dispatchHydroPower": "true", - "otherDispatchPower": "true", - "filterSynthesis": [ - "weekly", - "daily", - "hourly", - "monthly", - "annual" - ], - "filterByYear": [ - "weekly", - "daily", - "hourly", - "monthly", - "annual" - ], - "adequacyPatchMode": "outside" + "energyCostUnsupplied": 0, + "energyCostSpilled": 0, + "nonDispatchPower": "true", + "dispatchHydroPower": "true", + "otherDispatchPower": "true", + "filterSynthesis": ["weekly", "daily", "hourly", "monthly", "annual"], + "filterByYear": ["weekly", "daily", "hourly", "monthly", "annual"], + "adequacyPatchMode": "outside", } config_urls = re.compile(f"https://antares.com/api/v1/studies/{self.study_id}/config/.*") @@ -306,9 +293,9 @@ def test_read_study_api(self): expected_study_name, expected_study_version, ServiceFactory(self.api, expected_study_id, expected_study_name), - None + None, ) assert actual_study.name == expected_study.name assert actual_study.version == expected_study.version - assert actual_study.service.study_id == expected_study.service.study_id \ No newline at end of file + assert actual_study.service.study_id == expected_study.service.study_id diff --git a/tests/antares/services/local_services/test_area.py b/tests/antares/services/local_services/test_area.py index ea321bee..af32cf9e 100644 --- a/tests/antares/services/local_services/test_area.py +++ b/tests/antares/services/local_services/test_area.py @@ -1122,13 +1122,13 @@ def _write_file(_file_path, _time_series) -> None: _file_path.parent.mkdir(parents=True, exist_ok=True) _time_series.to_csv(_file_path, sep="\t", header=False, index=False, encoding="utf-8") + class TestReadLoad: def test_read_load_local(self, local_study_w_areas): study_path = local_study_w_areas.service.config.study_path local_study_object = read_study_local(study_path) areas = local_study_object.read_areas() - for area in areas: expected_time_serie = pd.DataFrame( [ @@ -1158,15 +1158,14 @@ def test_read_renewable_local(self, local_study_with_renewable): local_study_object = read_study_local(study_path) areas = local_study_object.read_areas() - - for area in areas: expected_time_serie = pd.DataFrame( - [ - [-9999999980506447872, 0, 9999999980506447872], - [0, area.id, 0], - ] - , dtype="object") + [ + [-9999999980506447872, 0, 9999999980506447872], + [0, area.id, 0], + ], + dtype="object", + ) renewable_list = area.read_renewables(area.id) if renewable_list: @@ -1183,9 +1182,9 @@ def test_read_renewable_local(self, local_study_with_renewable): assert renewable.properties.group.value == "Other RES 1" # Create folder and file for timeserie. - cluster_path = study_path / "input" / "renewables" / "series"/ Path(area.id) / Path(renewable.id) + cluster_path = study_path / "input" / "renewables" / "series" / Path(area.id) / Path(renewable.id) os.makedirs(cluster_path, exist_ok=True) - series_path = cluster_path / 'series.txt' + series_path = cluster_path / "series.txt" _write_file(series_path, expected_time_serie) # Check matrix @@ -1199,14 +1198,14 @@ def test_read_solar_local(self, local_study_w_areas): local_study_object = read_study_local(study_path) areas = local_study_object.read_areas() - for area in areas: expected_time_serie = pd.DataFrame( - [ - [-9999999980506447872, 0, 9999999980506447872], - [0, area.id, 0], - ] - , dtype="object") + [ + [-9999999980506447872, 0, 9999999980506447872], + [0, area.id, 0], + ], + dtype="object", + ) file_path = study_path / "input" / "solar" / "series" / f"solar_{area.id}.txt" _write_file(file_path, expected_time_serie) @@ -1221,22 +1220,23 @@ def test_read_solar_local(self, local_study_w_areas): matrix = area.get_solar_matrix() pd.testing.assert_frame_equal(matrix, expected_time_serie) + class TestReadReserves: def test_read_reserve_local(self, local_study_w_areas): study_path = local_study_w_areas.service.config.study_path local_study_object = read_study_local(study_path) areas = local_study_object.read_areas() - for area in areas: expected_time_serie = pd.DataFrame( - [ - [-9999999980506447872, 0, 9999999980506447872], - [0, area.id, 0], - ] - , dtype="object") + [ + [-9999999980506447872, 0, 9999999980506447872], + [0, area.id, 0], + ], + dtype="object", + ) - file_path = study_path / "input" / "reserves" / f"{area.id}.txt" + file_path = study_path / "input" / "reserves" / f"{area.id}.txt" _write_file(file_path, expected_time_serie) matrix = area.get_reserves_matrix() @@ -1244,26 +1244,27 @@ def test_read_reserve_local(self, local_study_w_areas): expected_time_serie = pd.DataFrame([]) for area in areas: - file_path = study_path / "input" / "reserves" / f"{area.id}.txt" + file_path = study_path / "input" / "reserves" / f"{area.id}.txt" _write_file(file_path, expected_time_serie) matrix = area.get_reserves_matrix() pd.testing.assert_frame_equal(matrix, expected_time_serie) + class TestReadWind: def test_read_wind_local(self, local_study_w_areas): study_path = local_study_w_areas.service.config.study_path local_study_object = read_study_local(study_path) areas = local_study_object.read_areas() - for area in areas: expected_time_serie = pd.DataFrame( - [ - [-9999999980506447872, 0, 9999999980506447872], - [0, area.id, 0], - ] - , dtype="object") - + [ + [-9999999980506447872, 0, 9999999980506447872], + [0, area.id, 0], + ], + dtype="object", + ) + file_path = study_path / "input" / "wind" / "series" / f"wind_{area.id}.txt" _write_file(file_path, expected_time_serie) @@ -1277,21 +1278,22 @@ def test_read_wind_local(self, local_study_w_areas): matrix = area.get_wind_matrix() pd.testing.assert_frame_equal(matrix, expected_time_serie) + class TestReadmisc_gen: def test_read_misc_gen_local(self, local_study_w_areas): study_path = local_study_w_areas.service.config.study_path local_study_object = read_study_local(study_path) areas = local_study_object.read_areas() - for area in areas: expected_time_serie = pd.DataFrame( - [ - [-9999999980506447872, 0, 9999999980506447872], - [0, area.id, 0], - ] - , dtype="object") - + [ + [-9999999980506447872, 0, 9999999980506447872], + [0, area.id, 0], + ], + dtype="object", + ) + file_path = study_path / "input" / "misc-gen" / f"miscgen-{area.id}.txt" _write_file(file_path, expected_time_serie) @@ -1303,4 +1305,4 @@ def test_read_misc_gen_local(self, local_study_w_areas): file_path = study_path / "input" / "misc-gen" / f"miscgen-{area.id}.txt" _write_file(file_path, expected_time_serie) matrix = area.get_misc_gen_matrix() - pd.testing.assert_frame_equal(matrix, expected_time_serie) \ No newline at end of file + pd.testing.assert_frame_equal(matrix, expected_time_serie) From 4b4edd43b947d5a2457ffc28d45f7da1d07e342a Mon Sep 17 00:00:00 2001 From: wahadameh Date: Wed, 4 Dec 2024 11:11:33 +0100 Subject: [PATCH 30/33] feat(api): adding read_study method, unit testing and integration testing of the method --- src/antares/model/study.py | 4 +-- .../services/api_services/test_study_api.py | 4 +-- tests/integration/test_web_client.py | 28 +++++++++++++------ 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/antares/model/study.py b/src/antares/model/study.py index b204c679..b105ff63 100644 --- a/src/antares/model/study.py +++ b/src/antares/model/study.py @@ -212,8 +212,8 @@ def service(self) -> BaseStudyService: def read_areas(self) -> list[Area]: """ - Syncronize the internal study object with the object written in an antares study - Returns: + Synchronize the internal study object with the actual object written in an antares study + Returns: the synchronized area list """ area_list = self._area_service.read_areas() self._areas = {area.id: area for area in area_list} diff --git a/tests/antares/services/api_services/test_study_api.py b/tests/antares/services/api_services/test_study_api.py index 2b2f24ee..05168b1d 100644 --- a/tests/antares/services/api_services/test_study_api.py +++ b/tests/antares/services/api_services/test_study_api.py @@ -207,7 +207,7 @@ def test_read_study_api(self): "version": "880", } - json_area = { + json_ui = { "zone": { "ui": {"x": 0, "y": 0, "color_r": 230, "color_g": 108, "color_b": 44, "layers": "0"}, "layerX": {"0": 0}, @@ -278,7 +278,7 @@ def test_read_study_api(self): with requests_mock.Mocker() as mocker: mocker.get(url, json=json_study) mocker.get(config_urls, json={}) - mocker.get(area_url, json=json_area) + mocker.get(area_url, json=json_ui) mocker.get(area_props_url, json=json_properties) mocker.get(renewable_url, json=json_renewable) mocker.get(thermal_url, json=json_thermal) diff --git a/tests/integration/test_web_client.py b/tests/integration/test_web_client.py index 181a01c8..6e9807eb 100644 --- a/tests/integration/test_web_client.py +++ b/tests/integration/test_web_client.py @@ -92,14 +92,6 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): assert area_ui.x == area_ui.x assert area_ui.color_rgb == area_ui.color_rgb - # tests study reading method and comparing ids, name, areas and settings - actual_study = read_study_api(api_config, study.service.study_id) - - assert study.service.study_id == actual_study.service.study_id - assert study.name == actual_study.name - assert study.version == actual_study.version - assert study.get_areas()["fr"].id == actual_study.get_areas()["fr"].id - # tests area creation with properties properties = AreaProperties() properties.energy_cost_spilled = 100 @@ -227,6 +219,26 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): assert actual_hydro.area_id == area_fr.id assert actual_hydro.properties == area_fr.hydro.properties + # tests study reading method and comparing ids, name, areas and settings + actual_study = read_study_api(api_config, study.service.study_id) + + assert study.service.study_id == actual_study.service.study_id + assert study.name == actual_study.name + assert study.version == actual_study.version + assert list(study.get_areas().keys()) == list(actual_study.get_areas().keys()) + assert ( + study.get_areas()["fr"].get_thermals().get("cluster_test").id + == actual_study.get_areas()["fr"].get_thermals().get("cluster_test").id + ) + assert ( + study.get_areas()["fr"].get_renewables().get("cluster_test").id + == actual_study.get_areas()["fr"].get_renewables().get("cluster_test").id + ) + assert ( + study.get_areas()["fr"].get_st_storages().get("cluster_test").id + == actual_study.get_areas()["fr"].get_st_storages().get("cluster_test").id + ) + # test short term storage creation with properties st_storage_name = "wind_onshore" storage_properties = STStorageProperties(reservoir_capacity=0.5) From e140264b1339307cb3890506e361c485514f8079 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Wed, 4 Dec 2024 14:27:55 +0100 Subject: [PATCH 31/33] feat(api): adding read_study method, unit testing and integration testing of the method --- .../services/api_services/test_study_api.py | 57 ++----------------- tests/integration/test_web_client.py | 15 +++-- 2 files changed, 13 insertions(+), 59 deletions(-) diff --git a/tests/antares/services/api_services/test_study_api.py b/tests/antares/services/api_services/test_study_api.py index 05168b1d..98c7cc96 100644 --- a/tests/antares/services/api_services/test_study_api.py +++ b/tests/antares/services/api_services/test_study_api.py @@ -216,55 +216,6 @@ def test_read_study_api(self): } } - json_thermal = [ - { - "id": "therm_un", - "group": "Gas", - "name": "therm_un", - "enabled": "true", - "unitCount": 1, - "nominalCapacity": 0, - } - ] - - json_renewable = [ - { - "id": "test_renouvelable", - "group": "Solar Thermal", - "name": "test_renouvelable", - "enabled": "true", - "unitCount": 1, - "nominalCapacity": 0, - "tsInterpretation": "power-generation", - } - ] - - json_st_storage = [ - { - "id": "test_storage", - "group": "Pondage", - "name": "test_storage", - "injectionNominalCapacity": 0, - "withdrawalNominalCapacity": 0, - "reservoirCapacity": 0, - "efficiency": 1, - "initialLevel": 0.5, - "initialLevelOptim": "false", - "enabled": "true", - } - ] - - json_properties = { - "energyCostUnsupplied": 0, - "energyCostSpilled": 0, - "nonDispatchPower": "true", - "dispatchHydroPower": "true", - "otherDispatchPower": "true", - "filterSynthesis": ["weekly", "daily", "hourly", "monthly", "annual"], - "filterByYear": ["weekly", "daily", "hourly", "monthly", "annual"], - "adequacyPatchMode": "outside", - } - config_urls = re.compile(f"https://antares.com/api/v1/studies/{self.study_id}/config/.*") base_url = "https://antares.com/api/v1" @@ -279,10 +230,10 @@ def test_read_study_api(self): mocker.get(url, json=json_study) mocker.get(config_urls, json={}) mocker.get(area_url, json=json_ui) - mocker.get(area_props_url, json=json_properties) - mocker.get(renewable_url, json=json_renewable) - mocker.get(thermal_url, json=json_thermal) - mocker.get(storage_url, json=json_st_storage) + mocker.get(area_props_url, json={}) + mocker.get(renewable_url, json=[]) + mocker.get(thermal_url, json=[]) + mocker.get(storage_url, json=[]) actual_study = read_study_api(self.api, self.study_id) expected_study_name = json_study.pop("name") diff --git a/tests/integration/test_web_client.py b/tests/integration/test_web_client.py index 6e9807eb..a2ad9336 100644 --- a/tests/integration/test_web_client.py +++ b/tests/integration/test_web_client.py @@ -226,17 +226,20 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): assert study.name == actual_study.name assert study.version == actual_study.version assert list(study.get_areas().keys()) == list(actual_study.get_areas().keys()) + + expected_area_fr = study.get_areas()["fr"] + actual_area_fr = actual_study.get_areas()["fr"] + assert ( + list(expected_area_fr.get_thermals()) == list(actual_area_fr.get_thermals()) + ) assert ( - study.get_areas()["fr"].get_thermals().get("cluster_test").id - == actual_study.get_areas()["fr"].get_thermals().get("cluster_test").id + list(expected_area_fr.get_renewables()) == list(actual_area_fr.get_renewables()) ) assert ( - study.get_areas()["fr"].get_renewables().get("cluster_test").id - == actual_study.get_areas()["fr"].get_renewables().get("cluster_test").id + list(expected_area_fr.get_st_storages()) == list(actual_area_fr.get_st_storages()) ) assert ( - study.get_areas()["fr"].get_st_storages().get("cluster_test").id - == actual_study.get_areas()["fr"].get_st_storages().get("cluster_test").id + list(study.get_settings()) == list(actual_study.get_settings()) ) # test short term storage creation with properties From 95b08fc9f22c487eb738348e3f1e2b262351ad6e Mon Sep 17 00:00:00 2001 From: wahadameh Date: Wed, 4 Dec 2024 14:29:29 +0100 Subject: [PATCH 32/33] feat(api): adding read_study method, unit testing and integration testing of the method --- tests/integration/test_web_client.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/integration/test_web_client.py b/tests/integration/test_web_client.py index a2ad9336..ca93f91f 100644 --- a/tests/integration/test_web_client.py +++ b/tests/integration/test_web_client.py @@ -229,18 +229,10 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): expected_area_fr = study.get_areas()["fr"] actual_area_fr = actual_study.get_areas()["fr"] - assert ( - list(expected_area_fr.get_thermals()) == list(actual_area_fr.get_thermals()) - ) - assert ( - list(expected_area_fr.get_renewables()) == list(actual_area_fr.get_renewables()) - ) - assert ( - list(expected_area_fr.get_st_storages()) == list(actual_area_fr.get_st_storages()) - ) - assert ( - list(study.get_settings()) == list(actual_study.get_settings()) - ) + assert list(expected_area_fr.get_thermals()) == list(actual_area_fr.get_thermals()) + assert list(expected_area_fr.get_renewables()) == list(actual_area_fr.get_renewables()) + assert list(expected_area_fr.get_st_storages()) == list(actual_area_fr.get_st_storages()) + assert list(study.get_settings()) == list(actual_study.get_settings()) # test short term storage creation with properties st_storage_name = "wind_onshore" From 78c93c16fa4e79adf6c3a93cbe03d85558b66e25 Mon Sep 17 00:00:00 2001 From: wahadameh Date: Wed, 4 Dec 2024 14:47:32 +0100 Subject: [PATCH 33/33] feat(api): adding read_study method, unit testing and integration testing of the method --- tests/integration/test_web_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_web_client.py b/tests/integration/test_web_client.py index ca93f91f..300f8301 100644 --- a/tests/integration/test_web_client.py +++ b/tests/integration/test_web_client.py @@ -232,7 +232,7 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop): assert list(expected_area_fr.get_thermals()) == list(actual_area_fr.get_thermals()) assert list(expected_area_fr.get_renewables()) == list(actual_area_fr.get_renewables()) assert list(expected_area_fr.get_st_storages()) == list(actual_area_fr.get_st_storages()) - assert list(study.get_settings()) == list(actual_study.get_settings()) + assert study.get_settings() == actual_study.get_settings() # test short term storage creation with properties st_storage_name = "wind_onshore"