diff --git a/mypy.ini b/mypy.ini index 9f15e122..2feb3af6 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,6 +1,5 @@ [mypy] mypy_path = src packages= antares -disallow_untyped_defs = true -disallow_untyped_calls = true +strict = true diff --git a/src/antares/craft/model/area.py b/src/antares/craft/model/area.py index 3742895e..cba3448d 100644 --- a/src/antares/craft/model/area.py +++ b/src/antares/craft/model/area.py @@ -17,7 +17,7 @@ """ from types import MappingProxyType -from typing import Any, Dict, List, Mapping, Optional, Set +from typing import Any, Mapping, Optional import pandas as pd @@ -26,6 +26,13 @@ from antares.craft.model.renewable import RenewableCluster, RenewableClusterProperties from antares.craft.model.st_storage import STStorage, STStorageProperties from antares.craft.model.thermal import ThermalCluster, ThermalClusterProperties +from antares.craft.service.base_services import ( + BaseAreaService, + BaseHydroService, + BaseRenewableService, + BaseShortTermStorageService, + BaseThermalService, +) from antares.craft.tools.alias_generators import to_space from antares.craft.tools.all_optional_meta import all_optional_model from antares.craft.tools.contents_tool import EnumIgnoreCase, transform_name_to_id @@ -55,14 +62,14 @@ class DefaultAreaProperties(BaseModel, extra="forbid", populate_by_name=True): non_dispatch_power: bool = True dispatch_hydro_power: bool = True other_dispatch_power: bool = True - filter_synthesis: Set[FilterOption] = { + filter_synthesis: set[FilterOption] = { FilterOption.HOURLY, FilterOption.DAILY, FilterOption.WEEKLY, FilterOption.MONTHLY, FilterOption.ANNUAL, } - filter_by_year: Set[FilterOption] = { + filter_by_year: set[FilterOption] = { FilterOption.HOURLY, FilterOption.DAILY, FilterOption.WEEKLY, @@ -121,11 +128,11 @@ class AreaUi(BaseModel, extra="forbid", populate_by_name=True, alias_generator=t layer: Optional[int] = None x: Optional[int] = None y: Optional[int] = None - color_rgb: Optional[List[int]] = None + color_rgb: Optional[list[int]] = None - layer_x: Optional[Dict[int, int]] = None - layer_y: Optional[Dict[int, int]] = None - layer_color: Optional[Dict[int, str]] = None + layer_x: Optional[dict[int, int]] = None + layer_y: Optional[dict[int, int]] = None + layer_color: Optional[dict[int, str]] = None class AreaUiLocal(BaseModel): @@ -151,7 +158,7 @@ def __init__( @computed_field # type: ignore[misc] @property - def ui(self) -> Dict[str, Optional[int]]: + def ui(self) -> dict[str, Optional[int]]: return dict( x=self._x, y=self._y, @@ -163,17 +170,17 @@ def ui(self) -> Dict[str, Optional[int]]: @computed_field # type: ignore[misc] @property - def layerX(self) -> Dict[int, int]: + def layerX(self) -> dict[int, int]: return self._layer_x @computed_field # type: ignore[misc] @property - def layerY(self) -> Dict[int, int]: + def layerY(self) -> dict[int, int]: return self._layer_y @computed_field # type: ignore[misc] @property - def layerColor(self) -> Dict[int, str]: + def layerColor(self) -> dict[int, str]: return self._layer_color def yield_area_ui(self) -> AreaUi: @@ -189,18 +196,18 @@ def yield_area_ui(self) -> AreaUi: class Area: - def __init__( # type: ignore # TODO: Find a way to avoid circular imports + def __init__( self, name: str, - area_service, - storage_service, - thermal_service, - renewable_service, - hydro_service, + area_service: BaseAreaService, + storage_service: BaseShortTermStorageService, + thermal_service: BaseThermalService, + renewable_service: BaseRenewableService, + hydro_service: BaseHydroService, *, - renewables: Optional[Dict[str, RenewableCluster]] = None, - thermals: Optional[Dict[str, ThermalCluster]] = None, - st_storages: Optional[Dict[str, STStorage]] = None, + renewables: Optional[dict[str, RenewableCluster]] = None, + thermals: Optional[dict[str, ThermalCluster]] = None, + st_storages: Optional[dict[str, STStorage]] = None, hydro: Optional[Hydro] = None, properties: Optional[AreaProperties] = None, ui: Optional[AreaUi] = None, @@ -299,7 +306,7 @@ def get_reserves_matrix(self) -> pd.DataFrame: def get_misc_gen_matrix(self) -> pd.DataFrame: return self._area_service.get_misc_gen_matrix(self.id) - def delete_thermal_clusters(self, thermal_clusters: List[ThermalCluster]) -> None: + def delete_thermal_clusters(self, thermal_clusters: list[ThermalCluster]) -> None: self._area_service.delete_thermal_clusters(self.id, thermal_clusters) for cluster in thermal_clusters: self._thermals.pop(cluster.id) @@ -307,7 +314,7 @@ def delete_thermal_clusters(self, thermal_clusters: List[ThermalCluster]) -> Non def delete_thermal_cluster(self, thermal_cluster: ThermalCluster) -> None: self.delete_thermal_clusters([thermal_cluster]) - def delete_renewable_clusters(self, renewable_clusters: List[RenewableCluster]) -> None: + def delete_renewable_clusters(self, renewable_clusters: list[RenewableCluster]) -> None: self._area_service.delete_renewable_clusters(self.id, renewable_clusters) for cluster in renewable_clusters: self._renewables.pop(cluster.id) @@ -315,7 +322,7 @@ def delete_renewable_clusters(self, renewable_clusters: List[RenewableCluster]) def delete_renewable_cluster(self, renewable_cluster: RenewableCluster) -> None: self.delete_renewable_clusters([renewable_cluster]) - def delete_st_storages(self, storages: List[STStorage]) -> None: + def delete_st_storages(self, storages: list[STStorage]) -> None: self._area_service.delete_st_storages(self.id, storages) for storage in storages: self._st_storages.pop(storage.id) @@ -349,7 +356,7 @@ def create_misc_gen(self, series: pd.DataFrame) -> None: def create_hydro( self, properties: Optional[HydroProperties] = None, - matrices: Optional[Dict[HydroMatrixName, pd.DataFrame]] = None, + matrices: Optional[dict[HydroMatrixName, pd.DataFrame]] = None, ) -> Hydro: # todo: is it necessary to create allocation or correlation ? hydro = self._area_service.create_hydro(self.id, properties, matrices) @@ -358,17 +365,17 @@ def create_hydro( def read_st_storages( self, - ) -> List[STStorage]: + ) -> list[STStorage]: return self._storage_service.read_st_storages(self.id) def read_renewables( self, - ) -> List[RenewableCluster]: + ) -> list[RenewableCluster]: return self._renewable_service.read_renewables(self.id) def read_thermal_clusters( self, - ) -> List[ThermalCluster]: + ) -> list[ThermalCluster]: return self._thermal_service.read_thermal_clusters(self.id) def read_hydro( diff --git a/src/antares/craft/model/binding_constraint.py b/src/antares/craft/model/binding_constraint.py index 667415f0..f87375ac 100644 --- a/src/antares/craft/model/binding_constraint.py +++ b/src/antares/craft/model/binding_constraint.py @@ -11,10 +11,11 @@ # This file is part of the Antares project. from enum import Enum -from typing import Any, Dict, List, Optional, Union +from typing import Any, Optional, Union import pandas as pd +from antares.craft.service.base_services import BaseBindingConstraintService from antares.craft.tools.all_optional_meta import all_optional_model from antares.craft.tools.contents_tool import EnumIgnoreCase, transform_name_to_id from pydantic import BaseModel, Field, model_validator @@ -76,12 +77,12 @@ class ConstraintTerm(TermOperators): id: str = Field(init=False) @model_validator(mode="before") - def fill_id(cls, v: Dict[str, Any]) -> Dict[str, Any]: + def fill_id(cls, v: dict[str, Any]) -> dict[str, Any]: v["id"] = cls.generate_id(v["data"]) return v @classmethod - def generate_id(cls, data: Union[Dict[str, str], LinkData, ClusterData]) -> str: + def generate_id(cls, data: Union[dict[str, str], LinkData, ClusterData]) -> str: if isinstance(data, dict): if "area1" in data: return "%".join(sorted((data["area1"].lower(), data["area2"].lower()))) @@ -120,12 +121,12 @@ class BindingConstraintProperties(DefaultBindingConstraintProperties): class BindingConstraint: - def __init__( # type: ignore # TODO: Find a way to avoid circular imports + def __init__( self, name: str, - binding_constraint_service, + binding_constraint_service: BaseBindingConstraintService, properties: Optional[BindingConstraintProperties] = None, - terms: Optional[List[ConstraintTerm]] = None, + terms: Optional[list[ConstraintTerm]] = None, ): self._name = name self._binding_constraint_service = binding_constraint_service @@ -149,10 +150,10 @@ def properties(self) -> BindingConstraintProperties: def properties(self, new_properties: BindingConstraintProperties) -> None: self._properties = new_properties - def get_terms(self) -> Dict[str, ConstraintTerm]: + def get_terms(self) -> dict[str, ConstraintTerm]: return self._terms - def add_terms(self, terms: List[ConstraintTerm]) -> None: + def add_terms(self, terms: list[ConstraintTerm]) -> None: added_terms = self._binding_constraint_service.add_constraint_terms(self, terms) for term in added_terms: self._terms[term.id] = term diff --git a/src/antares/craft/model/hydro.py b/src/antares/craft/model/hydro.py index 44418ed9..e914cc9e 100644 --- a/src/antares/craft/model/hydro.py +++ b/src/antares/craft/model/hydro.py @@ -11,10 +11,11 @@ # This file is part of the Antares project. from enum import Enum -from typing import Dict, Optional +from typing import Optional import pandas as pd +from antares.craft.service.base_services import BaseHydroService from antares.craft.tools.all_optional_meta import all_optional_model from pydantic import BaseModel from pydantic.alias_generators import to_camel @@ -90,12 +91,12 @@ def yield_hydro_properties(self) -> HydroProperties: class Hydro: - def __init__( # type: ignore # + def __init__( self, - service, + service: BaseHydroService, area_id: str, properties: Optional[HydroProperties] = None, - matrices: Optional[Dict[HydroMatrixName, pd.DataFrame]] = None, + matrices: Optional[dict[HydroMatrixName, pd.DataFrame]] = None, ): self._area_id = area_id self._service = service @@ -111,7 +112,7 @@ def properties(self) -> Optional[HydroProperties]: return self._properties @property - def matrices(self) -> Optional[Dict[HydroMatrixName, pd.DataFrame]]: + def matrices(self) -> Optional[dict[HydroMatrixName, pd.DataFrame]]: return self._matrices def get_maxpower(self) -> pd.DataFrame: diff --git a/src/antares/craft/model/link.py b/src/antares/craft/model/link.py index b7f00141..52a720b3 100644 --- a/src/antares/craft/model/link.py +++ b/src/antares/craft/model/link.py @@ -16,6 +16,7 @@ import pandas as pd from antares.craft.model.commons import FilterOption, sort_filter_values +from antares.craft.service.base_services import BaseLinkService from antares.craft.tools.alias_generators import to_kebab from antares.craft.tools.all_optional_meta import all_optional_model from antares.craft.tools.contents_tool import transform_name_to_id @@ -133,11 +134,11 @@ def yield_link_ui(self) -> LinkUi: class Link: - def __init__( # type: ignore # TODO: Find a way to avoid circular imports + def __init__( self, area_from: str, area_to: str, - link_service, + link_service: BaseLinkService, properties: Optional[LinkProperties] = None, ui: Optional[LinkUi] = None, ): diff --git a/src/antares/craft/model/output.py b/src/antares/craft/model/output.py index 2046daf8..74cbea20 100644 --- a/src/antares/craft/model/output.py +++ b/src/antares/craft/model/output.py @@ -14,6 +14,7 @@ import pandas as pd +from antares.craft.service.base_services import BaseOutputService from pydantic import BaseModel @@ -75,10 +76,10 @@ def to_api_query(self, object_type: str) -> str: class Output: - def __init__(self, name: str, archived: bool, output_service): # type: ignore + def __init__(self, name: str, archived: bool, output_service: BaseOutputService): self._name = name self._archived = archived - self._output_service = output_service + self._output_service: BaseOutputService = output_service @property def name(self) -> str: diff --git a/src/antares/craft/model/renewable.py b/src/antares/craft/model/renewable.py index 98d9ed7a..6e050f7c 100644 --- a/src/antares/craft/model/renewable.py +++ b/src/antares/craft/model/renewable.py @@ -16,6 +16,7 @@ import pandas as pd from antares.craft.model.cluster import ClusterProperties +from antares.craft.service.base_services import BaseRenewableService from antares.craft.tools.all_optional_meta import all_optional_model from antares.craft.tools.contents_tool import transform_name_to_id @@ -90,9 +91,9 @@ def yield_renewable_cluster_properties(self) -> RenewableClusterProperties: class RenewableCluster: - def __init__( # type: ignore # TODO: Find a way to avoid circular imports + def __init__( self, - renewable_service, + renewable_service: BaseRenewableService, area_id: str, name: str, properties: Optional[RenewableClusterProperties] = None, diff --git a/src/antares/craft/model/st_storage.py b/src/antares/craft/model/st_storage.py index edd3bc65..4466774e 100644 --- a/src/antares/craft/model/st_storage.py +++ b/src/antares/craft/model/st_storage.py @@ -15,6 +15,7 @@ import pandas as pd +from antares.craft.service.base_services import BaseShortTermStorageService from antares.craft.tools.all_optional_meta import all_optional_model from antares.craft.tools.contents_tool import transform_name_to_id from pydantic import BaseModel @@ -90,12 +91,18 @@ def yield_st_storage_properties(self) -> STStorageProperties: class STStorage: - def __init__(self, storage_service, area_id: str, name: str, properties: Optional[STStorageProperties] = None): # type: ignore # TODO: Find a way to avoid circular imports - self._area_id = area_id - self._storage_service = storage_service - self._name = name - self._id = transform_name_to_id(name) - self._properties = properties or STStorageProperties() + def __init__( + self, + storage_service: BaseShortTermStorageService, + area_id: str, + name: str, + properties: Optional[STStorageProperties] = None, + ): + self._area_id: str = area_id + self._storage_service: BaseShortTermStorageService = storage_service + self._name: str = name + self._id: str = transform_name_to_id(name) + self._properties: STStorageProperties = properties or STStorageProperties() # TODO: Add matrices. @@ -135,24 +142,16 @@ def get_storage_inflows(self) -> pd.DataFrame: return self._storage_service.get_storage_matrix(self, STStorageMatrixName.INFLOWS) def upload_pmax_injection(self, p_max_injection_matrix: pd.DataFrame) -> None: - return self._storage_service.upload_storage_matrix( - self, STStorageMatrixName.PMAX_INJECTION, p_max_injection_matrix - ) + self._storage_service.upload_storage_matrix(self, STStorageMatrixName.PMAX_INJECTION, p_max_injection_matrix) def upload_pmax_withdrawal(self, p_max_withdrawal_matrix: pd.DataFrame) -> None: - return self._storage_service.upload_storage_matrix( - self, STStorageMatrixName.PMAX_WITHDRAWAL, p_max_withdrawal_matrix - ) + self._storage_service.upload_storage_matrix(self, STStorageMatrixName.PMAX_WITHDRAWAL, p_max_withdrawal_matrix) def upload_lower_rule_curve(self, lower_rule_curve_matrix: pd.DataFrame) -> None: - return self._storage_service.upload_storage_matrix( - self, STStorageMatrixName.LOWER_CURVE_RULE, lower_rule_curve_matrix - ) + self._storage_service.upload_storage_matrix(self, STStorageMatrixName.LOWER_CURVE_RULE, lower_rule_curve_matrix) def upload_upper_rule_curve(self, upper_rule_curve_matrix: pd.DataFrame) -> None: - return self._storage_service.upload_storage_matrix( - self, STStorageMatrixName.UPPER_RULE_CURVE, upper_rule_curve_matrix - ) + self._storage_service.upload_storage_matrix(self, STStorageMatrixName.UPPER_RULE_CURVE, upper_rule_curve_matrix) def upload_storage_inflows(self, inflows_matrix: pd.DataFrame) -> None: - return self._storage_service.upload_storage_matrix(self, STStorageMatrixName.INFLOWS, inflows_matrix) + self._storage_service.upload_storage_matrix(self, STStorageMatrixName.INFLOWS, inflows_matrix) diff --git a/src/antares/craft/model/study.py b/src/antares/craft/model/study.py index 347b4d2f..ecb4aea9 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 +from typing import List, Optional, cast import pandas as pd @@ -40,7 +40,7 @@ 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.base_services import BaseStudyService +from antares.craft.service.base_services import BaseLinkService, BaseStudyService 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 @@ -337,7 +337,7 @@ def create_link( properties: Optional[LinkProperties] = None, ui: Optional[LinkUi] = None, ) -> Link: - temp_link = Link(area_from, area_to, link_service=None) + temp_link = Link(area_from, area_to, link_service=cast(BaseLinkService, None)) area_from, area_to = sorted([area_from, area_to]) area_from_id = temp_link.area_from_id area_to_id = temp_link.area_to_id diff --git a/src/antares/craft/model/thermal.py b/src/antares/craft/model/thermal.py index b4895e56..106e262b 100644 --- a/src/antares/craft/model/thermal.py +++ b/src/antares/craft/model/thermal.py @@ -16,6 +16,7 @@ import pandas as pd from antares.craft.model.cluster import ClusterProperties +from antares.craft.service.base_services import BaseThermalService from antares.craft.tools.all_optional_meta import all_optional_model from antares.craft.tools.contents_tool import transform_name_to_id @@ -171,9 +172,15 @@ class ThermalClusterMatrixName(Enum): class ThermalCluster: - def __init__(self, thermal_service, area_id: str, name: str, properties: Optional[ThermalClusterProperties] = None): # type: ignore # TODO: Find a way to avoid circular imports + def __init__( + self, + thermal_service: BaseThermalService, + area_id: str, + name: str, + properties: Optional[ThermalClusterProperties] = None, + ): self._area_id = area_id - self._thermal_service = thermal_service + self._thermal_service: BaseThermalService = thermal_service self._name = name self._id = transform_name_to_id(name) self._properties = properties or ThermalClusterProperties() diff --git a/src/antares/craft/service/api_services/area_api.py b/src/antares/craft/service/api_services/area_api.py index fedf885e..1d473f55 100644 --- a/src/antares/craft/service/api_services/area_api.py +++ b/src/antares/craft/service/api_services/area_api.py @@ -10,7 +10,7 @@ # # This file is part of the Antares project. -from typing import Dict, List, Optional, Union +from typing import Any, Optional, Union import pandas as pd @@ -51,28 +51,24 @@ class AreaApiService(BaseAreaService): - def __init__(self, config: APIconf, study_id: str) -> None: + def __init__( + self, + config: APIconf, + study_id: str, + storage_service: BaseShortTermStorageService, + thermal_service: BaseThermalService, + renewable_service: BaseRenewableService, + hydro_service: BaseHydroService, + ) -> None: super().__init__() self.api_config = config self.study_id = study_id self._wrapper = RequestWrapper(self.api_config.set_up_api_conf()) self._base_url = f"{self.api_config.get_host()}/api/v1" - self.storage_service: Optional[BaseShortTermStorageService] = None - self.thermal_service: Optional[BaseThermalService] = None - self.renewable_service: Optional[BaseRenewableService] = None - self.hydro_service: Optional[BaseHydroService] = None - - def set_storage_service(self, storage_service: BaseShortTermStorageService) -> None: - self.storage_service = storage_service - - def set_thermal_service(self, thermal_service: BaseThermalService) -> None: - self.thermal_service = thermal_service - - def set_renewable_service(self, renewable_service: BaseRenewableService) -> None: - self.renewable_service = renewable_service - - def set_hydro_service(self, hydro_service: "BaseHydroService") -> None: - self.hydro_service = hydro_service + self.storage_service: BaseShortTermStorageService = storage_service + self.thermal_service: BaseThermalService = thermal_service + self.renewable_service: BaseRenewableService = renewable_service + self.hydro_service: BaseHydroService = hydro_service def create_area( self, area_name: str, properties: Optional[AreaProperties] = None, ui: Optional[AreaUi] = None @@ -272,11 +268,11 @@ def _create_thermal_series( self._replace_matrix_request(json_payload) - def _replace_matrix_request(self, json_payload: Union[Dict, List[Dict]]) -> None: + def _replace_matrix_request(self, json_payload: Union[dict[str, Any], list[dict[str, Any]]]) -> None: """ Send a POST request with the given JSON payload to commands endpoint. - Args: Dict or List([Dict] with action = "replace_matrix" and matrix values + Args: dict or list([dict] with action = "replace_matrix" and matrix values """ url = f"{self._base_url}/studies/{self.study_id}/commands" @@ -404,7 +400,7 @@ def create_hydro( self, area_id: str, properties: Optional[HydroProperties], - matrices: Optional[Dict[HydroMatrixName, pd.DataFrame]], + matrices: Optional[dict[HydroMatrixName, pd.DataFrame]], ) -> Hydro: # todo: not model validation because endpoint does not return anything # properties = HydroProperties.model_validate(json_response) not possible @@ -437,7 +433,7 @@ def read_hydro( return hydro - def _create_hydro_series(self, area_id: str, matrices: Dict[HydroMatrixName, pd.DataFrame]) -> None: + def _create_hydro_series(self, area_id: str, matrices: dict[HydroMatrixName, pd.DataFrame]) -> None: command_body = [] for matrix_name, series in matrices.items(): if "SERIES" in matrix_name.name: @@ -506,7 +502,7 @@ def delete_area(self, area_id: str) -> None: except APIError as e: raise AreaDeletionError(area_id, e.message) from e - def delete_thermal_clusters(self, area_id: str, clusters: List[ThermalCluster]) -> None: + def delete_thermal_clusters(self, area_id: str, clusters: list[ThermalCluster]) -> None: url = f"{self._base_url}/studies/{self.study_id}/areas/{area_id}/clusters/thermal" body = [cluster.id for cluster in clusters] try: @@ -514,7 +510,7 @@ def delete_thermal_clusters(self, area_id: str, clusters: List[ThermalCluster]) except APIError as e: raise ThermalDeletionError(area_id, body, e.message) from e - def delete_renewable_clusters(self, area_id: str, clusters: List[RenewableCluster]) -> None: + def delete_renewable_clusters(self, area_id: str, clusters: list[RenewableCluster]) -> None: url = f"{self._base_url}/studies/{self.study_id}/areas/{area_id}/clusters/renewable" body = [cluster.id for cluster in clusters] try: @@ -522,7 +518,7 @@ def delete_renewable_clusters(self, area_id: str, clusters: List[RenewableCluste except APIError as e: raise RenewableDeletionError(area_id, body, e.message) from e - def delete_st_storages(self, area_id: str, storages: List[STStorage]) -> None: + def delete_st_storages(self, area_id: str, storages: list[STStorage]) -> None: url = f"{self._base_url}/studies/{self.study_id}/areas/{area_id}/storages" body = [storage.id for storage in storages] try: @@ -569,7 +565,7 @@ def craft_ui(self, url_str: str, area_id: str) -> AreaUi: return current_ui - def read_areas(self) -> List[Area]: + def read_areas(self) -> list[Area]: area_list = [] base_api_url = f"{self._base_url}/studies/{self.study_id}/areas" diff --git a/src/antares/craft/service/api_services/link_api.py b/src/antares/craft/service/api_services/link_api.py index e91aaed9..1b349492 100644 --- a/src/antares/craft/service/api_services/link_api.py +++ b/src/antares/craft/service/api_services/link_api.py @@ -258,7 +258,7 @@ def convert_api_link_to_internal_link(self, api_link: dict[str, Any]) -> Link: return Link(link_area_from_id, link_area_to_id, self, link_properties, link_ui) -def _join_filter_values_for_json(json_dict: dict, dict_to_extract: dict) -> dict: +def _join_filter_values_for_json(json_dict: dict[str, Any], dict_to_extract: dict[str, Any]) -> dict[str, Any]: for key in dict_to_extract: if key in ["filter-synthesis", "filter-year-by-year"]: json_dict[key] = ",".join(dict_to_extract[key]) diff --git a/src/antares/craft/service/api_services/renewable_api.py b/src/antares/craft/service/api_services/renewable_api.py index fe5b6c18..4ebfa8e0 100644 --- a/src/antares/craft/service/api_services/renewable_api.py +++ b/src/antares/craft/service/api_services/renewable_api.py @@ -104,7 +104,7 @@ def read_renewables( renewable_name = renewable.pop("name") renewable_props = RenewableClusterProperties(**renewable) - renewable_cluster = RenewableCluster(self.config, renewable_id, renewable_name, renewable_props) + renewable_cluster = RenewableCluster(self, renewable_id, renewable_name, renewable_props) renewables.append(renewable_cluster) renewables.sort(key=lambda renewable: renewable.id) diff --git a/src/antares/craft/service/api_services/run_api.py b/src/antares/craft/service/api_services/run_api.py index 699b4625..b4c3d3b9 100644 --- a/src/antares/craft/service/api_services/run_api.py +++ b/src/antares/craft/service/api_services/run_api.py @@ -11,7 +11,7 @@ # This file is part of the Antares project. import time -from typing import Any, Optional +from typing import Any, Optional, cast from antares.craft.api_conf.api_conf import APIconf from antares.craft.api_conf.request_wrapper import RequestWrapper @@ -103,5 +103,5 @@ def _get_unarchiving_task_id(self, job: Job, tasks: list[dict[str, Any]]) -> str task_name = task["name"] output_id = task_name.split("/")[-1].split(" ")[0] if output_id == job.output_id: - return task["id"] + return cast(str, task["id"]) raise AntaresSimulationUnzipError(self.study_id, job.job_id, "Could not find task for unarchiving job") diff --git a/src/antares/craft/service/api_services/st_storage_api.py b/src/antares/craft/service/api_services/st_storage_api.py index ec96abf4..c558c57f 100644 --- a/src/antares/craft/service/api_services/st_storage_api.py +++ b/src/antares/craft/service/api_services/st_storage_api.py @@ -85,7 +85,7 @@ def read_st_storages(self, area_id: str) -> List[STStorage]: storage_name = storage.pop("name") storage_properties = STStorageProperties(**storage) - st_storage = STStorage(self.config, storage_id, storage_name, storage_properties) + st_storage = STStorage(self, storage_id, storage_name, storage_properties) storages.append(st_storage) storages.sort(key=lambda storage: storage.id) diff --git a/src/antares/craft/service/api_services/study_api.py b/src/antares/craft/service/api_services/study_api.py index ce4ccb5c..f75cf11f 100644 --- a/src/antares/craft/service/api_services/study_api.py +++ b/src/antares/craft/service/api_services/study_api.py @@ -10,7 +10,7 @@ # # This file is part of the Antares project. from pathlib import Path, PurePath -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING import antares.craft.model.study as study @@ -38,13 +38,13 @@ class StudyApiService(BaseStudyService): - def __init__(self, config: APIconf, study_id: str): + def __init__(self, config: APIconf, study_id: str, output_service: BaseOutputService): 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()) - self._output_service: Optional[BaseOutputService] = None + self._output_service: BaseOutputService = output_service @property def study_id(self) -> str: @@ -55,12 +55,9 @@ def config(self) -> APIconf: return self._config @property - def output_service(self) -> Optional[BaseOutputService]: + def output_service(self) -> BaseOutputService: return self._output_service - def set_output_service(self, output_service: BaseOutputService) -> None: - self._output_service = output_service - 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 e841d495..838ae4cd 100644 --- a/src/antares/craft/service/base_services.py +++ b/src/antares/craft/service/base_services.py @@ -12,58 +12,46 @@ from abc import ABC, abstractmethod from pathlib import Path, PurePath -from typing import TYPE_CHECKING, Dict, List, Optional +from typing import TYPE_CHECKING, Optional import pandas as pd from antares.craft.config.base_configuration import BaseConfiguration -from antares.craft.model.area import Area, AreaProperties, AreaUi -from antares.craft.model.binding_constraint import ( - BindingConstraint, - BindingConstraintProperties, - ConstraintMatrixName, - ConstraintTerm, -) -from antares.craft.model.hydro import Hydro, HydroMatrixName, HydroProperties -from antares.craft.model.link import Link, LinkProperties, LinkUi -from antares.craft.model.output import AggregationEntry, Output -from antares.craft.model.renewable import RenewableCluster, RenewableClusterProperties from antares.craft.model.settings.study_settings import StudySettings from antares.craft.model.simulation import AntaresSimulationParameters, Job -from antares.craft.model.st_storage import STStorage, STStorageProperties -from antares.craft.model.thermal import ThermalCluster, ThermalClusterMatrixName, ThermalClusterProperties if TYPE_CHECKING: + from antares.craft.model.area import Area, AreaProperties, AreaUi + from antares.craft.model.binding_constraint import ( + BindingConstraint, + BindingConstraintProperties, + ConstraintMatrixName, + ConstraintTerm, + ) + from antares.craft.model.hydro import Hydro, HydroMatrixName, HydroProperties + from antares.craft.model.link import Link, LinkProperties, LinkUi + from antares.craft.model.output import AggregationEntry, Output + from antares.craft.model.renewable import RenewableCluster, RenewableClusterProperties + from antares.craft.model.st_storage import STStorage, STStorageMatrixName, STStorageProperties from antares.craft.model.study import Study + from antares.craft.model.thermal import ( + ThermalCluster, + ThermalClusterMatrixName, + ThermalClusterProperties, + ) class BaseAreaService(ABC): - @abstractmethod - def set_storage_service(self, storage_service: "BaseShortTermStorageService") -> None: - pass - - @abstractmethod - def set_thermal_service(self, thermal_service: "BaseThermalService") -> None: - pass - - @abstractmethod - def set_renewable_service(self, renewable_service: "BaseRenewableService") -> None: - pass - - @abstractmethod - def set_hydro_service(self, hydro_service: "BaseHydroService") -> None: - pass - @abstractmethod def create_area( - self, area_name: str, properties: Optional[AreaProperties] = None, ui: Optional[AreaUi] = None - ) -> Area: + self, area_name: str, properties: Optional["AreaProperties"] = None, ui: Optional["AreaUi"] = None + ) -> "Area": pass @abstractmethod def create_thermal_cluster( - self, area_id: str, thermal_name: str, properties: Optional[ThermalClusterProperties] = None - ) -> ThermalCluster: + self, area_id: str, thermal_name: str, properties: Optional["ThermalClusterProperties"] = None + ) -> "ThermalCluster": """ Args: area_id: the area id in which to create the thermal cluster @@ -71,7 +59,7 @@ def create_thermal_cluster( properties: the properties of the thermal cluster. If not provided, AntaresWeb will use its own default values. Returns: - The created thermal cluster + The created ThermalCluster """ pass @@ -80,13 +68,13 @@ def create_thermal_cluster_with_matrices( self, area_id: str, cluster_name: str, - parameters: ThermalClusterProperties, + parameters: "ThermalClusterProperties", prepro: Optional[pd.DataFrame], modulation: Optional[pd.DataFrame], series: Optional[pd.DataFrame], CO2Cost: Optional[pd.DataFrame], fuelCost: Optional[pd.DataFrame], - ) -> ThermalCluster: + ) -> "ThermalCluster": """ Args: @@ -110,9 +98,9 @@ def create_renewable_cluster( self, area_id: str, renewable_name: str, - properties: Optional[RenewableClusterProperties], + properties: Optional["RenewableClusterProperties"], series: Optional[pd.DataFrame], - ) -> RenewableCluster: + ) -> "RenewableCluster": """ Args: area_id: the area id in which to create the renewable cluster @@ -137,8 +125,8 @@ def create_load(self, area_id: str, series: pd.DataFrame) -> None: @abstractmethod def create_st_storage( - self, area_id: str, st_storage_name: str, properties: Optional[STStorageProperties] = None - ) -> STStorage: + self, area_id: str, st_storage_name: str, properties: Optional["STStorageProperties"] = None + ) -> "STStorage": """ Args: @@ -194,7 +182,7 @@ def create_misc_gen(self, area_id: str, series: pd.DataFrame) -> None: pass @abstractmethod - def update_area_properties(self, area_id: str, properties: AreaProperties) -> AreaProperties: + def update_area_properties(self, area_id: str, properties: "AreaProperties") -> "AreaProperties": """ Args: area_id: concerned area @@ -203,7 +191,7 @@ def update_area_properties(self, area_id: str, properties: AreaProperties) -> Ar pass @abstractmethod - def update_area_ui(self, area_id: str, ui: AreaUi) -> AreaUi: + def update_area_ui(self, area_id: str, ui: "AreaUi") -> "AreaUi": """ Args: area_id: concerned area @@ -220,29 +208,29 @@ def delete_area(self, area_id: str) -> None: pass @abstractmethod - def delete_thermal_clusters(self, area_id: str, thermal_clusters: List[ThermalCluster]) -> None: + def delete_thermal_clusters(self, area_id: str, thermal_clusters: list["ThermalCluster"]) -> None: """ Args: area_id: area containing the cluster - thermal_clusters: List of thermal clusters object to be deleted + thermal_clusters: list of thermal clusters object to be deleted """ pass @abstractmethod - def delete_renewable_clusters(self, area_id: str, renewable_clusters: List[RenewableCluster]) -> None: + def delete_renewable_clusters(self, area_id: str, renewable_clusters: list["RenewableCluster"]) -> None: """ Args: area_id: area containing the cluster - renewable_clusters: List of renewable clusters object to be deleted + renewable_clusters: list of renewable clusters object to be deleted """ pass @abstractmethod - def delete_st_storages(self, area_id: str, storages: List[STStorage]) -> None: + def delete_st_storages(self, area_id: str, storages: list["STStorage"]) -> None: """ Args: area_id: area containing the cluster - storages: List of short term storage objects to be deleted + storages: list of short term storage objects to be deleted """ pass @@ -290,7 +278,7 @@ def get_wind_matrix(self, area_id: str) -> pd.DataFrame: pass @abstractmethod - def read_areas(self) -> list[Area]: + def read_areas(self) -> list["Area"]: """ Returns: Returns a list of areas """ @@ -300,9 +288,9 @@ def read_areas(self) -> list[Area]: def create_hydro( self, area_id: str, - properties: Optional[HydroProperties], - matrices: Optional[Dict[HydroMatrixName, pd.DataFrame]], - ) -> Hydro: + properties: Optional["HydroProperties"], + matrices: Optional[dict["HydroMatrixName", pd.DataFrame]], + ) -> "Hydro": """ Args: area_id: area in which hydro will be created @@ -316,7 +304,7 @@ def create_hydro( def read_hydro( self, area_id: str, - ) -> Hydro: + ) -> "Hydro": pass @@ -348,9 +336,9 @@ def create_link( self, area_from: str, area_to: str, - properties: Optional[LinkProperties] = None, - ui: Optional[LinkUi] = None, - ) -> Link: + properties: Optional["LinkProperties"] = None, + ui: Optional["LinkUi"] = None, + ) -> "Link": """ Args: area_from: area where the link goes from @@ -364,7 +352,7 @@ def create_link( pass @abstractmethod - def delete_link(self, link: Link) -> None: + def delete_link(self, link: "Link") -> None: """ Args: link: link object to be deleted @@ -372,7 +360,7 @@ def delete_link(self, link: Link) -> None: pass @abstractmethod - def update_link_properties(self, link: Link, properties: LinkProperties) -> LinkProperties: + def update_link_properties(self, link: "Link", properties: "LinkProperties") -> "LinkProperties": """ Args: link: concerned link @@ -381,7 +369,7 @@ def update_link_properties(self, link: Link, properties: LinkProperties) -> Link pass @abstractmethod - def update_link_ui(self, link: Link, ui: LinkUi) -> LinkUi: + def update_link_ui(self, link: "Link", ui: "LinkUi") -> "LinkUi": """ Args: link: concerned link @@ -401,7 +389,7 @@ def create_parameters(self, series: pd.DataFrame, area_from: str, area_to: str) pass @abstractmethod - def read_links(self) -> List[Link]: + def read_links(self) -> list["Link"]: pass @abstractmethod @@ -430,8 +418,8 @@ def create_capacity_indirect(self, series: pd.DataFrame, area_from: str, area_to class BaseThermalService(ABC): @abstractmethod def update_thermal_properties( - self, thermal_cluster: ThermalCluster, properties: ThermalClusterProperties - ) -> ThermalClusterProperties: + self, thermal_cluster: "ThermalCluster", properties: "ThermalClusterProperties" + ) -> "ThermalClusterProperties": """ Args: thermal_cluster: concerned cluster @@ -440,11 +428,13 @@ def update_thermal_properties( pass @abstractmethod - def update_thermal_matrix(self, thermal_cluster: ThermalCluster, matrix: pd.DataFrame) -> None: + def update_thermal_matrix(self, thermal_cluster: "ThermalCluster", matrix: pd.DataFrame) -> None: pass @abstractmethod - def get_thermal_matrix(self, thermal_cluster: ThermalCluster, ts_name: ThermalClusterMatrixName) -> pd.DataFrame: + def get_thermal_matrix( + self, thermal_cluster: "ThermalCluster", ts_name: "ThermalClusterMatrixName" + ) -> pd.DataFrame: """ Args: thermal_cluster: cluster to retrieve matrix @@ -456,23 +446,23 @@ def get_thermal_matrix(self, thermal_cluster: ThermalCluster, ts_name: ThermalCl pass @abstractmethod - def read_thermal_clusters(self, area_id: str) -> List[ThermalCluster]: + def read_thermal_clusters(self, area_id: str) -> list["ThermalCluster"]: pass class BaseBindingConstraintService(ABC): - binding_constraints: dict[str, BindingConstraint] + binding_constraints: dict[str, "BindingConstraint"] @abstractmethod def create_binding_constraint( self, name: str, - properties: Optional[BindingConstraintProperties] = None, - terms: Optional[List[ConstraintTerm]] = None, + properties: Optional["BindingConstraintProperties"] = None, + terms: Optional[list["ConstraintTerm"]] = None, less_term_matrix: Optional[pd.DataFrame] = None, equal_term_matrix: Optional[pd.DataFrame] = None, greater_term_matrix: Optional[pd.DataFrame] = None, - ) -> BindingConstraint: + ) -> "BindingConstraint": """ Args: name: the binding constraint name @@ -488,7 +478,9 @@ def create_binding_constraint( pass @abstractmethod - def add_constraint_terms(self, constraint: BindingConstraint, terms: List[ConstraintTerm]) -> List[ConstraintTerm]: + def add_constraint_terms( + self, constraint: "BindingConstraint", terms: list["ConstraintTerm"] + ) -> list["ConstraintTerm"]: """ Args: constraint: the concerned binding constraint @@ -510,8 +502,8 @@ def delete_binding_constraint_term(self, constraint_id: str, term_id: str) -> No @abstractmethod def update_binding_constraint_properties( - self, binding_constraint: BindingConstraint, properties: BindingConstraintProperties - ) -> BindingConstraintProperties: + self, binding_constraint: "BindingConstraint", properties: "BindingConstraintProperties" + ) -> "BindingConstraintProperties": """ Args: binding_constraint: concerned binding_constraint @@ -520,7 +512,9 @@ def update_binding_constraint_properties( pass @abstractmethod - def get_constraint_matrix(self, constraint: BindingConstraint, matrix_name: ConstraintMatrixName) -> pd.DataFrame: + def get_constraint_matrix( + self, constraint: "BindingConstraint", matrix_name: "ConstraintMatrixName" + ) -> pd.DataFrame: """ Args: constraint: the concerned binding constraint @@ -530,7 +524,7 @@ def get_constraint_matrix(self, constraint: BindingConstraint, matrix_name: Cons @abstractmethod def update_constraint_matrix( - self, constraint: BindingConstraint, matrix_name: ConstraintMatrixName, matrix: pd.DataFrame + self, constraint: "BindingConstraint", matrix_name: "ConstraintMatrixName", matrix: pd.DataFrame ) -> None: """ Args: @@ -541,7 +535,7 @@ def update_constraint_matrix( pass @abstractmethod - def read_binding_constraints(self) -> list[BindingConstraint]: + def read_binding_constraints(self) -> list["BindingConstraint"]: """ Loads binding constraints into study @@ -564,7 +558,7 @@ def config(self) -> BaseConfiguration: pass @abstractmethod - def delete_binding_constraint(self, constraint: BindingConstraint) -> None: + def delete_binding_constraint(self, constraint: "BindingConstraint") -> None: """ Args: constraint: binding constraint object to be deleted @@ -599,18 +593,14 @@ def move_study(self, new_parent_path: Path) -> PurePath: pass @abstractmethod - def read_outputs(self) -> list[Output]: + def read_outputs(self) -> list["Output"]: """ Gets the output list of a study - Returns: Output list + Returns: "Output" list """ pass - @abstractmethod - def set_output_service(self, output_service: "BaseOutputService") -> None: - pass - @abstractmethod def delete_outputs(self) -> None: """ @@ -636,8 +626,8 @@ def generate_thermal_timeseries(self) -> None: class BaseRenewableService(ABC): @abstractmethod def update_renewable_properties( - self, renewable_cluster: RenewableCluster, properties: RenewableClusterProperties - ) -> RenewableClusterProperties: + self, renewable_cluster: "RenewableCluster", properties: "RenewableClusterProperties" + ) -> "RenewableClusterProperties": """ Args: renewable_cluster: concerned cluster @@ -657,7 +647,7 @@ def get_renewable_matrix(self, cluster_id: str, area_id: str) -> pd.DataFrame: pass @abstractmethod - def update_renewable_matrix(self, renewable_cluster: RenewableCluster, matrix: pd.DataFrame) -> None: + def update_renewable_matrix(self, renewable_cluster: "RenewableCluster", matrix: pd.DataFrame) -> None: """ Args: renewable_cluster: the renewable_cluster @@ -667,15 +657,15 @@ def update_renewable_matrix(self, renewable_cluster: RenewableCluster, matrix: p pass @abstractmethod - def read_renewables(self, area_id: str) -> List[RenewableCluster]: + def read_renewables(self, area_id: str) -> list["RenewableCluster"]: pass class BaseShortTermStorageService(ABC): @abstractmethod def update_st_storage_properties( - self, st_storage: STStorage, properties: STStorageProperties - ) -> STStorageProperties: + self, st_storage: "STStorage", properties: "STStorageProperties" + ) -> "STStorageProperties": """ Args: st_storage: concerned storage @@ -684,7 +674,15 @@ def update_st_storage_properties( pass @abstractmethod - def read_st_storages(self, area_id: str) -> List[STStorage]: + def read_st_storages(self, area_id: str) -> list["STStorage"]: + pass + + @abstractmethod + def get_storage_matrix(self, storage: "STStorage", ts_name: "STStorageMatrixName") -> pd.DataFrame: + pass + + @abstractmethod + def upload_storage_matrix(self, storage: "STStorage", ts_name: "STStorageMatrixName", matrix: pd.DataFrame) -> None: pass @@ -730,7 +728,7 @@ def get_matrix(self, output_id: str, file_path: str) -> pd.DataFrame: @abstractmethod def aggregate_values( - self, output_id: str, aggregation_entry: AggregationEntry, object_type: str, mc_type: str + self, output_id: str, aggregation_entry: "AggregationEntry", object_type: str, mc_type: str ) -> pd.DataFrame: """ Creates a matrix of aggregated raw data diff --git a/src/antares/craft/service/local_services/area_local.py b/src/antares/craft/service/local_services/area_local.py index b543c727..962ebc5a 100644 --- a/src/antares/craft/service/local_services/area_local.py +++ b/src/antares/craft/service/local_services/area_local.py @@ -63,22 +63,23 @@ def _sets_ini_content() -> ConfigParser: class AreaLocalService(BaseAreaService): - def __init__(self, config: LocalConfiguration, study_name: str, **kwargs: Any) -> None: + def __init__( + self, + config: LocalConfiguration, + study_name: str, + storage_service: BaseShortTermStorageService, + thermal_service: BaseThermalService, + renewable_service: BaseRenewableService, + hydro_service: BaseHydroService, + **kwargs: Any, + ) -> None: super().__init__(**kwargs) self.config = config self.study_name = study_name - - def set_storage_service(self, storage_service: BaseShortTermStorageService) -> None: - self.storage_service = storage_service - - def set_thermal_service(self, thermal_service: BaseThermalService) -> None: - self.thermal_service = thermal_service - - def set_renewable_service(self, renewable_service: BaseRenewableService) -> None: - self.renewable_service = renewable_service - - def set_hydro_service(self, hydro_service: "BaseHydroService") -> None: - self.hydro_service = hydro_service + self.storage_service: BaseShortTermStorageService = storage_service + self.thermal_service: BaseThermalService = thermal_service + self.renewable_service: BaseRenewableService = renewable_service + self.hydro_service: BaseHydroService = hydro_service def create_thermal_cluster( self, @@ -203,7 +204,7 @@ def create_hydro( IniFile.create_hydro_initialization_files_for_area(self.config.study_path, area_id) - return Hydro(self, area_id, local_hydro_properties.yield_hydro_properties()) + return Hydro(self.hydro_service, area_id, local_hydro_properties.yield_hydro_properties()) def read_hydro( self, diff --git a/src/antares/craft/service/local_services/binding_constraint_local.py b/src/antares/craft/service/local_services/binding_constraint_local.py index 941a270c..f6b09755 100644 --- a/src/antares/craft/service/local_services/binding_constraint_local.py +++ b/src/antares/craft/service/local_services/binding_constraint_local.py @@ -176,7 +176,7 @@ def _write_binding_constraint_ini( serialized_terms = {term.id: term.weight_offset() for term in terms} if terms else {} - existing_terms.update(serialized_terms) # type: ignore + existing_terms.update(serialized_terms) current_ini_content[existing_section] = existing_terms # Persist the updated INI content diff --git a/src/antares/craft/service/local_services/models/settings.py b/src/antares/craft/service/local_services/models/settings.py index 5f68b083..04adaf86 100644 --- a/src/antares/craft/service/local_services/models/settings.py +++ b/src/antares/craft/service/local_services/models/settings.py @@ -13,7 +13,7 @@ import ast from dataclasses import asdict -from typing import Any, Sequence, Set +from typing import Any, Sequence, Set, cast from antares.craft.model.settings.adequacy_patch import AdequacyPatchParameters, PriceTakingOrder from antares.craft.model.settings.advanced_parameters import ( @@ -98,7 +98,7 @@ def validate_accuracy_on_correlation(cls, v: Any) -> Sequence[str]: """Ensure the ID is lower case.""" if v is None: return [] - return ast.literal_eval(v) + return cast(Sequence[str], ast.literal_eval(v)) class SeedParametersLocal(LocalBaseModel, alias_generator=to_kebab): @@ -187,7 +187,7 @@ class OutputSectionLocal(LocalBaseModel): class GeneralParametersLocal(LocalBaseModel): general: GeneralSectionLocal - input: dict = {"import": ""} + input: dict[str, str] = {"import": ""} output: OutputSectionLocal @staticmethod diff --git a/src/antares/craft/service/local_services/renewable_local.py b/src/antares/craft/service/local_services/renewable_local.py index 824f6e86..3babf13f 100644 --- a/src/antares/craft/service/local_services/renewable_local.py +++ b/src/antares/craft/service/local_services/renewable_local.py @@ -11,7 +11,7 @@ # This file is part of the Antares project. -from typing import Any, List +from typing import Any, get_type_hints import pandas as pd @@ -39,30 +39,42 @@ def get_renewable_matrix(self, cluster_id: str, area_id: str) -> pd.DataFrame: 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]: + def _extract_renewable_properties(self, renewable_data: dict[str, Any]) -> RenewableClusterProperties: + # get_type_hints will yield a dict with every local property as key and its type as the value + property_types = get_type_hints(RenewableClusterPropertiesLocal) + + # the name key is called "name" in renewable_data but "renewable_name" in the properties, that's why we map it + property_mapping = {"name": "renewable_name"} + + # for each property in renewable_data, we will type it according to property_types while making sure it's not None + # because it's Optional. If it's "name" then we get its mapping from the property_mapping dict + parsed_data = { + property_mapping.get(property, property): property_types[property_mapping.get(property, property)](value) + if value is not None + else None + for property, value in renewable_data.items() + if property_mapping.get(property, property) in property_types + } + + return RenewableClusterPropertiesLocal(**parsed_data).yield_renewable_cluster_properties() + + def read_renewables(self, area_id: str) -> list[RenewableCluster]: renewable_dict = IniFile( self.config.study_path, InitializationFilesTypes.RENEWABLES_LIST_INI, area_id=area_id ).ini_dict - renewables_clusters = [] - if renewable_dict: - for renewable_cluster in renewable_dict: - renewable_properties = RenewableClusterPropertiesLocal( - group=renewable_dict[renewable_cluster].get("group"), - renewable_name=renewable_dict[renewable_cluster].get("name"), - enabled=renewable_dict[renewable_cluster].get("enabled"), - unit_count=renewable_dict[renewable_cluster].get("unitcount"), - nominal_capacity=renewable_dict[renewable_cluster].get("nominalcapacity"), - ts_interpretation=renewable_dict[renewable_cluster].get("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(), - ) - ) - return renewables_clusters + + if not renewable_dict: + return [] + + return [ + RenewableCluster( + renewable_service=self, + area_id=area_id, + name=renewable_data["name"], + properties=self._extract_renewable_properties(renewable_data), + ) + for renewable_data in renewable_dict.values() + ] def update_renewable_matrix(self, renewable_cluster: RenewableCluster, matrix: pd.DataFrame) -> None: raise NotImplementedError diff --git a/src/antares/craft/service/local_services/st_storage_local.py b/src/antares/craft/service/local_services/st_storage_local.py index aa21b593..cd3e0d6e 100644 --- a/src/antares/craft/service/local_services/st_storage_local.py +++ b/src/antares/craft/service/local_services/st_storage_local.py @@ -12,8 +12,10 @@ from typing import Any, List +import pandas as pd + from antares.craft.config.local_configuration import LocalConfiguration -from antares.craft.model.st_storage import STStorage, STStorageProperties +from antares.craft.model.st_storage import STStorage, STStorageMatrixName, STStorageProperties from antares.craft.service.base_services import BaseShortTermStorageService @@ -30,3 +32,9 @@ def update_st_storage_properties( def read_st_storages(self, area_id: str) -> List[STStorage]: raise NotImplementedError + + def upload_storage_matrix(self, storage: STStorage, ts_name: STStorageMatrixName, matrix: pd.DataFrame) -> None: + raise NotImplementedError + + def get_storage_matrix(self, storage: STStorage, ts_name: STStorageMatrixName) -> pd.DataFrame: + raise NotImplementedError diff --git a/src/antares/craft/service/local_services/study_local.py b/src/antares/craft/service/local_services/study_local.py index 7f9575b1..6c73ce90 100644 --- a/src/antares/craft/service/local_services/study_local.py +++ b/src/antares/craft/service/local_services/study_local.py @@ -10,7 +10,7 @@ # # This file is part of the Antares project. from pathlib import Path, PurePath -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any from antares.craft.config.local_configuration import LocalConfiguration from antares.craft.model.binding_constraint import BindingConstraint @@ -22,11 +22,13 @@ class StudyLocalService(BaseStudyService): - def __init__(self, config: LocalConfiguration, study_name: str, **kwargs: Any) -> None: + def __init__( + self, config: LocalConfiguration, study_name: str, output_service: BaseOutputService, **kwargs: Any + ) -> None: super().__init__(**kwargs) self._config = config self._study_name = study_name - self._output_service: Optional[BaseOutputService] = None + self._output_service: BaseOutputService = output_service @property def study_id(self) -> str: @@ -37,12 +39,9 @@ def config(self) -> LocalConfiguration: return self._config @property - def output_service(self) -> Optional[BaseOutputService]: + def output_service(self) -> BaseOutputService: return self._output_service - def set_output_service(self, output_service: BaseOutputService) -> None: - self._output_service = output_service - def delete_binding_constraint(self, constraint: BindingConstraint) -> None: raise NotImplementedError diff --git a/src/antares/craft/service/local_services/thermal_local.py b/src/antares/craft/service/local_services/thermal_local.py index f0dea929..50e83f55 100644 --- a/src/antares/craft/service/local_services/thermal_local.py +++ b/src/antares/craft/service/local_services/thermal_local.py @@ -10,7 +10,7 @@ # # This file is part of the Antares project. -from typing import Any, List +from typing import Any, get_type_hints import pandas as pd @@ -57,62 +57,37 @@ def get_thermal_matrix(self, thermal_cluster: ThermalCluster, ts_name: ThermalCl cluster_id=thermal_cluster.id, ) - def read_thermal_clusters(self, area_id: str) -> List[ThermalCluster]: + def _extract_thermal_properties(self, thermal_data: dict[str, Any]) -> ThermalClusterProperties: + property_types = get_type_hints(ThermalClusterPropertiesLocal) + + property_mapping = {"name": "thermal_name"} + + parsed_data = { + property_mapping.get(property, property): property_types[property_mapping.get(property, property)](value) + if value is not None + else None + for property, value in thermal_data.items() + if property_mapping.get(property, property) in property_types + } + + return ThermalClusterPropertiesLocal(**parsed_data).yield_thermal_cluster_properties() + + def read_thermal_clusters(self, area_id: str) -> list[ThermalCluster]: thermal_dict = IniFile( self.config.study_path, InitializationFilesTypes.THERMAL_LIST_INI, area_id=area_id ).ini_dict - thermal_clusters = [] - if thermal_dict: - for thermal_cluster in thermal_dict: - # TODO refactor this as it is really not clean - thermal_properties = ThermalClusterPropertiesLocal( - group=thermal_dict[thermal_cluster].get("group"), - thermal_name=thermal_dict[thermal_cluster]["name"], - enabled=thermal_dict[thermal_cluster].get("enabled"), - unit_count=thermal_dict[thermal_cluster].get("unitcount"), - nominal_capacity=thermal_dict[thermal_cluster].get("nominalcapacity"), - gen_ts=thermal_dict[thermal_cluster].get("gen-ts"), - min_stable_power=thermal_dict[thermal_cluster].get("min-stable-power"), - min_up_time=thermal_dict[thermal_cluster].get("min-up-time"), - min_down_time=thermal_dict[thermal_cluster].get("min-down-time"), - must_run=thermal_dict[thermal_cluster].get("must-run"), - spinning=thermal_dict[thermal_cluster].get("spinning"), - volatility_forced=thermal_dict[thermal_cluster].get("volatility.forced"), - volatility_planned=thermal_dict[thermal_cluster].get("volatility.planned"), - law_forced=thermal_dict[thermal_cluster].get("law.forced"), - law_planned=thermal_dict[thermal_cluster].get("law.planned"), - marginal_cost=thermal_dict[thermal_cluster].get("marginal-cost"), - spread_cost=thermal_dict[thermal_cluster].get("spread-cost"), - fixed_cost=thermal_dict[thermal_cluster].get("fixed-cost"), - startup_cost=thermal_dict[thermal_cluster].get("startup-cost"), - market_bid_cost=thermal_dict[thermal_cluster].get("market-bid-cost"), - co2=thermal_dict[thermal_cluster].get("co2"), - nh3=thermal_dict[thermal_cluster].get("nh3"), - so2=thermal_dict[thermal_cluster].get("so2"), - nox=thermal_dict[thermal_cluster].get("nox"), - pm2_5=thermal_dict[thermal_cluster].get("pm2_5"), - pm5=thermal_dict[thermal_cluster].get("pm5"), - pm10=thermal_dict[thermal_cluster].get("pm10"), - nmvoc=thermal_dict[thermal_cluster].get("nmvoc"), - op1=thermal_dict[thermal_cluster].get("op1"), - op2=thermal_dict[thermal_cluster].get("op2"), - op3=thermal_dict[thermal_cluster].get("op3"), - op4=thermal_dict[thermal_cluster].get("op4"), - op5=thermal_dict[thermal_cluster].get("op5"), - cost_generation=thermal_dict[thermal_cluster].get("costgeneration"), - efficiency=thermal_dict[thermal_cluster].get("efficiency"), - variable_o_m_cost=thermal_dict[thermal_cluster].get("variableomcost"), - ) + if not thermal_dict: + return [] - thermal_clusters.append( - ThermalCluster( - thermal_service=self, - area_id=area_id, - name=thermal_dict[thermal_cluster]["name"], - properties=thermal_properties.yield_thermal_cluster_properties(), - ) - ) - return thermal_clusters + return [ + ThermalCluster( + thermal_service=self, + area_id=area_id, + name=thermal_data["name"], + properties=self._extract_thermal_properties(thermal_data), + ) + for thermal_data in thermal_dict.values() + ] def update_thermal_matrix(self, thermal_cluster: ThermalCluster, matrix: pd.DataFrame) -> None: raise NotImplementedError diff --git a/src/antares/craft/service/service_factory.py b/src/antares/craft/service/service_factory.py index 90e1ef67..7e5c3cfb 100644 --- a/src/antares/craft/service/service_factory.py +++ b/src/antares/craft/service/service_factory.py @@ -60,25 +60,21 @@ def __init__(self, config: BaseConfiguration, study_id: str = "", study_name: st def create_area_service(self) -> BaseAreaService: if isinstance(self.config, APIconf): - area_service: BaseAreaService = AreaApiService(self.config, self.study_id) storage_service: BaseShortTermStorageService = ShortTermStorageApiService(self.config, self.study_id) thermal_service: BaseThermalService = ThermalApiService(self.config, self.study_id) renewable_service: BaseRenewableService = RenewableApiService(self.config, self.study_id) hydro_service: BaseHydroService = HydroApiService(self.config, self.study_id) - area_service.set_storage_service(storage_service) - area_service.set_thermal_service(thermal_service) - area_service.set_renewable_service(renewable_service) - area_service.set_hydro_service(hydro_service) + area_service: BaseAreaService = AreaApiService( + self.config, self.study_id, storage_service, thermal_service, renewable_service, hydro_service + ) elif isinstance(self.config, LocalConfiguration): - area_service = AreaLocalService(self.config, self.study_name) storage_service = ShortTermStorageLocalService(self.config, self.study_name) thermal_service = ThermalLocalService(self.config, self.study_name) renewable_service = RenewableLocalService(self.config, self.study_name) hydro_service = HydroLocalService(self.config, self.study_name) - area_service.set_storage_service(storage_service) - area_service.set_thermal_service(thermal_service) - area_service.set_renewable_service(renewable_service) - area_service.set_hydro_service(hydro_service) + area_service = AreaLocalService( + self.config, self.study_name, storage_service, thermal_service, renewable_service, hydro_service + ) else: raise TypeError(f"{ERROR_MESSAGE}{repr(self.config)}") return area_service @@ -114,14 +110,14 @@ def create_binding_constraints_service(self) -> BaseBindingConstraintService: def create_study_service(self) -> BaseStudyService: study_service: BaseStudyService + output_service = self.create_output_service() if isinstance(self.config, APIconf): - study_service = StudyApiService(self.config, self.study_id) + study_service = StudyApiService(self.config, self.study_id, output_service) elif isinstance(self.config, LocalConfiguration): - study_service = StudyLocalService(self.config, self.study_name) + study_service = StudyLocalService(self.config, self.study_name, output_service) else: raise TypeError(f"{ERROR_MESSAGE}{repr(self.config)}") - study_service.set_output_service(self.create_output_service()) return study_service def create_renewable_service(self) -> BaseRenewableService: diff --git a/src/antares/craft/tools/contents_tool.py b/src/antares/craft/tools/contents_tool.py index 41964338..376f16f6 100644 --- a/src/antares/craft/tools/contents_tool.py +++ b/src/antares/craft/tools/contents_tool.py @@ -13,7 +13,7 @@ import re from enum import Enum -from typing import Any, Dict, Optional +from typing import Any, Optional from antares.craft.tools.custom_raw_config_parser import CustomRawConfigParser from pydantic import BaseModel @@ -53,12 +53,12 @@ class MapResponse(BaseModel): x: int y: int - layerColor: Dict[str, str] - layerX: Dict[str, float] - layerY: Dict[str, float] + layerColor: dict[str, str] + layerX: dict[str, float] + layerY: dict[str, float] ui: MapResponse - def to_craft(self) -> Dict[str, Any]: + def to_craft(self) -> dict[str, Any]: json_ui = { "layer": self.ui.layers, "x": self.ui.x, diff --git a/src/antares/craft/tools/custom_raw_config_parser.py b/src/antares/craft/tools/custom_raw_config_parser.py index d1ad4445..96016488 100644 --- a/src/antares/craft/tools/custom_raw_config_parser.py +++ b/src/antares/craft/tools/custom_raw_config_parser.py @@ -20,7 +20,7 @@ class CustomRawConfigParser(RawConfigParser): def __init__(self, special_keys: Optional[list[str]] = None, **kwargs: Any) -> None: super().__init__(**kwargs) - _special_keys: list = [ + _special_keys: list[str] = [ "playlist_year_weight", "playlist_year +", "playlist_year -", @@ -34,17 +34,19 @@ def __init__(self, special_keys: Optional[list[str]] = None, **kwargs: Any) -> N def optionxform(self, optionstr: str) -> str: return optionstr - def _write_line(self, file_path: "SupportsWrite", section_name: str, delimiter: str, key: str, value: str) -> None: + def _write_line( + self, file_path: "SupportsWrite[str]", section_name: str, delimiter: str, key: str, value: str + ) -> None: """Writes a single line of the provided section to the specified `file_path`.""" value = self._interpolation.before_write(self, section_name, key, value) - if value is not None or not self._allow_no_value: # type:ignore + if value is not None or not self._allow_no_value: value = delimiter + str(value).replace("\n", "\n\t") else: value = "" file_path.write(f"{key}{value}\n") def _write_section( - self, fp: "SupportsWrite", section_name: str, section_items: ItemsView[str, str], delimiter: str + self, fp: "SupportsWrite[str]", section_name: str, section_items: ItemsView[str, str], delimiter: str ) -> None: """Write a single section to the specified `fp`. Overrides the function in `RawConfigParser`. diff --git a/src/antares/craft/tools/ini_tool.py b/src/antares/craft/tools/ini_tool.py index 417e668c..153ed577 100644 --- a/src/antares/craft/tools/ini_tool.py +++ b/src/antares/craft/tools/ini_tool.py @@ -98,7 +98,7 @@ def __init__( self.write_ini_file() @property - def ini_dict(self) -> dict: + def ini_dict(self) -> dict[str, Any]: """Ini contents as a python dictionary""" return {section: dict(self._ini_contents[section]) for section in self._ini_contents.sections()} @@ -147,7 +147,7 @@ def add_section(self, section: Any, append: bool = False) -> None: else: raise TypeError(f"Only dict or Path are allowed, received {type(section)}") - def _check_if_duplicated_section_names(self, sections: Iterable, append: bool = False) -> None: + def _check_if_duplicated_section_names(self, sections: Iterable[str], append: bool = False) -> None: for section in sections: if section in self.ini_dict and not append: raise DuplicateSectionError(section) @@ -228,7 +228,7 @@ def create_list_ini_for_area(cls, study_path: Path, area_id: str) -> None: cls(study_path=study_path, ini_file_type=property_file, area_id=area_id) -def merge_dicts_for_ini(dict_a: dict[str, Any], dict_b: dict[str, Any]) -> dict: +def merge_dicts_for_ini(dict_a: dict[str, Any], dict_b: dict[str, Any]) -> dict[str, Any]: """ Merges two dictionaries, combining fields with the same name into a list of the values in the fields. @@ -240,10 +240,10 @@ def merge_dicts_for_ini(dict_a: dict[str, Any], dict_b: dict[str, Any]) -> dict: dict: The merged dictionary. """ - def _ensure_list(item: Any) -> list: + def _ensure_list(item: Any) -> list[Any]: return item if isinstance(item, list) else [item] - def _filter_out_empty_list_entries(list_of_entries: list[Any]) -> list: + def _filter_out_empty_list_entries(list_of_entries: list[Any]) -> list[Any]: return [entry for entry in list_of_entries if entry] output_dict = dict_a | dict_b @@ -257,7 +257,7 @@ def _filter_out_empty_list_entries(list_of_entries: list[Any]) -> list: return output_dict -def get_ini_fields_for_ini(model: BaseModel) -> dict: +def get_ini_fields_for_ini(model: BaseModel) -> dict[str, Any]: """ Creates a dictionary of the property `ini_fields` from a `BaseModel` object that contains the merged dictionaries of all the `ini_fields` properties. diff --git a/src/antares/craft/tools/matrix_tool.py b/src/antares/craft/tools/matrix_tool.py index e767a48d..20efef8f 100644 --- a/src/antares/craft/tools/matrix_tool.py +++ b/src/antares/craft/tools/matrix_tool.py @@ -12,14 +12,14 @@ import os from pathlib import Path -from typing import Dict, Optional +from typing import Optional, Union import pandas as pd from antares.craft.tools.time_series_tool import TimeSeriesFileType -def prepare_args_replace_matrix(series: pd.DataFrame, series_path: str) -> Dict: +def prepare_args_replace_matrix(series: pd.DataFrame, series_path: str) -> dict[str, Union[str, dict[str, str]]]: """ Args: diff --git a/tests/antares/delete/test_delete_api.py b/tests/antares/delete/test_delete_api.py index c82838cb..a288398c 100644 --- a/tests/antares/delete/test_delete_api.py +++ b/tests/antares/delete/test_delete_api.py @@ -33,6 +33,7 @@ from antares.craft.service.api_services.binding_constraint_api import BindingConstraintApiService from antares.craft.service.api_services.hydro_api import HydroApiService from antares.craft.service.api_services.link_api import LinkApiService +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.st_storage_api import ShortTermStorageApiService from antares.craft.service.api_services.study_api import StudyApiService @@ -42,12 +43,13 @@ class TestDeleteAPI: api = APIconf("https://antares.com", "token", verify=False) study_id = "22c52f44-4c2a-407b-862b-490887f93dd8" - study_service = StudyApiService(api, study_id) - area_service = AreaApiService(api, study_id) + output_service = OutputApiService(api, study_id) + study_service = StudyApiService(api, study_id, output_service) thermal_service = ThermalApiService(api, study_id) renewable_service = RenewableApiService(api, study_id) st_storage_service = ShortTermStorageApiService(api, study_id) hydro_service = HydroApiService(api, study_id) + area_service = AreaApiService(api, study_id, st_storage_service, thermal_service, renewable_service, hydro_service) area_fr = Area("fr", area_service, st_storage_service, thermal_service, renewable_service, hydro_service) area_be = Area("be", area_service, st_storage_service, thermal_service, renewable_service, hydro_service) link_service = LinkApiService(api, study_id) diff --git a/tests/antares/services/api_services/test_area_api.py b/tests/antares/services/api_services/test_area_api.py index fbc7fc47..8f96a26e 100644 --- a/tests/antares/services/api_services/test_area_api.py +++ b/tests/antares/services/api_services/test_area_api.py @@ -37,15 +37,21 @@ class TestCreateAPI: api = APIconf("https://antares.com", "token", verify=False) study_id = "22c52f44-4c2a-407b-862b-490887f93dd8" - area = Area( - "area_test", - ServiceFactory(api, study_id).create_area_service(), - ServiceFactory(api, study_id).create_st_storage_service(), - ServiceFactory(api, study_id).create_thermal_service(), - ServiceFactory(api, study_id).create_renewable_service(), - ServiceFactory(api, study_id).create_hydro_service(), + area_service = ServiceFactory(api, study_id).create_area_service() + st_storage_service = ServiceFactory(api, study_id).create_st_storage_service() + thermal_service = ServiceFactory(api, study_id).create_thermal_service() + renewable_service = ServiceFactory(api, study_id).create_renewable_service() + hydro_service = ServiceFactory(api, study_id).create_hydro_service() + area = Area("area_test", area_service, st_storage_service, thermal_service, renewable_service, hydro_service) + + area_api = AreaApiService( + api, + "248bbb99-c909-47b7-b239-01f6f6ae7de7", + st_storage_service, + thermal_service, + renewable_service, + hydro_service, ) - area_api = AreaApiService(api, "248bbb99-c909-47b7-b239-01f6f6ae7de7") antares_web_description_msg = "Mocked Server KO" matrix = pd.DataFrame(data=[[0]]) study = Study("TestStudy", "880", ServiceFactory(api, study_id)) diff --git a/tests/antares/services/api_services/test_renewable_api.py b/tests/antares/services/api_services/test_renewable_api.py index 6fc47aa0..7c03ccc1 100644 --- a/tests/antares/services/api_services/test_renewable_api.py +++ b/tests/antares/services/api_services/test_renewable_api.py @@ -9,10 +9,11 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. - import pytest import requests_mock +from unittest.mock import Mock + import pandas as pd from antares.craft.api_conf.api_conf import APIconf @@ -136,8 +137,13 @@ def test_read_renewables(self): 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) + storage_service = Mock() + thermal_service = Mock() + hydro_service = Mock() + area_api = AreaApiService( + self.api, study_id_test, storage_service, thermal_service, renewable_api, hydro_service + ) actual_renewable_list = renewable_api.read_renewables(area_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 79f81348..0ad79538 100644 --- a/tests/antares/services/api_services/test_st_storage_api.py +++ b/tests/antares/services/api_services/test_st_storage_api.py @@ -9,10 +9,11 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. - import pytest import requests_mock +from unittest.mock import Mock + import pandas as pd from antares.craft.api_conf.api_conf import APIconf @@ -138,8 +139,14 @@ 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, study_id_test) + renewable_service = Mock() + thermal_service = Mock() + hydro_service = Mock() + area_api = AreaApiService( + self.api, study_id_test, storage_api, thermal_service, renewable_service, hydro_service + ) actual_storage_list = storage_api.read_st_storages(area_id) diff --git a/tests/antares/services/api_services/test_thermal_api.py b/tests/antares/services/api_services/test_thermal_api.py index 2feaf5a1..4e837a6e 100644 --- a/tests/antares/services/api_services/test_thermal_api.py +++ b/tests/antares/services/api_services/test_thermal_api.py @@ -9,10 +9,11 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. - import pytest import requests_mock +from unittest.mock import Mock + import pandas as pd from antares.craft.api_conf.api_conf import APIconf @@ -152,8 +153,14 @@ def test_read_thermals(self): 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) + renewable_service = Mock() + storage_service = Mock() + hydro_service = Mock() + area_api = AreaApiService( + self.api, study_id_test, storage_service, thermal_api, renewable_service, hydro_service + ) actual_thermal_list = thermal_api.read_thermal_clusters(area_id)