From 7f10ab2964ad4feffa8f3b35b4ca1b658c2b5c2f Mon Sep 17 00:00:00 2001 From: MartinBelthle Date: Fri, 7 Feb 2025 16:45:06 +0100 Subject: [PATCH] refactor(settings): create settings service (#76) --- src/antares/craft/model/study.py | 48 ++++---- .../service/api_services/services/settings.py | 103 +++++++++++------- .../craft/service/api_services/study_api.py | 9 -- src/antares/craft/service/base_services.py | 29 +++-- .../local_services/services/settings.py | 18 ++- .../service/local_services/study_local.py | 5 - src/antares/craft/service/service_factory.py | 12 ++ 7 files changed, 137 insertions(+), 87 deletions(-) diff --git a/src/antares/craft/model/study.py b/src/antares/craft/model/study.py index 83dd2992..347b4d2f 100644 --- a/src/antares/craft/model/study.py +++ b/src/antares/craft/model/study.py @@ -16,7 +16,7 @@ from pathlib import Path, PurePath from types import MappingProxyType -from typing import List, Optional, Union +from typing import List, Optional import pandas as pd @@ -40,9 +40,8 @@ from antares.craft.model.output import Output from antares.craft.model.settings.study_settings import StudySettings from antares.craft.model.simulation import AntaresSimulationParameters, Job -from antares.craft.service.api_services.services.settings import read_study_settings_api from antares.craft.service.base_services import BaseStudyService -from antares.craft.service.local_services.services.settings import edit_study_settings, read_study_settings_local +from antares.craft.service.local_services.services.settings import edit_study_settings from antares.craft.service.service_factory import ServiceFactory from antares.craft.tools.ini_tool import IniFile, InitializationFilesTypes @@ -83,10 +82,11 @@ def create_study_api( response = wrapper.post(url) study_id = response.json() # Settings part - study_settings = None if settings else read_study_settings_api(base_url, study_id, wrapper) - study = Study(study_name, version, ServiceFactory(api_config, study_id), study_settings) + study = Study(study_name, version, ServiceFactory(api_config, study_id)) if settings: study.update_settings(settings) + else: + study.read_settings() # Move part if parent_path: study.move(parent_path) @@ -177,14 +177,15 @@ def create_study_local( _create_correlation_ini_files(study_directory) logging.info(f"Study successfully created: {study_name}") - new_settings = edit_study_settings(study_directory, settings, update=False) - return Study( + study = Study( name=study_name, version=version, service_factory=ServiceFactory(config=local_config, study_name=study_name), - settings=new_settings, path=study_directory, ) + # We need to create the file with default value + study._settings = edit_study_settings(study_directory, settings, False) + return study def read_study_local(study_directory: Path) -> "Study": @@ -210,15 +211,14 @@ def _directory_not_exists(local_path: Path) -> None: local_config = LocalConfiguration(study_directory.parent, study_directory.name) - settings = read_study_settings_local(study_directory) - - return Study( + study = Study( name=study_params["caption"], version=study_params["version"], service_factory=ServiceFactory(config=local_config, study_name=study_params["caption"]), path=study_directory, - settings=settings, ) + study.read_settings() + return study def read_study_api(api_config: APIconf, study_id: str) -> "Study": @@ -232,11 +232,9 @@ def read_study_api(api_config: APIconf, study_id: str) -> "Study": path = json_study.pop("folder") pure_path = PurePath(path) if path else PurePath(".") - study_settings = read_study_settings_api(base_url, study_id, wrapper) - study = Study( - study_name, study_version, ServiceFactory(api_config, study_id, study_name), study_settings, pure_path - ) + study = Study(study_name, study_version, ServiceFactory(api_config, study_id, study_name), pure_path) + study.read_settings() study.read_areas() study.read_outputs() study.read_binding_constraints() @@ -265,7 +263,6 @@ def __init__( name: str, version: str, service_factory: ServiceFactory, - settings: Union[StudySettings, None] = None, path: PurePath = PurePath("."), ): self.name = name @@ -276,7 +273,8 @@ def __init__( self._link_service = service_factory.create_link_service() self._run_service = service_factory.create_run_service() self._binding_constraints_service = service_factory.create_binding_constraints_service() - self._settings = settings or StudySettings() + self._settings_service = service_factory.create_settings_service() + self._settings = StudySettings() self._areas: dict[str, Area] = dict() self._links: dict[str, Link] = dict() self._binding_constraints: dict[str, BindingConstraint] = dict() @@ -298,7 +296,15 @@ def read_areas(self) -> list[Area]: def read_links(self) -> list[Link]: link_list = self._link_service.read_links() self._links = {link.id: link for link in link_list} - return self._link_service.read_links() + return link_list + + def read_settings(self) -> StudySettings: + study_settings = self._settings_service.read_study_settings() + self._settings = study_settings + return study_settings + + def update_settings(self, settings: StudySettings) -> None: + self._settings = self._settings_service.edit_study_settings(settings) def get_areas(self) -> MappingProxyType[str, Area]: return MappingProxyType(dict(sorted(self._areas.items()))) @@ -389,10 +395,6 @@ def read_binding_constraints(self) -> list[BindingConstraint]: self._binding_constraints = {constraint.id: constraint for constraint in constraints} return constraints - def update_settings(self, settings: StudySettings) -> None: - self._study_service.update_study_settings(settings) - self._settings = settings - def delete_binding_constraint(self, constraint: BindingConstraint) -> None: self._study_service.delete_binding_constraint(constraint) self._binding_constraints.pop(constraint.id) diff --git a/src/antares/craft/service/api_services/services/settings.py b/src/antares/craft/service/api_services/services/settings.py index 0d5eb0c3..6f241518 100644 --- a/src/antares/craft/service/api_services/services/settings.py +++ b/src/antares/craft/service/api_services/services/settings.py @@ -11,8 +11,9 @@ # This file is part of the Antares project. from dataclasses import asdict +from antares.craft.api_conf.api_conf import APIconf from antares.craft.api_conf.request_wrapper import RequestWrapper -from antares.craft.exceptions.exceptions import APIError, StudySettingsReadError +from antares.craft.exceptions.exceptions import APIError, StudySettingsReadError, StudySettingsUpdateError from antares.craft.model.settings.playlist_parameters import PlaylistParameters from antares.craft.model.settings.study_settings import StudySettings from antares.craft.service.api_services.models.settings import ( @@ -22,6 +23,29 @@ OptimizationParametersAPI, ThematicTrimmingParametersAPI, ) +from antares.craft.service.base_services import BaseStudySettingsService + + +class StudySettingsAPIService(BaseStudySettingsService): + def __init__(self, config: APIconf, study_id: str): + super().__init__() + self.config = config + self.study_id = study_id + self._base_url = f"{self.config.get_host()}/api/v1" + self._wrapper = RequestWrapper(self.config.set_up_api_conf()) + + def edit_study_settings(self, settings: StudySettings) -> StudySettings: + try: + edit_study_settings(self._base_url, self.study_id, self._wrapper, settings) + return settings + except APIError as e: + raise StudySettingsUpdateError(self.study_id, e.message) from e + + def read_study_settings(self) -> StudySettings: + try: + return read_study_settings_api(self._base_url, self.study_id, self._wrapper) + except APIError as e: + raise StudySettingsReadError(self.study_id, e.message) from e def edit_study_settings(base_url: str, study_id: str, wrapper: RequestWrapper, settings: StudySettings) -> None: @@ -79,51 +103,48 @@ def edit_study_settings(base_url: str, study_id: str, wrapper: RequestWrapper, s def read_study_settings_api(base_url: str, study_id: str, wrapper: RequestWrapper) -> StudySettings: settings_base_url = f"{base_url}/studies/{study_id}/config" - try: - # thematic trimming - thematic_trimming_url = f"{settings_base_url}/thematictrimming/form" - response = wrapper.get(thematic_trimming_url) - thematic_trimming_api_model = ThematicTrimmingParametersAPI.model_validate(response.json()) - thematic_trimming_parameters = thematic_trimming_api_model.to_user_model() - # playlist - playlist_url = f"{settings_base_url}/playlist/form" - response = wrapper.get(playlist_url) - json_response = response.json() - user_playlist = {} - for key, value in json_response.items(): - user_playlist[int(key)] = PlaylistParameters(**value) + # thematic trimming + thematic_trimming_url = f"{settings_base_url}/thematictrimming/form" + response = wrapper.get(thematic_trimming_url) + thematic_trimming_api_model = ThematicTrimmingParametersAPI.model_validate(response.json()) + thematic_trimming_parameters = thematic_trimming_api_model.to_user_model() - # optimization - optimization_url = f"{settings_base_url}/optimization/form" - response = wrapper.get(optimization_url) - optimization_api_model = OptimizationParametersAPI.model_validate(response.json()) - optimization_parameters = optimization_api_model.to_user_model() + # playlist + playlist_url = f"{settings_base_url}/playlist/form" + response = wrapper.get(playlist_url) + json_response = response.json() + user_playlist = {} + for key, value in json_response.items(): + user_playlist[int(key)] = PlaylistParameters(**value) - # general and timeseries - general_url = f"{settings_base_url}/general/form" - response = wrapper.get(general_url) - general_api_model = GeneralParametersAPI.model_validate(response.json()) - timeseries_url = f"{base_url}/studies/{study_id}/timeseries/config" - response = wrapper.get(timeseries_url) - nb_ts_thermal = response.json()["thermal"]["number"] - general_parameters = general_api_model.to_user_model(nb_ts_thermal) - - # advanced and seed parameters - advanced_parameters_url = f"{settings_base_url}/advancedparameters/form" - response = wrapper.get(advanced_parameters_url) - advanced_parameters_api_model = AdvancedAndSeedParametersAPI.model_validate(response.json()) - seed_parameters = advanced_parameters_api_model.to_user_seed_parameters_model() - advanced_parameters = advanced_parameters_api_model.to_user_advanced_parameters_model() + # optimization + optimization_url = f"{settings_base_url}/optimization/form" + response = wrapper.get(optimization_url) + optimization_api_model = OptimizationParametersAPI.model_validate(response.json()) + optimization_parameters = optimization_api_model.to_user_model() - # adequacy patch - adequacy_patch_url = f"{settings_base_url}/adequacypatch/form" - response = wrapper.get(adequacy_patch_url) - adequacy_patch_api_model = AdequacyPatchParametersAPI.model_validate(response.json()) - adequacy_patch_parameters = adequacy_patch_api_model.to_user_model() + # general and timeseries + general_url = f"{settings_base_url}/general/form" + response = wrapper.get(general_url) + general_api_model = GeneralParametersAPI.model_validate(response.json()) + timeseries_url = f"{base_url}/studies/{study_id}/timeseries/config" + response = wrapper.get(timeseries_url) + nb_ts_thermal = response.json()["thermal"]["number"] + general_parameters = general_api_model.to_user_model(nb_ts_thermal) + + # advanced and seed parameters + advanced_parameters_url = f"{settings_base_url}/advancedparameters/form" + response = wrapper.get(advanced_parameters_url) + advanced_parameters_api_model = AdvancedAndSeedParametersAPI.model_validate(response.json()) + seed_parameters = advanced_parameters_api_model.to_user_seed_parameters_model() + advanced_parameters = advanced_parameters_api_model.to_user_advanced_parameters_model() - except APIError as e: - raise StudySettingsReadError(study_id, e.message) from e + # adequacy patch + adequacy_patch_url = f"{settings_base_url}/adequacypatch/form" + response = wrapper.get(adequacy_patch_url) + adequacy_patch_api_model = AdequacyPatchParametersAPI.model_validate(response.json()) + adequacy_patch_parameters = adequacy_patch_api_model.to_user_model() return StudySettings( general_parameters=general_parameters, diff --git a/src/antares/craft/service/api_services/study_api.py b/src/antares/craft/service/api_services/study_api.py index 39bd44a5..ce4ccb5c 100644 --- a/src/antares/craft/service/api_services/study_api.py +++ b/src/antares/craft/service/api_services/study_api.py @@ -23,7 +23,6 @@ OutputsRetrievalError, StudyDeletionError, StudyMoveError, - StudySettingsUpdateError, StudyVariantCreationError, TaskFailedError, TaskTimeOutError, @@ -31,8 +30,6 @@ ) from antares.craft.model.binding_constraint import BindingConstraint from antares.craft.model.output import Output -from antares.craft.model.settings.study_settings import StudySettings -from antares.craft.service.api_services.services.settings import edit_study_settings from antares.craft.service.api_services.utils import wait_task_completion from antares.craft.service.base_services import BaseOutputService, BaseStudyService @@ -64,12 +61,6 @@ def output_service(self) -> Optional[BaseOutputService]: def set_output_service(self, output_service: BaseOutputService) -> None: self._output_service = output_service - def update_study_settings(self, settings: StudySettings) -> None: - try: - edit_study_settings(self._base_url, self.study_id, self._wrapper, settings) - except APIError as e: - raise StudySettingsUpdateError(self.study_id, e.message) from e - def delete_binding_constraint(self, constraint: BindingConstraint) -> None: url = f"{self._base_url}/studies/{self.study_id}/bindingconstraints/{constraint.id}" try: diff --git a/src/antares/craft/service/base_services.py b/src/antares/craft/service/base_services.py index 591f0c31..e841d495 100644 --- a/src/antares/craft/service/base_services.py +++ b/src/antares/craft/service/base_services.py @@ -563,14 +563,6 @@ def config(self) -> BaseConfiguration: """The configuration of the study.""" pass - @abstractmethod - def update_study_settings(self, settings: StudySettings) -> None: - """ - Args: - settings: new study settings. Only registered fields will be updated. - """ - pass - @abstractmethod def delete_binding_constraint(self, constraint: BindingConstraint) -> None: """ @@ -752,3 +744,24 @@ def aggregate_values( Returns: Pandas DataFrame corresponding to the aggregated raw data """ pass + + +class BaseStudySettingsService(ABC): + @abstractmethod + def edit_study_settings(self, settings: StudySettings) -> StudySettings: + """ + Edit the settings for a given study + + Args: + settings: the new Settings for the study + + Returns: the new Settings for the study + """ + pass + + @abstractmethod + def read_study_settings(self) -> StudySettings: + """ + Reads the settings of a study + """ + pass diff --git a/src/antares/craft/service/local_services/services/settings.py b/src/antares/craft/service/local_services/services/settings.py index 69ef20f9..22892d79 100644 --- a/src/antares/craft/service/local_services/services/settings.py +++ b/src/antares/craft/service/local_services/services/settings.py @@ -11,7 +11,9 @@ # This file is part of the Antares project. from pathlib import Path +from typing import Any +from antares.craft.config.local_configuration import LocalConfiguration from antares.craft.model.settings.adequacy_patch import AdequacyPatchParameters from antares.craft.model.settings.advanced_parameters import ( AdvancedParameters, @@ -22,6 +24,7 @@ OptimizationParameters, ) from antares.craft.model.settings.study_settings import StudySettings +from antares.craft.service.base_services import BaseStudySettingsService from antares.craft.service.local_services.models.settings import ( AdequacyPatchParametersLocal, AdvancedAndSeedParametersLocal, @@ -34,7 +37,20 @@ from antares.craft.tools.ini_tool import IniFile, InitializationFilesTypes -def read_study_settings_local(study_directory: Path) -> StudySettings: +class StudySettingsLocalService(BaseStudySettingsService): + def __init__(self, config: LocalConfiguration, study_name: str, **kwargs: Any) -> None: + super().__init__(**kwargs) + self.config = config + self.study_name = study_name + + def edit_study_settings(self, settings: StudySettings) -> StudySettings: + return edit_study_settings(self.config.study_path, settings, update=True) + + def read_study_settings(self) -> StudySettings: + return read_study_settings(self.config.study_path) + + +def read_study_settings(study_directory: Path) -> StudySettings: general_data_ini = IniFile(study_directory, InitializationFilesTypes.GENERAL) ini_content = general_data_ini.ini_dict diff --git a/src/antares/craft/service/local_services/study_local.py b/src/antares/craft/service/local_services/study_local.py index 847c3933..7f9575b1 100644 --- a/src/antares/craft/service/local_services/study_local.py +++ b/src/antares/craft/service/local_services/study_local.py @@ -15,9 +15,7 @@ from antares.craft.config.local_configuration import LocalConfiguration from antares.craft.model.binding_constraint import BindingConstraint from antares.craft.model.output import Output -from antares.craft.model.settings.study_settings import StudySettings from antares.craft.service.base_services import BaseOutputService, BaseStudyService -from antares.craft.service.local_services.services.settings import edit_study_settings if TYPE_CHECKING: from antares.craft.model.study import Study @@ -45,9 +43,6 @@ def output_service(self) -> Optional[BaseOutputService]: def set_output_service(self, output_service: BaseOutputService) -> None: self._output_service = output_service - def update_study_settings(self, settings: StudySettings) -> None: - edit_study_settings(self.config.study_path, settings, update=True) - def delete_binding_constraint(self, constraint: BindingConstraint) -> None: raise NotImplementedError diff --git a/src/antares/craft/service/service_factory.py b/src/antares/craft/service/service_factory.py index 0280a0f1..90e1ef67 100644 --- a/src/antares/craft/service/service_factory.py +++ b/src/antares/craft/service/service_factory.py @@ -20,6 +20,7 @@ from antares.craft.service.api_services.output_api import OutputApiService from antares.craft.service.api_services.renewable_api import RenewableApiService from antares.craft.service.api_services.run_api import RunApiService +from antares.craft.service.api_services.services.settings import StudySettingsAPIService from antares.craft.service.api_services.st_storage_api import ShortTermStorageApiService from antares.craft.service.api_services.study_api import StudyApiService from antares.craft.service.api_services.thermal_api import ThermalApiService @@ -33,6 +34,7 @@ BaseRunService, BaseShortTermStorageService, BaseStudyService, + BaseStudySettingsService, BaseThermalService, ) from antares.craft.service.local_services.area_local import AreaLocalService @@ -42,6 +44,7 @@ from antares.craft.service.local_services.output_local import OutputLocalService from antares.craft.service.local_services.renewable_local import RenewableLocalService from antares.craft.service.local_services.run_local import RunLocalService +from antares.craft.service.local_services.services.settings import StudySettingsLocalService from antares.craft.service.local_services.st_storage_local import ShortTermStorageLocalService from antares.craft.service.local_services.study_local import StudyLocalService from antares.craft.service.local_services.thermal_local import ThermalLocalService @@ -159,6 +162,15 @@ def create_output_service(self) -> BaseOutputService: raise TypeError(f"{ERROR_MESSAGE}{repr(self.config)}") return output_service + def create_settings_service(self) -> BaseStudySettingsService: + if isinstance(self.config, APIconf): + settings_service: BaseStudySettingsService = StudySettingsAPIService(self.config, self.study_id) + elif isinstance(self.config, LocalConfiguration): + settings_service = StudySettingsLocalService(self.config, self.study_name) + else: + raise TypeError(f"{ERROR_MESSAGE}{repr(self.config)}") + return settings_service + def create_hydro_service(self) -> BaseHydroService: if isinstance(self.config, APIconf): hydro_service: BaseHydroService = HydroApiService(self.config, self.study_id)