From 11cf2d4de3483f38f4b7fae0ca12e641c2312fab Mon Sep 17 00:00:00 2001 From: belthlemar Date: Thu, 27 Feb 2025 16:55:12 +0100 Subject: [PATCH] use frozen dataclas --- src/antares/craft/model/area.py | 4 ++-- src/antares/craft/model/binding_constraint.py | 12 +++++----- src/antares/craft/model/cluster.py | 2 +- src/antares/craft/model/hydro.py | 2 +- src/antares/craft/model/link.py | 4 ++-- src/antares/craft/model/renewable.py | 2 +- .../craft/model/settings/adequacy_patch.py | 2 +- .../model/settings/advanced_parameters.py | 2 +- src/antares/craft/model/settings/general.py | 2 +- .../craft/model/settings/optimization.py | 2 +- .../model/settings/playlist_parameters.py | 2 +- .../craft/model/settings/study_settings.py | 2 +- .../craft/model/settings/thematic_trimming.py | 2 +- src/antares/craft/model/st_storage.py | 2 +- src/antares/craft/model/study.py | 7 ++++-- src/antares/craft/model/thermal.py | 2 +- .../services/binding_constraint.py | 7 +++--- .../antares/integration/test_local_client.py | 24 ++++++++----------- tests/integration/test_web_client.py | 22 ++++++++--------- 19 files changed, 51 insertions(+), 53 deletions(-) diff --git a/src/antares/craft/model/area.py b/src/antares/craft/model/area.py index fe2b794c..75c092fc 100644 --- a/src/antares/craft/model/area.py +++ b/src/antares/craft/model/area.py @@ -63,7 +63,7 @@ class AreaPropertiesUpdate: spread_spilled_energy_cost: Optional[float] = None -@dataclass +@dataclass(frozen=True) class AreaProperties: energy_cost_unsupplied: float = 0.0 energy_cost_spilled: float = 0.0 @@ -88,7 +88,7 @@ def __post_init__(self) -> None: raise ValueError(f"The `color_rgb` list must contain exactly 3 values, currently {self.color_rgb}") -@dataclass +@dataclass(frozen=True) class AreaUi: x: int = 0 y: int = 0 diff --git a/src/antares/craft/model/binding_constraint.py b/src/antares/craft/model/binding_constraint.py index a1753c39..ba5464f8 100644 --- a/src/antares/craft/model/binding_constraint.py +++ b/src/antares/craft/model/binding_constraint.py @@ -38,7 +38,7 @@ class ConstraintMatrixName(Enum): GREATER_TERM = "gt" -@dataclass +@dataclass(frozen=True) class LinkData: """ DTO for a constraint term on a link between two areas. @@ -48,7 +48,7 @@ class LinkData: area2: str -@dataclass +@dataclass(frozen=True) class ClusterData: """ DTO for a constraint term on a cluster in an area. @@ -58,7 +58,7 @@ class ClusterData: cluster: str -@dataclass +@dataclass(frozen=True) class ConstraintTermData: data: Union[LinkData, ClusterData] @@ -77,13 +77,13 @@ def from_dict(input: dict[str, str]) -> Union[LinkData, ClusterData]: raise ValueError(f"Dict {input} couldn't be serialized as a ConstraintTermData object") -@dataclass +@dataclass(frozen=True) class ConstraintTermUpdate(ConstraintTermData): weight: Optional[float] = None offset: Optional[int] = None -@dataclass +@dataclass(frozen=True) class ConstraintTerm(ConstraintTermData): weight: float = 1 offset: int = 0 @@ -103,7 +103,7 @@ class BindingConstraintPropertiesUpdate: group: Optional[str] = None -@dataclass +@dataclass(frozen=True) class BindingConstraintProperties: enabled: bool = True time_step: BindingConstraintFrequency = BindingConstraintFrequency.HOURLY diff --git a/src/antares/craft/model/cluster.py b/src/antares/craft/model/cluster.py index c133f350..c26712ac 100644 --- a/src/antares/craft/model/cluster.py +++ b/src/antares/craft/model/cluster.py @@ -13,7 +13,7 @@ from typing import Optional -@dataclass +@dataclass(frozen=True) class ClusterProperties: """ Common properties for thermal and renewable clusters diff --git a/src/antares/craft/model/hydro.py b/src/antares/craft/model/hydro.py index 681d86c3..da18fec9 100644 --- a/src/antares/craft/model/hydro.py +++ b/src/antares/craft/model/hydro.py @@ -36,7 +36,7 @@ class HydroPropertiesUpdate: pumping_efficiency: Optional[float] = None -@dataclass +@dataclass(frozen=True) class HydroProperties: inter_daily_breakdown: float = 1 intra_daily_modulation: float = 24 diff --git a/src/antares/craft/model/link.py b/src/antares/craft/model/link.py index 102436c0..784a5002 100644 --- a/src/antares/craft/model/link.py +++ b/src/antares/craft/model/link.py @@ -54,7 +54,7 @@ class LinkPropertiesUpdate: filter_year_by_year: Optional[Set[FilterOption]] = None -@dataclass +@dataclass(frozen=True) class LinkProperties: hurdles_cost: bool = False loop_flow: bool = False @@ -67,7 +67,7 @@ class LinkProperties: filter_year_by_year: comma_separated_enum_set = field(default_factory=lambda: FILTER_VALUES) -@dataclass +@dataclass(frozen=True) class LinkUi: link_style: LinkStyle = LinkStyle.PLAIN link_width: float = 1 diff --git a/src/antares/craft/model/renewable.py b/src/antares/craft/model/renewable.py index e4f18481..36741ba0 100644 --- a/src/antares/craft/model/renewable.py +++ b/src/antares/craft/model/renewable.py @@ -54,7 +54,7 @@ class TimeSeriesInterpretation(Enum): PRODUCTION_FACTOR = "production-factor" -@dataclass +@dataclass(frozen=True) class RenewableClusterProperties(ClusterProperties): group: RenewableClusterGroup = RenewableClusterGroup.OTHER1 ts_interpretation: TimeSeriesInterpretation = TimeSeriesInterpretation.POWER_GENERATION diff --git a/src/antares/craft/model/settings/adequacy_patch.py b/src/antares/craft/model/settings/adequacy_patch.py index bdd06cdc..f7b9b8c6 100644 --- a/src/antares/craft/model/settings/adequacy_patch.py +++ b/src/antares/craft/model/settings/adequacy_patch.py @@ -19,7 +19,7 @@ class PriceTakingOrder(Enum): LOAD = "Load" -@dataclass +@dataclass(frozen=True) class AdequacyPatchParameters: include_adq_patch: bool = False set_to_null_ntc_from_physical_out_to_physical_in_for_first_step: bool = True diff --git a/src/antares/craft/model/settings/advanced_parameters.py b/src/antares/craft/model/settings/advanced_parameters.py index 7b2bec77..fbca9691 100644 --- a/src/antares/craft/model/settings/advanced_parameters.py +++ b/src/antares/craft/model/settings/advanced_parameters.py @@ -61,7 +61,7 @@ class RenewableGenerationModeling(Enum): CLUSTERS = "clusters" -@dataclass +@dataclass(frozen=True) class AdvancedParameters: initial_reservoir_levels: InitialReservoirLevel = InitialReservoirLevel.COLD_START hydro_heuristic_policy: HydroHeuristicPolicy = HydroHeuristicPolicy.ACCOMMODATE_RULES_CURVES diff --git a/src/antares/craft/model/settings/general.py b/src/antares/craft/model/settings/general.py index bd3808a0..81b14c9b 100644 --- a/src/antares/craft/model/settings/general.py +++ b/src/antares/craft/model/settings/general.py @@ -67,7 +67,7 @@ class OutputFormat(EnumIgnoreCase): ZIP = "zip-files" -@dataclass +@dataclass(frozen=True) class GeneralParameters: mode: Mode = Mode.ECONOMY horizon: str = "" diff --git a/src/antares/craft/model/settings/optimization.py b/src/antares/craft/model/settings/optimization.py index 328e8711..81b4208d 100644 --- a/src/antares/craft/model/settings/optimization.py +++ b/src/antares/craft/model/settings/optimization.py @@ -41,7 +41,7 @@ class ExportMPS(Enum): OPTIM2 = "optim2" -@dataclass +@dataclass(frozen=True) class OptimizationParameters: simplex_range: SimplexOptimizationRange = SimplexOptimizationRange.WEEK transmission_capacities: OptimizationTransmissionCapacities = OptimizationTransmissionCapacities.LOCAL_VALUES diff --git a/src/antares/craft/model/settings/playlist_parameters.py b/src/antares/craft/model/settings/playlist_parameters.py index ae7e466c..5fd9d5e3 100644 --- a/src/antares/craft/model/settings/playlist_parameters.py +++ b/src/antares/craft/model/settings/playlist_parameters.py @@ -12,7 +12,7 @@ from dataclasses import dataclass -@dataclass +@dataclass(frozen=True) class PlaylistParameters: status: bool weight: float diff --git a/src/antares/craft/model/settings/study_settings.py b/src/antares/craft/model/settings/study_settings.py index c3ac7055..48c572b6 100644 --- a/src/antares/craft/model/settings/study_settings.py +++ b/src/antares/craft/model/settings/study_settings.py @@ -36,7 +36,7 @@ class StudySettingsUpdate: playlist_parameters: Optional[dict[int, PlaylistParameters]] = None -@dataclass +@dataclass(frozen=True) class StudySettings: general_parameters: GeneralParameters = field(default_factory=GeneralParameters) optimization_parameters: OptimizationParameters = field(default_factory=OptimizationParameters) diff --git a/src/antares/craft/model/settings/thematic_trimming.py b/src/antares/craft/model/settings/thematic_trimming.py index 4293cbf8..b1d356b6 100644 --- a/src/antares/craft/model/settings/thematic_trimming.py +++ b/src/antares/craft/model/settings/thematic_trimming.py @@ -13,7 +13,7 @@ from typing import Optional -@dataclass +@dataclass(frozen=True) class ThematicTrimmingParameters: ov_cost: bool = False op_cost: bool = False diff --git a/src/antares/craft/model/st_storage.py b/src/antares/craft/model/st_storage.py index 43c868e6..895ff77e 100644 --- a/src/antares/craft/model/st_storage.py +++ b/src/antares/craft/model/st_storage.py @@ -52,7 +52,7 @@ class STStoragePropertiesUpdate: enabled: Optional[bool] = None -@dataclass +@dataclass(frozen=True) class STStorageProperties: group: STStorageGroup = STStorageGroup.OTHER1 injection_nominal_capacity: float = 0 diff --git a/src/antares/craft/model/study.py b/src/antares/craft/model/study.py index 86186ff1..aedd8ab5 100644 --- a/src/antares/craft/model/study.py +++ b/src/antares/craft/model/study.py @@ -9,7 +9,7 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. - +from dataclasses import replace from pathlib import Path, PurePath from types import MappingProxyType from typing import List, Optional, cast @@ -263,7 +263,10 @@ def move(self, parent_path: Path) -> None: def generate_thermal_timeseries(self, nb_years: int) -> None: self._study_service.generate_thermal_timeseries(nb_years) - self._settings.general_parameters.nb_timeseries_thermal = nb_years + # Copies objects to bypass the fact that the class is frozen + new_general_parameters = replace(self._settings.general_parameters, nb_timeseries_thermal=nb_years) + new_settings = replace(self._settings, general_parameters=new_general_parameters) + self._settings = new_settings # Design note: diff --git a/src/antares/craft/model/thermal.py b/src/antares/craft/model/thermal.py index a28ebee5..0d1cd572 100644 --- a/src/antares/craft/model/thermal.py +++ b/src/antares/craft/model/thermal.py @@ -64,7 +64,7 @@ class ThermalCostGeneration(Enum): USE_COST_TIME_SERIES = "useCostTimeseries" -@dataclass +@dataclass(frozen=True) class ThermalClusterProperties(ClusterProperties): group: ThermalClusterGroup = ThermalClusterGroup.OTHER1 gen_ts: LocalTSGenerationBehavior = LocalTSGenerationBehavior.USE_GLOBAL diff --git a/src/antares/craft/service/api_services/services/binding_constraint.py b/src/antares/craft/service/api_services/services/binding_constraint.py index 8c49bb3d..a6a9d3b1 100644 --- a/src/antares/craft/service/api_services/services/binding_constraint.py +++ b/src/antares/craft/service/api_services/services/binding_constraint.py @@ -9,7 +9,7 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. -from dataclasses import asdict +from dataclasses import asdict, replace from pathlib import PurePosixPath from typing import Any, Optional @@ -134,10 +134,11 @@ def update_binding_constraint_term( except APIError as e: raise ConstraintTermEditionError(constraint_id, term.id, e.message) from e + # Copies object to bypass the fact that the class is frozen if term.weight: - existing_term.weight = term.weight + existing_term = replace(existing_term, weight=term.weight) if term.offset: - existing_term.offset = term.offset + existing_term = replace(existing_term, offset=term.offset) return existing_term @override diff --git a/tests/antares/integration/test_local_client.py b/tests/antares/integration/test_local_client.py index 3fc318b5..f5a312d7 100644 --- a/tests/antares/integration/test_local_client.py +++ b/tests/antares/integration/test_local_client.py @@ -135,10 +135,11 @@ def test_local_study(self, tmp_path: Path, unknown_area): assert be_ui_file.is_file() # tests area creation with properties - properties = AreaProperties() - properties.energy_cost_spilled = 123 - properties.adequacy_patch_mode = AdequacyPatchMode.INSIDE - properties.filter_synthesis = [FilterOption.HOURLY, FilterOption.DAILY, FilterOption.HOURLY] + properties = AreaProperties( + energy_cost_spilled=123, + adequacy_patch_mode=AdequacyPatchMode.INSIDE, + filter_synthesis={FilterOption.HOURLY, FilterOption.DAILY, FilterOption.HOURLY}, + ) area_name = "DE" area_de = test_study.create_area(area_name, properties=properties) assert area_de.properties.energy_cost_spilled == 123 @@ -153,8 +154,7 @@ def test_local_study(self, tmp_path: Path, unknown_area): # tests link creation with ui and properties link_ui = LinkUi(colorr=44) - link_properties = LinkProperties(hurdles_cost=True) - link_properties.filter_year_by_year = [FilterOption.HOURLY] + link_properties = LinkProperties(hurdles_cost=True, filter_year_by_year={FilterOption.HOURLY}) link_be_fr = test_study.create_link(area_from=area_be.id, area_to=fr.id, ui=link_ui, properties=link_properties) assert link_be_fr.ui.colorr == 44 assert link_be_fr.properties.hurdles_cost @@ -181,8 +181,7 @@ def test_local_study(self, tmp_path: Path, unknown_area): # test thermal cluster creation with properties thermal_name = "gaz_be" - thermal_properties = ThermalClusterProperties(efficiency=55) - thermal_properties.group = ThermalClusterGroup.GAS + thermal_properties = ThermalClusterProperties(efficiency=55, group=ThermalClusterGroup.GAS) thermal_be = area_be.create_thermal_cluster(thermal_name, thermal_properties) properties = thermal_be.properties assert properties.efficiency == 55 @@ -196,8 +195,7 @@ def test_local_study(self, tmp_path: Path, unknown_area): # test renewable cluster creation with properties renewable_name = "wind_onshore" - renewable_properties = RenewableClusterProperties(enabled=False) - renewable_properties.group = RenewableClusterGroup.WIND_ON_SHORE + renewable_properties = RenewableClusterProperties(enabled=False, group=RenewableClusterGroup.WIND_ON_SHORE) renewable_onshore = fr.create_renewable_cluster(renewable_name, renewable_properties, None) properties = renewable_onshore.properties assert not properties.enabled @@ -211,16 +209,14 @@ def test_local_study(self, tmp_path: Path, unknown_area): # test short term storage creation with properties st_storage_name = "wind_onshore" - storage_properties = STStorageProperties(reservoir_capacity=0.5) - storage_properties.group = STStorageGroup.BATTERY + storage_properties = STStorageProperties(reservoir_capacity=0.5, group=STStorageGroup.BATTERY) battery_fr = fr.create_st_storage(st_storage_name, storage_properties) properties = battery_fr.properties assert properties.reservoir_capacity == 0.5 assert properties.group == STStorageGroup.BATTERY # test binding constraint creation without terms - properties = BindingConstraintProperties(enabled=False) - properties.group = "group_1" + properties = BindingConstraintProperties(enabled=False, group="group_1") constraint_1 = test_study.create_binding_constraint(name="bc_1", properties=properties) assert constraint_1.name == "bc_1" assert not constraint_1.properties.enabled diff --git a/tests/integration/test_web_client.py b/tests/integration/test_web_client.py index 3d41883e..8585b9f5 100644 --- a/tests/integration/test_web_client.py +++ b/tests/integration/test_web_client.py @@ -130,8 +130,11 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop, tmp_path): assert area_be.ui.color_rgb == area_ui.color_rgb # tests area creation with properties - properties = AreaProperties(energy_cost_spilled=100, adequacy_patch_mode=AdequacyPatchMode.INSIDE) - properties.filter_synthesis = [FilterOption.HOURLY, FilterOption.DAILY, FilterOption.HOURLY] + properties = AreaProperties( + energy_cost_spilled=100, + adequacy_patch_mode=AdequacyPatchMode.INSIDE, + filter_synthesis={FilterOption.HOURLY, FilterOption.DAILY, FilterOption.HOURLY}, + ) area_name = "DE" area_de = study.create_area(area_name, properties=properties) assert area_de.properties.energy_cost_spilled == 100 @@ -146,8 +149,7 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop, tmp_path): # tests link creation with ui and properties link_ui = LinkUi(colorr=44) - link_properties = LinkProperties(hurdles_cost=True) - link_properties.filter_year_by_year = [FilterOption.HOURLY] + link_properties = LinkProperties(hurdles_cost=True, filter_year_by_year={FilterOption.HOURLY}) link_be_fr = study.create_link(area_from=area_be.id, area_to=area_fr.id, ui=link_ui, properties=link_properties) assert link_be_fr.ui.colorr == 44 assert link_be_fr.properties.hurdles_cost @@ -179,8 +181,7 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop, tmp_path): # test thermal cluster creation with properties thermal_name = "gaz_be" - thermal_properties = ThermalClusterProperties(efficiency=55) - thermal_properties.group = ThermalClusterGroup.GAS + thermal_properties = ThermalClusterProperties(efficiency=55, group=ThermalClusterGroup.GAS) thermal_be = area_be.create_thermal_cluster(thermal_name, thermal_properties) properties = thermal_be.properties assert properties.efficiency == 55 @@ -238,8 +239,7 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop, tmp_path): # test renewable cluster creation with properties renewable_name = "wind_onshore" - renewable_properties = RenewableClusterProperties(enabled=False) - renewable_properties.group = RenewableClusterGroup.WIND_ON_SHORE + renewable_properties = RenewableClusterProperties(enabled=False, group=RenewableClusterGroup.WIND_ON_SHORE) renewable_onshore = area_fr.create_renewable_cluster(renewable_name, renewable_properties, None) properties = renewable_onshore.properties assert not properties.enabled @@ -303,8 +303,7 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop, tmp_path): # test short term storage creation with properties st_storage_name = "wind_onshore" - storage_properties = STStorageProperties(reservoir_capacity=0.5) - storage_properties.group = STStorageGroup.BATTERY + storage_properties = STStorageProperties(reservoir_capacity=0.5, group=STStorageGroup.BATTERY) battery_fr = area_fr.create_st_storage(st_storage_name, storage_properties) properties = battery_fr.properties assert properties.reservoir_capacity == 0.5 @@ -352,8 +351,7 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop, tmp_path): assert area_fr.get_st_storages() == {battery_fr.id: battery_fr, storage_fr.id: storage_fr} # test binding constraint creation without terms - properties = BindingConstraintProperties(enabled=False) - properties.group = "group_1" + properties = BindingConstraintProperties(enabled=False, group="group_1") constraint_1 = study.create_binding_constraint(name="bc_1", properties=properties) assert constraint_1.name == "bc_1" assert not constraint_1.properties.enabled