Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dataclasses): make user classes frozen to avoid careless modifications #99

Merged
merged 1 commit into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/antares/craft/model/area.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
12 changes: 6 additions & 6 deletions src/antares/craft/model/binding_constraint.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -58,7 +58,7 @@ class ClusterData:
cluster: str


@dataclass
@dataclass(frozen=True)
class ConstraintTermData:
data: Union[LinkData, ClusterData]

Expand All @@ -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
Expand All @@ -103,7 +103,7 @@ class BindingConstraintPropertiesUpdate:
group: Optional[str] = None


@dataclass
@dataclass(frozen=True)
class BindingConstraintProperties:
enabled: bool = True
time_step: BindingConstraintFrequency = BindingConstraintFrequency.HOURLY
Expand Down
2 changes: 1 addition & 1 deletion src/antares/craft/model/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from typing import Optional


@dataclass
@dataclass(frozen=True)
class ClusterProperties:
"""
Common properties for thermal and renewable clusters
Expand Down
2 changes: 1 addition & 1 deletion src/antares/craft/model/hydro.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/antares/craft/model/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/antares/craft/model/renewable.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/antares/craft/model/settings/adequacy_patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/antares/craft/model/settings/advanced_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/antares/craft/model/settings/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class OutputFormat(EnumIgnoreCase):
ZIP = "zip-files"


@dataclass
@dataclass(frozen=True)
class GeneralParameters:
mode: Mode = Mode.ECONOMY
horizon: str = ""
Expand Down
2 changes: 1 addition & 1 deletion src/antares/craft/model/settings/optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/antares/craft/model/settings/playlist_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from dataclasses import dataclass


@dataclass
@dataclass(frozen=True)
class PlaylistParameters:
status: bool
weight: float
2 changes: 1 addition & 1 deletion src/antares/craft/model/settings/study_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/antares/craft/model/settings/thematic_trimming.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from typing import Optional


@dataclass
@dataclass(frozen=True)
class ThematicTrimmingParameters:
ov_cost: bool = False
op_cost: bool = False
Expand Down
2 changes: 1 addition & 1 deletion src/antares/craft/model/st_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions src/antares/craft/model/study.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion src/antares/craft/model/thermal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
24 changes: 10 additions & 14 deletions tests/antares/integration/test_local_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
22 changes: 10 additions & 12 deletions tests/integration/test_web_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading