diff --git a/antarest/core/model.py b/antarest/core/model.py index 78aa7a1e82..adb554e542 100644 --- a/antarest/core/model.py +++ b/antarest/core/model.py @@ -13,6 +13,9 @@ import enum from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union +import typing_extensions as te +from pydantic import StringConstraints + from antarest.core.serialization import AntaresBaseModel if TYPE_CHECKING: @@ -22,6 +25,7 @@ JSON = Dict[str, Any] ELEMENT = Union[str, int, float, bool, bytes] SUB_JSON = Union[ELEMENT, JSON, List[Any], None] +LowerCaseStr = te.Annotated[str, StringConstraints(to_lower=True)] class PublicMode(enum.StrEnum): diff --git a/antarest/launcher/extensions/adequacy_patch/extension.py b/antarest/launcher/extensions/adequacy_patch/extension.py index 20cd3407cd..bb4d19f323 100644 --- a/antarest/launcher/extensions/adequacy_patch/extension.py +++ b/antarest/launcher/extensions/adequacy_patch/extension.py @@ -23,7 +23,7 @@ from antarest.core.utils.utils import assert_this from antarest.launcher.extensions.interface import ILauncherExtension from antarest.study.service import StudyService -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy logger = logging.getLogger(__name__) diff --git a/antarest/study/business/area_management.py b/antarest/study/business/area_management.py index 5220b0e8e1..e2b89f7979 100644 --- a/antarest/study/business/area_management.py +++ b/antarest/study/business/area_management.py @@ -32,7 +32,8 @@ ThermalAreasProperties, UIProperties, ) -from antarest.study.storage.rawstudy.model.filesystem.config.model import Area, DistrictSet, transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.model import Area, DistrictSet from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.storage_service import StudyStorageService from antarest.study.storage.variantstudy.model.command.create_area import CreateArea diff --git a/antarest/study/business/areas/renewable_management.py b/antarest/study/business/areas/renewable_management.py index 2063750ca3..14ba0e60e7 100644 --- a/antarest/study/business/areas/renewable_management.py +++ b/antarest/study/business/areas/renewable_management.py @@ -22,12 +22,13 @@ from antarest.study.business.enum_ignore_case import EnumIgnoreCase from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import Study -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.config.renewable import ( RenewableConfig, RenewableConfigType, RenewableProperties, create_renewable_config, + create_renewable_properties, ) from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.storage_service import StudyStorageService @@ -274,7 +275,6 @@ def update_cluster( Raises: RenewableClusterNotFound: If the cluster to update is not found. """ - study_version = StudyVersion.parse(study.version) file_study = self._get_file_study(study) path = _CLUSTER_PATH.format(area_id=area_id, cluster_id=cluster_id) @@ -284,19 +284,19 @@ def update_cluster( except KeyError: raise RenewableClusterNotFound(path, cluster_id) from None else: - old_config = create_renewable_config(study_version, **values) + old_properties = create_renewable_properties(study_version, **values) # use Python values to synchronize Config and Form values new_values = cluster_data.model_dump(by_alias=False, exclude_none=True) - new_config = old_config.copy(exclude={"id"}, update=new_values) - new_data = new_config.model_dump(mode="json", by_alias=True, exclude={"id"}) + new_properties = old_properties.copy(exclude={"id"}, update=new_values) # create the dict containing the new values using aliases data: t.Dict[str, t.Any] = {} - for field_name, field in new_config.model_fields.items(): - if field_name in new_values: - name = field.alias if field.alias else field_name - data[name] = new_data[name] + for updated_field, updated_value in new_values.items(): + if updated_field in old_properties.model_fields: + field_info = old_properties.model_fields[updated_field] + field_name = field_info.alias if field_info.alias else updated_field + data[field_name] = updated_value # create the update config commands with the modified data command_context = self.storage_service.variant_study_service.command_factory.command_context @@ -308,7 +308,7 @@ def update_cluster( ] execute_or_add_commands(study, file_study, commands, self.storage_service) - values = new_config.model_dump(by_alias=False) + values = new_properties.model_dump(by_alias=False) return RenewableClusterOutput(**values, id=cluster_id) def delete_clusters(self, study: Study, area_id: str, cluster_ids: t.Sequence[str]) -> None: @@ -357,9 +357,8 @@ def duplicate_cluster( Raises: DuplicateRenewableCluster: If a cluster with the new name already exists in the area. """ - new_id = transform_name_to_id(new_cluster_name, lower=False) - lower_new_id = new_id.lower() - if any(lower_new_id == cluster.id.lower() for cluster in self.get_clusters(study, area_id)): + new_id = transform_name_to_id(new_cluster_name) + if any(new_id == cluster.id for cluster in self.get_clusters(study, area_id)): raise DuplicateRenewableCluster(area_id, new_id) # Cluster duplication @@ -371,9 +370,8 @@ def duplicate_cluster( create_cluster_cmd = self._make_create_cluster_cmd(area_id, new_config, study_version) # Matrix edition - lower_source_id = source_id.lower() - source_path = f"input/renewables/series/{area_id}/{lower_source_id}/series" - new_path = f"input/renewables/series/{area_id}/{lower_new_id}/series" + source_path = f"input/renewables/series/{area_id}/{source_id}/series" + new_path = f"input/renewables/series/{area_id}/{new_id}/series" # Prepare and execute commands storage_service = self.storage_service.get_storage(study) diff --git a/antarest/study/business/areas/st_storage_management.py b/antarest/study/business/areas/st_storage_management.py index b0ef5322ab..5786b4183b 100644 --- a/antarest/study/business/areas/st_storage_management.py +++ b/antarest/study/business/areas/st_storage_management.py @@ -33,7 +33,7 @@ from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import STUDY_VERSION_8_8, Study -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.config.st_storage import ( STStorage880Config, STStorage880Properties, @@ -305,7 +305,7 @@ def _make_create_cluster_cmd( ) -> CreateSTStorage: command = CreateSTStorage( area_id=area_id, - parameters=cluster, + parameters=cluster.model_dump(mode="json", by_alias=True, exclude={"id"}), command_context=self.storage_service.variant_study_service.command_factory.command_context, study_version=study_version, ) @@ -551,8 +551,7 @@ def duplicate_cluster(self, study: Study, area_id: str, source_id: str, new_clus ClusterAlreadyExists: If a cluster with the new name already exists in the area. """ new_id = transform_name_to_id(new_cluster_name) - lower_new_id = new_id.lower() - if any(lower_new_id == storage.id.lower() for storage in self.get_storages(study, area_id)): + if any(new_id == storage.id for storage in self.get_storages(study, area_id)): raise DuplicateSTStorage(area_id, new_id) # Cluster duplication @@ -571,16 +570,13 @@ def duplicate_cluster(self, study: Study, area_id: str, source_id: str, new_clus create_cluster_cmd = self._make_create_cluster_cmd(area_id, new_config, study_version) # Matrix edition - lower_source_id = source_id.lower() # noinspection SpellCheckingInspection ts_names = ["pmax_injection", "pmax_withdrawal", "lower_rule_curve", "upper_rule_curve", "inflows"] source_paths = [ - _STORAGE_SERIES_PATH.format(area_id=area_id, storage_id=lower_source_id, ts_name=ts_name) - for ts_name in ts_names + _STORAGE_SERIES_PATH.format(area_id=area_id, storage_id=source_id, ts_name=ts_name) for ts_name in ts_names ] new_paths = [ - _STORAGE_SERIES_PATH.format(area_id=area_id, storage_id=lower_new_id, ts_name=ts_name) - for ts_name in ts_names + _STORAGE_SERIES_PATH.format(area_id=area_id, storage_id=new_id, ts_name=ts_name) for ts_name in ts_names ] # Prepare and execute commands diff --git a/antarest/study/business/areas/thermal_management.py b/antarest/study/business/areas/thermal_management.py index cbd0f7e997..150dd7adfa 100644 --- a/antarest/study/business/areas/thermal_management.py +++ b/antarest/study/business/areas/thermal_management.py @@ -28,12 +28,13 @@ from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import STUDY_VERSION_8_7, Study -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.config.thermal import ( Thermal870Config, Thermal870Properties, ThermalConfigType, create_thermal_config, + create_thermal_properties, ) from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.storage_service import StudyStorageService @@ -348,7 +349,6 @@ def update_cluster( ThermalClusterNotFound: If the provided `cluster_id` does not match the ID of the cluster in the provided cluster_data. """ - study_version = StudyVersion.parse(study.version) file_study = self._get_file_study(study) path = _CLUSTER_PATH.format(area_id=area_id, cluster_id=cluster_id) @@ -357,19 +357,19 @@ def update_cluster( except KeyError: raise ThermalClusterNotFound(path, cluster_id) from None else: - old_config = create_thermal_config(study_version, **values) + old_properties = create_thermal_properties(study_version, **values) # Use Python values to synchronize Config and Form values new_values = cluster_data.model_dump(mode="json", by_alias=False, exclude_none=True) - new_config = old_config.copy(exclude={"id"}, update=new_values) - new_data = new_config.model_dump(mode="json", by_alias=True, exclude={"id"}) + new_properties = old_properties.copy(exclude={"id"}, update=new_values) # create the dict containing the new values using aliases data: t.Dict[str, t.Any] = {} - for field_name, field in new_config.model_fields.items(): - if field_name in new_values: - name = field.alias if field.alias else field_name - data[name] = new_data[name] + for updated_field, updated_value in new_values.items(): + if updated_field in old_properties.model_fields: + field_info = old_properties.model_fields[updated_field] + field_name = field_info.alias if field_info.alias else updated_field + data[field_name] = updated_value # create the update config commands with the modified data command_context = self.storage_service.variant_study_service.command_factory.command_context @@ -381,7 +381,7 @@ def update_cluster( ] execute_or_add_commands(study, file_study, commands, self.storage_service) - values = {**new_config.model_dump(mode="json", by_alias=False), "id": cluster_id} + values = {**new_properties.model_dump(mode="json", by_alias=False), "id": cluster_id} return ThermalClusterOutput.model_validate(values) def delete_clusters(self, study: Study, area_id: str, cluster_ids: t.Sequence[str]) -> None: @@ -431,9 +431,8 @@ def duplicate_cluster( Raises: ClusterAlreadyExists: If a cluster with the new name already exists in the area. """ - new_id = transform_name_to_id(new_cluster_name, lower=False) - lower_new_id = new_id.lower() - if any(lower_new_id == cluster.id.lower() for cluster in self.get_clusters(study, area_id)): + new_id = transform_name_to_id(new_cluster_name) + if any(new_id == cluster.id for cluster in self.get_clusters(study, area_id)): raise DuplicateThermalCluster(area_id, new_id) # Cluster duplication @@ -445,23 +444,22 @@ def duplicate_cluster( create_cluster_cmd = self._make_create_cluster_cmd(area_id, new_config, study_version) # Matrix edition - lower_source_id = source_id.lower() source_paths = [ - f"input/thermal/series/{area_id}/{lower_source_id}/series", - f"input/thermal/prepro/{area_id}/{lower_source_id}/modulation", - f"input/thermal/prepro/{area_id}/{lower_source_id}/data", + f"input/thermal/series/{area_id}/{source_id}/series", + f"input/thermal/prepro/{area_id}/{source_id}/modulation", + f"input/thermal/prepro/{area_id}/{source_id}/data", ] new_paths = [ - f"input/thermal/series/{area_id}/{lower_new_id}/series", - f"input/thermal/prepro/{area_id}/{lower_new_id}/modulation", - f"input/thermal/prepro/{area_id}/{lower_new_id}/data", + f"input/thermal/series/{area_id}/{new_id}/series", + f"input/thermal/prepro/{area_id}/{new_id}/modulation", + f"input/thermal/prepro/{area_id}/{new_id}/data", ] study_version = StudyVersion.parse(study.version) if study_version >= STUDY_VERSION_8_7: - source_paths.append(f"input/thermal/series/{area_id}/{lower_source_id}/CO2Cost") - source_paths.append(f"input/thermal/series/{area_id}/{lower_source_id}/fuelCost") - new_paths.append(f"input/thermal/series/{area_id}/{lower_new_id}/CO2Cost") - new_paths.append(f"input/thermal/series/{area_id}/{lower_new_id}/fuelCost") + source_paths.append(f"input/thermal/series/{area_id}/{source_id}/CO2Cost") + source_paths.append(f"input/thermal/series/{area_id}/{source_id}/fuelCost") + new_paths.append(f"input/thermal/series/{area_id}/{new_id}/CO2Cost") + new_paths.append(f"input/thermal/series/{area_id}/{new_id}/fuelCost") # Prepare and execute commands commands: t.List[t.Union[CreateCluster, ReplaceMatrix]] = [create_cluster_cmd] @@ -479,8 +477,7 @@ def duplicate_cluster( return ThermalClusterOutput(**new_config.model_dump(mode="json", by_alias=False)) def validate_series(self, study: Study, area_id: str, cluster_id: str) -> bool: - lower_cluster_id = cluster_id.lower() - thermal_cluster_path = Path(f"input/thermal/series/{area_id}/{lower_cluster_id}") + thermal_cluster_path = Path(f"input/thermal/series/{area_id}/{cluster_id.lower()}") series_path = [thermal_cluster_path / "series"] if StudyVersion.parse(study.version) >= STUDY_VERSION_8_7: series_path.append(thermal_cluster_path / "CO2Cost") diff --git a/antarest/study/business/binding_constraint_management.py b/antarest/study/business/binding_constraint_management.py index 19e9c0f782..de461cf681 100644 --- a/antarest/study/business/binding_constraint_management.py +++ b/antarest/study/business/binding_constraint_management.py @@ -30,7 +30,7 @@ MatrixWidthMismatchError, WrongMatrixHeightError, ) -from antarest.core.model import JSON +from antarest.core.model import JSON, LowerCaseStr from antarest.core.requests import CaseInsensitiveDict from antarest.core.serialization import AntaresBaseModel from antarest.core.utils.string import to_camel_case @@ -44,7 +44,7 @@ BindingConstraintFrequency, BindingConstraintOperator, ) -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.storage_service import StudyStorageService from antarest.study.storage.variantstudy.business.matrix_constants.binding_constraint.series_after_v87 import ( @@ -340,7 +340,7 @@ class ConstraintOutput830(ConstraintOutputBase): class ConstraintOutput870(ConstraintOutput830): - group: str = DEFAULT_GROUP + group: LowerCaseStr = DEFAULT_GROUP # WARNING: Do not change the order of the following line, it is used to determine diff --git a/antarest/study/business/district_manager.py b/antarest/study/business/district_manager.py index 2cfec172d8..9147fb22f2 100644 --- a/antarest/study/business/district_manager.py +++ b/antarest/study/business/district_manager.py @@ -16,7 +16,7 @@ from antarest.core.serialization import AntaresBaseModel from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import Study -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from antarest.study.storage.storage_service import StudyStorageService from antarest.study.storage.variantstudy.model.command.create_district import CreateDistrict, DistrictBaseFilter from antarest.study.storage.variantstudy.model.command.remove_district import RemoveDistrict diff --git a/antarest/study/business/table_mode_management.py b/antarest/study/business/table_mode_management.py index aba99c9b57..e7a12b0289 100644 --- a/antarest/study/business/table_mode_management.py +++ b/antarest/study/business/table_mode_management.py @@ -217,7 +217,9 @@ def update_table_data( thermals_by_areas = collections.defaultdict(dict) for key, values in data.items(): area_id, cluster_id = key.split(" / ") - thermals_by_areas[area_id][cluster_id] = ThermalClusterInput(**values) + # Thermal clusters ids were not lowered at the time. + # So to ensure this endpoint still works with old scripts we have to lower the id at first. + thermals_by_areas[area_id][cluster_id.lower()] = ThermalClusterInput(**values) thermals_map = self._thermal_manager.update_thermals_props(study, thermals_by_areas) data = { f"{area_id} / {cluster_id}": cluster.model_dump(by_alias=True, exclude={"id", "name"}) @@ -230,7 +232,8 @@ def update_table_data( renewables_by_areas = collections.defaultdict(dict) for key, values in data.items(): area_id, cluster_id = key.split(" / ") - renewables_by_areas[area_id][cluster_id] = RenewableClusterInput(**values) + # Same reason as for thermal clusters + renewables_by_areas[area_id][cluster_id.lower()] = RenewableClusterInput(**values) renewables_map = self._renewable_manager.update_renewables_props(study, renewables_by_areas) data = { f"{area_id} / {cluster_id}": cluster.model_dump(by_alias=True, exclude={"id", "name"}) diff --git a/antarest/study/storage/rawstudy/model/filesystem/config/cluster.py b/antarest/study/storage/rawstudy/model/filesystem/config/cluster.py index f2a6349d90..238012858e 100644 --- a/antarest/study/storage/rawstudy/model/filesystem/config/cluster.py +++ b/antarest/study/storage/rawstudy/model/filesystem/config/cluster.py @@ -21,6 +21,7 @@ from pydantic import Field +from antarest.core.model import LowerCaseStr from antarest.core.serialization import AntaresBaseModel @@ -47,9 +48,9 @@ class ItemProperties( [('group-A', 'cluster-01'), ('GROUP-A', 'cluster-02'), ('Group-B', 'CLUSTER-01')] """ - group: str = Field(default="", description="Cluster group") + group: LowerCaseStr = Field(default="", description="Cluster group") - name: str = Field(description="Cluster name", pattern=r"[a-zA-Z0-9_(),& -]+") + name: LowerCaseStr = Field(description="Cluster name", pattern=r"[a-zA-Z0-9_(),& -]+") def __lt__(self, other: t.Any) -> bool: """ @@ -58,7 +59,7 @@ def __lt__(self, other: t.Any) -> bool: This method may be used to sort and group clusters by `group` and `name`. """ if isinstance(other, ItemProperties): - return (self.group.upper(), self.name.upper()).__lt__((other.group.upper(), other.name.upper())) + return (self.group, self.name).__lt__((other.group, other.name)) return NotImplemented diff --git a/antarest/study/storage/rawstudy/model/filesystem/config/field_validators.py b/antarest/study/storage/rawstudy/model/filesystem/config/field_validators.py index f6a371769b..5761cdc587 100644 --- a/antarest/study/storage/rawstudy/model/filesystem/config/field_validators.py +++ b/antarest/study/storage/rawstudy/model/filesystem/config/field_validators.py @@ -9,7 +9,7 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. - +import re import typing as t _ALL_FILTERING = ["hourly", "daily", "weekly", "monthly", "annual"] @@ -87,3 +87,28 @@ def validate_color_rgb(v: t.Any) -> str: raise TypeError(f"Invalid type for 'color_rgb': {type(v)}") return f"#{r:02X}{g:02X}{b:02X}" + + +# Invalid chars was taken from Antares Simulator (C++). +_sub_invalid_chars = re.compile(r"[^a-zA-Z0-9_(),& -]+").sub + + +def transform_name_to_id(name: str) -> str: + """ + Transform a name into an identifier by replacing consecutive + invalid characters by a single white space, then whitespaces + are striped from both ends and the id is lowered. + + Valid characters are `[a-zA-Z0-9_(),& -]` (including space). + + Args: + name: The name to convert. + """ + return _sub_invalid_chars(" ", name).strip().lower() + + +def validate_id_against_name(name: str) -> str: + to_return = transform_name_to_id(name) + if not to_return: + raise ValueError("Cluster name must only contains [a-zA-Z0-9],&,-,_,(,) characters") + return to_return diff --git a/antarest/study/storage/rawstudy/model/filesystem/config/files.py b/antarest/study/storage/rawstudy/model/filesystem/config/files.py index 8f23fecd25..4d65f380b9 100644 --- a/antarest/study/storage/rawstudy/model/filesystem/config/files.py +++ b/antarest/study/storage/rawstudy/model/filesystem/config/files.py @@ -42,7 +42,10 @@ SimulationParsingError, XpansionParsingError, ) -from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import extract_filtering +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import ( + extract_filtering, + transform_name_to_id, +) from antarest.study.storage.rawstudy.model.filesystem.config.model import ( Area, BindingConstraintDTO, @@ -50,7 +53,6 @@ FileStudyTreeConfig, Link, Simulation, - transform_name_to_id, ) from antarest.study.storage.rawstudy.model.filesystem.config.renewable import ( RenewableConfigType, diff --git a/antarest/study/storage/rawstudy/model/filesystem/config/identifier.py b/antarest/study/storage/rawstudy/model/filesystem/config/identifier.py index ab428fca75..66e69fc7d1 100644 --- a/antarest/study/storage/rawstudy/model/filesystem/config/identifier.py +++ b/antarest/study/storage/rawstudy/model/filesystem/config/identifier.py @@ -14,12 +14,12 @@ from pydantic import Field, model_validator -__all__ = ("IgnoreCaseIdentifier", "LowerCaseIdentifier") +__all__ = "LowerCaseIdentifier" from antarest.core.serialization import AntaresBaseModel -class IgnoreCaseIdentifier( +class LowerCaseIdentifier( AntaresBaseModel, extra="forbid", validate_assignment=True, @@ -43,9 +43,9 @@ def generate_id(cls, name: str) -> str: The ID of the section. """ # Avoid circular imports - from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id + from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id - return transform_name_to_id(name, lower=False) + return transform_name_to_id(name) @model_validator(mode="before") def validate_id(cls, values: t.MutableMapping[str, t.Any]) -> t.Mapping[str, t.Any]: @@ -74,27 +74,3 @@ def validate_id(cls, values: t.MutableMapping[str, t.Any]) -> t.Mapping[str, t.A else: raise ValueError(f"Invalid name '{name}'.") return values - - -class LowerCaseIdentifier(IgnoreCaseIdentifier): - """ - Base class for all configuration sections with a lower case ID. - """ - - id: str = Field(description="ID (section name)", pattern=r"[a-z0-9_(),& -]+") - - @classmethod - def generate_id(cls, name: str) -> str: - """ - Generate an ID from a name. - - Args: - name: Name of a section read from an INI file - - Returns: - The ID of the section. - """ - # Avoid circular imports - from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id - - return transform_name_to_id(name, lower=True) diff --git a/antarest/study/storage/rawstudy/model/filesystem/config/model.py b/antarest/study/storage/rawstudy/model/filesystem/config/model.py index 2f96b845a2..938cddaba3 100644 --- a/antarest/study/storage/rawstudy/model/filesystem/config/model.py +++ b/antarest/study/storage/rawstudy/model/filesystem/config/model.py @@ -285,26 +285,6 @@ def get_filters_year(self, area: str, link: t.Optional[str] = None) -> t.List[st return self.areas[area].filters_year -# Invalid chars was taken from Antares Simulator (C++). -_sub_invalid_chars = re.compile(r"[^a-zA-Z0-9_(),& -]+").sub - - -def transform_name_to_id(name: str, lower: bool = True) -> str: - """ - Transform a name into an identifier by replacing consecutive - invalid characters by a single white space, and then whitespaces - are striped from both ends. - - Valid characters are `[a-zA-Z0-9_(),& -]` (including space). - - Args: - name: The name to convert. - lower: The flag used to turn the identifier in lower case. - """ - valid_id = _sub_invalid_chars(" ", name).strip() - return valid_id.lower() if lower else valid_id - - class FileStudyTreeConfigDTO(AntaresBaseModel): study_path: Path path: Path diff --git a/antarest/study/storage/rawstudy/model/filesystem/config/renewable.py b/antarest/study/storage/rawstudy/model/filesystem/config/renewable.py index e796ed1beb..dcd7e6f16c 100644 --- a/antarest/study/storage/rawstudy/model/filesystem/config/renewable.py +++ b/antarest/study/storage/rawstudy/model/filesystem/config/renewable.py @@ -18,7 +18,7 @@ from antarest.study.business.enum_ignore_case import EnumIgnoreCase from antarest.study.model import STUDY_VERSION_8_1 from antarest.study.storage.rawstudy.model.filesystem.config.cluster import ClusterProperties -from antarest.study.storage.rawstudy.model.filesystem.config.identifier import IgnoreCaseIdentifier +from antarest.study.storage.rawstudy.model.filesystem.config.identifier import LowerCaseIdentifier class TimeSeriesInterpretation(EnumIgnoreCase): @@ -44,15 +44,15 @@ class RenewableClusterGroup(EnumIgnoreCase): If not specified, the renewable cluster will be part of the group "Other RES 1". """ - THERMAL_SOLAR = "Solar Thermal" - PV_SOLAR = "Solar PV" - ROOFTOP_SOLAR = "Solar Rooftop" - WIND_ON_SHORE = "Wind Onshore" - WIND_OFF_SHORE = "Wind Offshore" - OTHER1 = "Other RES 1" - OTHER2 = "Other RES 2" - OTHER3 = "Other RES 3" - OTHER4 = "Other RES 4" + THERMAL_SOLAR = "solar thermal" + PV_SOLAR = "solar pv" + ROOFTOP_SOLAR = "solar rooftop" + WIND_ON_SHORE = "wind onshore" + WIND_OFF_SHORE = "wind offshore" + OTHER1 = "other res 1" + OTHER2 = "other res 2" + OTHER3 = "other res 3" + OTHER4 = "other res 4" def __repr__(self) -> str: return f"{self.__class__.__name__}.{self.name}" @@ -65,7 +65,7 @@ def _missing_(cls, value: object) -> t.Optional["RenewableClusterGroup"]: if isinstance(value, str): # Check if any group value matches the input value ignoring case sensitivity. # noinspection PyUnresolvedReferences - if any(value.upper() == group.value.upper() for group in cls): + if any(value.lower() == group.value for group in cls): return t.cast(RenewableClusterGroup, super()._missing_(value)) # If a group is not found, return the default group ('OTHER1' by default). return cls.OTHER1 @@ -91,7 +91,7 @@ class RenewableProperties(ClusterProperties): ) -class RenewableConfig(RenewableProperties, IgnoreCaseIdentifier): +class RenewableConfig(RenewableProperties, LowerCaseIdentifier): """ Configuration of a renewable cluster. @@ -110,6 +110,7 @@ class RenewableConfig(RenewableProperties, IgnoreCaseIdentifier): RenewableConfigType = RenewableConfig +RenewablePropertiesType = RenewableProperties def get_renewable_config_cls(study_version: StudyVersion) -> t.Type[RenewableConfig]: @@ -127,6 +128,25 @@ def get_renewable_config_cls(study_version: StudyVersion) -> t.Type[RenewableCon raise ValueError(f"Unsupported study version {study_version}, required 810 or above.") +def create_renewable_properties(study_version: StudyVersion, **kwargs: t.Any) -> RenewablePropertiesType: + """ + Factory method to create renewable properties. + + Args: + study_version: The version of the study. + **kwargs: The properties to be used to initialize the model. + + Returns: + The renewable properties. + + Raises: + ValueError: If the study version is not supported. + """ + if study_version >= STUDY_VERSION_8_1: + return RenewableProperties.model_validate(kwargs) + raise ValueError(f"Unsupported study version {study_version}, required 810 or above.") + + def create_renewable_config(study_version: StudyVersion, **kwargs: t.Any) -> RenewableConfigType: """ Factory method to create a renewable configuration model. diff --git a/antarest/study/storage/rawstudy/model/filesystem/config/st_storage.py b/antarest/study/storage/rawstudy/model/filesystem/config/st_storage.py index 426e263baa..74df0803ac 100644 --- a/antarest/study/storage/rawstudy/model/filesystem/config/st_storage.py +++ b/antarest/study/storage/rawstudy/model/filesystem/config/st_storage.py @@ -34,15 +34,15 @@ class STStorageGroup(EnumIgnoreCase): - OTHER1...OTHER5: Represents other energy storage systems. """ - PSP_OPEN = "PSP_open" - PSP_CLOSED = "PSP_closed" - PONDAGE = "Pondage" - BATTERY = "Battery" - OTHER1 = "Other1" - OTHER2 = "Other2" - OTHER3 = "Other3" - OTHER4 = "Other4" - OTHER5 = "Other5" + PSP_OPEN = "psp_open" + PSP_CLOSED = "psp_closed" + PONDAGE = "pondage" + BATTERY = "battery" + OTHER1 = "other1" + OTHER2 = "other2" + OTHER3 = "other3" + OTHER4 = "other4" + OTHER5 = "other5" # noinspection SpellCheckingInspection @@ -161,6 +161,28 @@ class STStorage880Config(STStorage880Properties, LowerCaseIdentifier): # NOTE: In the following Union, it is important to place the older version first, # because otherwise, creating a short term storage always creates a v8.8 one. STStorageConfigType = t.Union[STStorageConfig, STStorage880Config] +STStoragePropertiesType = t.Union[STStorageProperties, STStorage880Properties] + + +def create_st_storage_properties(study_version: StudyVersion, **kwargs: t.Any) -> STStoragePropertiesType: + """ + Factory method to create st_storage properties. + + Args: + study_version: The version of the study. + **kwargs: The properties to be used to initialize the model. + + Returns: + The short term storage properties. + + Raises: + ValueError: If the study version is not supported. + """ + if study_version >= STUDY_VERSION_8_8: + return STStorage880Properties.model_validate(kwargs) + elif study_version >= STUDY_VERSION_8_6: + return STStorageProperties.model_validate(kwargs) + raise ValueError(f"Unsupported study version: {study_version}") def get_st_storage_config_cls(study_version: StudyVersion) -> t.Type[STStorageConfigType]: diff --git a/antarest/study/storage/rawstudy/model/filesystem/config/thermal.py b/antarest/study/storage/rawstudy/model/filesystem/config/thermal.py index ff095c2b2e..5093c9c289 100644 --- a/antarest/study/storage/rawstudy/model/filesystem/config/thermal.py +++ b/antarest/study/storage/rawstudy/model/filesystem/config/thermal.py @@ -13,11 +13,11 @@ import typing as t from antares.study.version import StudyVersion -from pydantic import Field +from pydantic import Field, ValidationError from antarest.study.business.enum_ignore_case import EnumIgnoreCase from antarest.study.storage.rawstudy.model.filesystem.config.cluster import ClusterProperties -from antarest.study.storage.rawstudy.model.filesystem.config.identifier import IgnoreCaseIdentifier +from antarest.study.storage.rawstudy.model.filesystem.config.identifier import LowerCaseIdentifier class LocalTSGenerationBehavior(EnumIgnoreCase): @@ -58,16 +58,16 @@ class ThermalClusterGroup(EnumIgnoreCase): The group `OTHER1` is used by default. """ - NUCLEAR = "Nuclear" - LIGNITE = "Lignite" - HARD_COAL = "Hard Coal" - GAS = "Gas" - OIL = "Oil" - MIXED_FUEL = "Mixed Fuel" - OTHER1 = "Other 1" - OTHER2 = "Other 2" - OTHER3 = "Other 3" - OTHER4 = "Other 4" + NUCLEAR = "nuclear" + LIGNITE = "lignite" + HARD_COAL = "hard coal" + GAS = "gas" + OIL = "oil" + MIXED_FUEL = "mixed fuel" + OTHER1 = "other 1" + OTHER2 = "other 2" + OTHER3 = "other 3" + OTHER4 = "other 4" def __repr__(self) -> str: # pragma: no cover return f"{self.__class__.__name__}.{self.name}" @@ -80,10 +80,8 @@ def _missing_(cls, value: object) -> t.Optional["ThermalClusterGroup"]: if isinstance(value, str): # Check if any group value matches the input value ignoring case sensitivity. # noinspection PyUnresolvedReferences - if any(value.upper() == group.value.upper() for group in cls): + if any(value.lower() == group.value for group in cls): return t.cast(ThermalClusterGroup, super()._missing_(value)) - # If a group is not found, return the default group ('OTHER1' by default). - # Note that 'OTHER' is an alias for 'OTHER1'. return cls.OTHER1 return t.cast(t.Optional["ThermalClusterGroup"], super()._missing_(value)) @@ -330,7 +328,7 @@ class Thermal870Properties(Thermal860Properties): ) -class ThermalConfig(ThermalProperties, IgnoreCaseIdentifier): +class ThermalConfig(ThermalProperties, LowerCaseIdentifier): """ Thermal properties with section ID. @@ -351,7 +349,7 @@ class ThermalConfig(ThermalProperties, IgnoreCaseIdentifier): AttributeError: 'ThermalConfig' object has no attribute 'nh3'""" -class Thermal860Config(Thermal860Properties, IgnoreCaseIdentifier): +class Thermal860Config(Thermal860Properties, LowerCaseIdentifier): """ Thermal properties for study in version 860 @@ -373,7 +371,7 @@ class Thermal860Config(Thermal860Properties, IgnoreCaseIdentifier): """ -class Thermal870Config(Thermal870Properties, IgnoreCaseIdentifier): +class Thermal870Config(Thermal870Properties, LowerCaseIdentifier): """ Thermal properties for study in version 8.7 or above. @@ -404,6 +402,7 @@ class Thermal870Config(Thermal870Properties, IgnoreCaseIdentifier): # NOTE: In the following Union, it is important to place the most specific type first, # because the type matching generally occurs sequentially from left to right within the union. ThermalConfigType = t.Union[Thermal870Config, Thermal860Config, ThermalConfig] +ThermalPropertiesType = t.Union[Thermal870Properties, Thermal860Properties, ThermalProperties] def get_thermal_config_cls(study_version: StudyVersion) -> t.Type[ThermalConfigType]: @@ -424,6 +423,28 @@ def get_thermal_config_cls(study_version: StudyVersion) -> t.Type[ThermalConfigT return ThermalConfig +def create_thermal_properties(study_version: StudyVersion, **kwargs: t.Any) -> ThermalPropertiesType: + """ + Factory method to create thermal properties. + + Args: + study_version: The version of the study. + **kwargs: The properties to be used to initialize the model. + + Returns: + The thermal properties. + + Raises: + ValueError: If the study version is not supported. + """ + if study_version >= 870: + return Thermal870Properties.model_validate(kwargs) + elif study_version == 860: + return Thermal860Properties.model_validate(kwargs) + else: + return ThermalProperties.model_validate(kwargs) + + def create_thermal_config(study_version: StudyVersion, **kwargs: t.Any) -> ThermalConfigType: """ Factory method to create a thermal configuration model. diff --git a/antarest/study/storage/rawstudy/model/filesystem/root/input/thermal/prepro/area/area.py b/antarest/study/storage/rawstudy/model/filesystem/root/input/thermal/prepro/area/area.py index daba4ac183..a804f5dbae 100644 --- a/antarest/study/storage/rawstudy/model/filesystem/root/input/thermal/prepro/area/area.py +++ b/antarest/study/storage/rawstudy/model/filesystem/root/input/thermal/prepro/area/area.py @@ -30,11 +30,8 @@ def __init__( self.area = area def build(self) -> TREE: - # Note that cluster IDs are case-insensitive, but series IDs are in lower case. - # For instance, if your cluster ID is "Base", then the series ID will be "base". - series_ids = map(str.lower, self.config.get_thermal_ids(self.area)) children: TREE = { series_id: InputThermalPreproAreaThermal(self.context, self.config.next_file(series_id)) - for series_id in series_ids + for series_id in self.config.get_thermal_ids(self.area) } return children diff --git a/antarest/study/storage/variantstudy/business/command_extractor.py b/antarest/study/storage/variantstudy/business/command_extractor.py index 2da4ac4271..0f034f8a37 100644 --- a/antarest/study/storage/variantstudy/business/command_extractor.py +++ b/antarest/study/storage/variantstudy/business/command_extractor.py @@ -222,7 +222,7 @@ def _extract_cluster(self, study: FileStudy, area_id: str, cluster_id: str, rene create_cluster_command( area_id=area_id, cluster_name=cluster.id, - parameters=cluster.model_dump(by_alias=True, exclude_defaults=True, exclude={"id"}), + parameters=cluster.model_dump(mode="json", by_alias=True, exclude={"id"}), command_context=self.command_context, study_version=study_tree.config.version, ), diff --git a/antarest/study/storage/variantstudy/business/command_reverter.py b/antarest/study/storage/variantstudy/business/command_reverter.py index 7b585f46b5..c7e88f83af 100644 --- a/antarest/study/storage/variantstudy/business/command_reverter.py +++ b/antarest/study/storage/variantstudy/business/command_reverter.py @@ -15,7 +15,7 @@ from pathlib import Path from antarest.core.exceptions import ChildNotFoundError -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.model.command.common import CommandName from antarest.study.storage.variantstudy.model.command.create_area import CreateArea @@ -222,7 +222,7 @@ def _revert_create_st_storage( history: t.List["ICommand"], base: FileStudy, ) -> t.List[ICommand]: - storage_id = base_command.parameters.id + storage_id = base_command.storage_id return [ RemoveSTStorage( area_id=base_command.area_id, diff --git a/antarest/study/storage/variantstudy/model/command/create_area.py b/antarest/study/storage/variantstudy/model/command/create_area.py index 8dfd782f43..1eefb00d1a 100644 --- a/antarest/study/storage/variantstudy/model/command/create_area.py +++ b/antarest/study/storage/variantstudy/model/command/create_area.py @@ -16,12 +16,8 @@ from antarest.core.model import JSON from antarest.study.model import STUDY_VERSION_6_5, STUDY_VERSION_8_1, STUDY_VERSION_8_3, STUDY_VERSION_8_6 -from antarest.study.storage.rawstudy.model.filesystem.config.model import ( - Area, - EnrModelling, - FileStudyTreeConfig, - transform_name_to_id, -) +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.model import Area, EnrModelling, FileStudyTreeConfig from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.model.command.common import CommandName, CommandOutput, FilteringOptions from antarest.study.storage.variantstudy.model.command.icommand import MATCH_SIGNATURE_SEPARATOR, ICommand diff --git a/antarest/study/storage/variantstudy/model/command/create_binding_constraint.py b/antarest/study/storage/variantstudy/model/command/create_binding_constraint.py index 7fe37187b6..93a65ff25a 100644 --- a/antarest/study/storage/variantstudy/model/command/create_binding_constraint.py +++ b/antarest/study/storage/variantstudy/model/command/create_binding_constraint.py @@ -18,6 +18,7 @@ from antares.study.version import StudyVersion from pydantic import Field, field_validator, model_validator +from antarest.core.model import LowerCaseStr from antarest.core.serialization import AntaresBaseModel from antarest.matrixstore.model import MatrixData from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model @@ -29,8 +30,11 @@ BindingConstraintFrequency, BindingConstraintOperator, ) -from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import validate_filtering -from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig, transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import ( + transform_name_to_id, + validate_filtering, +) +from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.matrix_constants_generator import GeneratorMatrixConstants from antarest.study.storage.variantstudy.business.utils import validate_matrix @@ -117,7 +121,7 @@ def _validate_filtering(cls, v: t.Any) -> str: class BindingConstraintProperties870(BindingConstraintProperties830): - group: str = DEFAULT_GROUP + group: LowerCaseStr = DEFAULT_GROUP BindingConstraintProperties = t.Union[ @@ -339,8 +343,8 @@ def apply_binding_constraint( elif "." in link_or_cluster: # Cluster IDs are stored in lower case in the binding constraints file. area, cluster_id = link_or_cluster.split(".") - thermal_ids = {thermal.id.lower() for thermal in study_data.config.areas[area].thermals} - if area not in study_data.config.areas or cluster_id.lower() not in thermal_ids: + thermal_ids = {thermal.id for thermal in study_data.config.areas[area].thermals} + if area not in study_data.config.areas or cluster_id not in thermal_ids: return CommandOutput( status=False, message=f"Cluster '{link_or_cluster}' does not exist in binding constraint '{bd_id}'", diff --git a/antarest/study/storage/variantstudy/model/command/create_cluster.py b/antarest/study/storage/variantstudy/model/command/create_cluster.py index ab76488049..779d85ee3c 100644 --- a/antarest/study/storage/variantstudy/model/command/create_cluster.py +++ b/antarest/study/storage/variantstudy/model/command/create_cluster.py @@ -12,18 +12,19 @@ import typing as t -from pydantic import Field, ValidationInfo, field_validator +from pydantic import Field, model_validator -from antarest.core.model import JSON +from antarest.core.model import JSON, LowerCaseStr from antarest.core.utils.utils import assert_this from antarest.matrixstore.model import MatrixData from antarest.study.model import STUDY_VERSION_8_7 -from antarest.study.storage.rawstudy.model.filesystem.config.model import ( - Area, - FileStudyTreeConfig, - transform_name_to_id, +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.model import Area, FileStudyTreeConfig +from antarest.study.storage.rawstudy.model.filesystem.config.thermal import ( + ThermalPropertiesType, + create_thermal_config, + create_thermal_properties, ) -from antarest.study.storage.rawstudy.model.filesystem.config.thermal import create_thermal_config from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.utils import strip_matrix_protocol, validate_matrix from antarest.study.storage.variantstudy.model.command.common import CommandName, CommandOutput @@ -47,44 +48,30 @@ class CreateCluster(ICommand): # ================== area_id: str - cluster_name: str - parameters: t.Dict[str, t.Any] + cluster_name: LowerCaseStr + parameters: ThermalPropertiesType prepro: t.Optional[t.Union[t.List[t.List[MatrixData]], str]] = Field(None, validate_default=True) modulation: t.Optional[t.Union[t.List[t.List[MatrixData]], str]] = Field(None, validate_default=True) - @field_validator("cluster_name", mode="before") - def validate_cluster_name(cls, val: str) -> str: - valid_name = transform_name_to_id(val, lower=False) - if valid_name != val: - raise ValueError("Cluster name must only contains [a-zA-Z0-9],&,-,_,(,) characters") - return val - - @field_validator("prepro", mode="before") - def validate_prepro( - cls, - v: t.Optional[t.Union[t.List[t.List[MatrixData]], str]], - values: t.Union[t.Dict[str, t.Any], ValidationInfo], - ) -> t.Optional[t.Union[t.List[t.List[MatrixData]], str]]: - new_values = values if isinstance(values, dict) else values.data - if v is None: - v = new_values["command_context"].generator_matrix_constants.get_thermal_prepro_data() - return v + @model_validator(mode="before") + def validate_model(cls, values: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: + # Validate parameters + args = {"name": values["cluster_name"], **values["parameters"]} + values["parameters"] = create_thermal_properties(values["study_version"], **args) + + # Validate prepro + if "prepro" in values: + values["prepro"] = validate_matrix(values["prepro"], values) else: - return validate_matrix(v, new_values) - - @field_validator("modulation", mode="before") - def validate_modulation( - cls, - v: t.Optional[t.Union[t.List[t.List[MatrixData]], str]], - values: t.Union[t.Dict[str, t.Any], ValidationInfo], - ) -> t.Optional[t.Union[t.List[t.List[MatrixData]], str]]: - new_values = values if isinstance(values, dict) else values.data - if v is None: - v = new_values["command_context"].generator_matrix_constants.get_thermal_prepro_modulation() - return v + values["prepro"] = values["command_context"].generator_matrix_constants.get_thermal_prepro_data() + # Validate modulation + if "modulation" in values: + values["modulation"] = validate_matrix(values["modulation"], values) else: - return validate_matrix(v, new_values) + values["modulation"] = values["command_context"].generator_matrix_constants.get_thermal_prepro_modulation() + + return values def _apply_config(self, study_data: FileStudyTreeConfig) -> t.Tuple[CommandOutput, t.Dict[str, t.Any]]: # Search the Area in the configuration @@ -125,12 +112,11 @@ def _apply(self, study_data: FileStudy, listener: t.Optional[ICommandListener] = if not output.status: return output - # default values - self.parameters.setdefault("name", self.cluster_name) + version = study_data.config.version cluster_id = data["cluster_id"] config = study_data.tree.get(["input", "thermal", "clusters", self.area_id, "list"]) - config[cluster_id] = self.parameters + config[cluster_id] = self.parameters.model_dump(mode="json", by_alias=True) # Series identifiers are in lower case. series_id = cluster_id.lower() @@ -151,7 +137,7 @@ def _apply(self, study_data: FileStudy, listener: t.Optional[ICommandListener] = } } } - if study_data.config.version >= STUDY_VERSION_8_7: + if version >= STUDY_VERSION_8_7: new_cluster_data["input"]["thermal"]["series"][self.area_id][series_id]["CO2Cost"] = null_matrix new_cluster_data["input"]["thermal"]["series"][self.area_id][series_id]["fuelCost"] = null_matrix study_data.tree.save(new_cluster_data) @@ -164,7 +150,7 @@ def to_dto(self) -> CommandDTO: args={ "area_id": self.area_id, "cluster_name": self.cluster_name, - "parameters": self.parameters, + "parameters": self.parameters.model_dump(mode="json", by_alias=True), "prepro": strip_matrix_protocol(self.prepro), "modulation": strip_matrix_protocol(self.modulation), }, @@ -186,9 +172,11 @@ def match(self, other: ICommand, equal: bool = False) -> bool: simple_match = self.area_id == other.area_id and self.cluster_name == other.cluster_name if not equal: return simple_match + self_params = self.parameters.model_dump(mode="json", by_alias=True) + other_params = other.parameters.model_dump(mode="json", by_alias=True) return ( simple_match - and self.parameters == other.parameters + and self_params == other_params and self.prepro == other.prepro and self.modulation == other.modulation ) @@ -199,7 +187,7 @@ def _create_diff(self, other: "ICommand") -> t.List["ICommand"]: from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig # Series identifiers are in lower case. - series_id = transform_name_to_id(self.cluster_name, lower=True) + series_id = transform_name_to_id(self.cluster_name) commands: t.List[ICommand] = [] if self.prepro != other.prepro: commands.append( @@ -219,11 +207,13 @@ def _create_diff(self, other: "ICommand") -> t.List["ICommand"]: study_version=self.study_version, ) ) - if self.parameters != other.parameters: + self_params = self.parameters.model_dump(mode="json", by_alias=True) + other_params = other.parameters.model_dump(mode="json", by_alias=True) + if self_params != other_params: commands.append( UpdateConfig( target=f"input/thermal/clusters/{self.area_id}/list/{self.cluster_name}", - data=other.parameters, + data=other_params, command_context=self.command_context, study_version=self.study_version, ) diff --git a/antarest/study/storage/variantstudy/model/command/create_district.py b/antarest/study/storage/variantstudy/model/command/create_district.py index 5ed1fc8469..e8ed7f69d9 100644 --- a/antarest/study/storage/variantstudy/model/command/create_district.py +++ b/antarest/study/storage/variantstudy/model/command/create_district.py @@ -15,11 +15,9 @@ from pydantic import field_validator -from antarest.study.storage.rawstudy.model.filesystem.config.model import ( - DistrictSet, - FileStudyTreeConfig, - transform_name_to_id, -) +from antarest.core.model import LowerCaseStr +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.model import DistrictSet, FileStudyTreeConfig from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.model.command.common import CommandName, CommandOutput from antarest.study.storage.variantstudy.model.command.icommand import MATCH_SIGNATURE_SEPARATOR, ICommand @@ -52,13 +50,6 @@ class CreateDistrict(ICommand): output: bool = True comments: str = "" - @field_validator("name") - def validate_district_name(cls, val: str) -> str: - valid_name = transform_name_to_id(val, lower=False) - if valid_name != val: - raise ValueError("Area name must only contains [a-zA-Z0-9],&,-,_,(,) characters") - return val - def _apply_config(self, study_data: FileStudyTreeConfig) -> Tuple[CommandOutput, Dict[str, Any]]: district_id = transform_name_to_id(self.name) if district_id in study_data.sets: diff --git a/antarest/study/storage/variantstudy/model/command/create_renewables_cluster.py b/antarest/study/storage/variantstudy/model/command/create_renewables_cluster.py index 9a1413665b..6c0c6381c8 100644 --- a/antarest/study/storage/variantstudy/model/command/create_renewables_cluster.py +++ b/antarest/study/storage/variantstudy/model/command/create_renewables_cluster.py @@ -12,16 +12,15 @@ import typing as t -from pydantic import field_validator - -from antarest.core.model import JSON -from antarest.study.storage.rawstudy.model.filesystem.config.model import ( - Area, - EnrModelling, - FileStudyTreeConfig, - transform_name_to_id, +from pydantic import model_validator + +from antarest.core.model import JSON, LowerCaseStr +from antarest.study.storage.rawstudy.model.filesystem.config.model import Area, EnrModelling, FileStudyTreeConfig +from antarest.study.storage.rawstudy.model.filesystem.config.renewable import ( + RenewableProperties, + create_renewable_config, + create_renewable_properties, ) -from antarest.study.storage.rawstudy.model.filesystem.config.renewable import create_renewable_config from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.model.command.common import CommandName, CommandOutput from antarest.study.storage.variantstudy.model.command.icommand import MATCH_SIGNATURE_SEPARATOR, ICommand @@ -44,15 +43,15 @@ class CreateRenewablesCluster(ICommand): # ================== area_id: str - cluster_name: str - parameters: t.Dict[str, t.Any] + cluster_name: LowerCaseStr + parameters: RenewableProperties - @field_validator("cluster_name") - def validate_cluster_name(cls, val: str) -> str: - valid_name = transform_name_to_id(val, lower=False) - if valid_name != val: - raise ValueError("Area name must only contains [a-zA-Z0-9],&,-,_,(,) characters") - return val + @model_validator(mode="before") + def validate_model(cls, values: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: + # Validate parameters + args = {"name": values["cluster_name"], **values["parameters"]} + values["parameters"] = create_renewable_properties(values["study_version"], **args) + return values def _apply_config(self, study_data: FileStudyTreeConfig) -> t.Tuple[CommandOutput, t.Dict[str, t.Any]]: if EnrModelling(study_data.enr_modelling) != EnrModelling.CLUSTERS: @@ -106,14 +105,9 @@ def _apply(self, study_data: FileStudy, listener: t.Optional[ICommandListener] = if not output.status: return output - # default values - if "ts-interpretation" not in self.parameters: - self.parameters["ts-interpretation"] = "power-generation" - self.parameters.setdefault("name", self.cluster_name) - cluster_id = data["cluster_id"] config = study_data.tree.get(["input", "renewables", "clusters", self.area_id, "list"]) - config[cluster_id] = self.parameters + config[cluster_id] = self.parameters.model_dump(mode="json", by_alias=True) # Series identifiers are in lower case. series_id = cluster_id.lower() @@ -139,7 +133,7 @@ def to_dto(self) -> CommandDTO: args={ "area_id": self.area_id, "cluster_name": self.cluster_name, - "parameters": self.parameters, + "parameters": self.parameters.model_dump(mode="json", by_alias=True), }, study_version=self.study_version, ) @@ -159,18 +153,22 @@ def match(self, other: ICommand, equal: bool = False) -> bool: simple_match = self.area_id == other.area_id and self.cluster_name == other.cluster_name if not equal: return simple_match - return simple_match and self.parameters == other.parameters + self_params = self.parameters.model_dump(mode="json", by_alias=True) + other_params = other.parameters.model_dump(mode="json", by_alias=True) + return simple_match and self_params == other_params def _create_diff(self, other: "ICommand") -> t.List["ICommand"]: other = t.cast(CreateRenewablesCluster, other) from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig commands: t.List[ICommand] = [] - if self.parameters != other.parameters: + self_params = self.parameters.model_dump(mode="json", by_alias=True) + other_params = other.parameters.model_dump(mode="json", by_alias=True) + if self_params != other_params: commands.append( UpdateConfig( target=f"input/renewables/clusters/{self.area_id}/list/{self.cluster_name}", - data=other.parameters, + data=other_params, command_context=self.command_context, study_version=self.study_version, ) diff --git a/antarest/study/storage/variantstudy/model/command/create_st_storage.py b/antarest/study/storage/variantstudy/model/command/create_st_storage.py index ae39cf8c28..8d15d5c625 100644 --- a/antarest/study/storage/variantstudy/model/command/create_st_storage.py +++ b/antarest/study/storage/variantstudy/model/command/create_st_storage.py @@ -15,11 +15,16 @@ import numpy as np from pydantic import Field, ValidationInfo, model_validator -from antarest.core.model import JSON +from antarest.core.model import JSON, LowerCaseStr from antarest.matrixstore.model import MatrixData from antarest.study.model import STUDY_VERSION_8_6 +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.config.model import Area, FileStudyTreeConfig -from antarest.study.storage.rawstudy.model.filesystem.config.st_storage import STStorageConfigType +from antarest.study.storage.rawstudy.model.filesystem.config.st_storage import ( + STStoragePropertiesType, + create_st_storage_config, + create_st_storage_properties, +) from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.matrix_constants_generator import GeneratorMatrixConstants from antarest.study.storage.variantstudy.business.utils import strip_matrix_protocol, validate_matrix @@ -58,8 +63,8 @@ class CreateSTStorage(ICommand): # Command parameters # ================== - area_id: str = Field(description="Area ID", pattern=r"[a-z0-9_(),& -]+") - parameters: STStorageConfigType + area_id: LowerCaseStr = Field(description="Area ID", pattern=r"[a-z0-9_(),& -]+") + parameters: STStoragePropertiesType pmax_injection: t.Optional[t.Union[MatrixType, str]] = Field( default=None, description="Charge capacity (modulation)", @@ -84,13 +89,18 @@ class CreateSTStorage(ICommand): @property def storage_id(self) -> str: """The normalized version of the storage's name used as the ID.""" - return self.parameters.id + return transform_name_to_id(self.storage_name) @property def storage_name(self) -> str: """The label representing the name of the storage for the user.""" return self.parameters.name + @model_validator(mode="before") + def validate_model(cls, values: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: + values["parameters"] = create_st_storage_properties(values["study_version"], **values["parameters"]) + return values + @staticmethod def validate_field( v: t.Optional[t.Union[MatrixType, str]], values: t.Dict[str, t.Any], field: str @@ -173,6 +183,7 @@ def _apply_config(self, study_data: FileStudyTreeConfig) -> t.Tuple[CommandOutpu """ # Check if the study version is above the minimum required version. + storage_id = self.storage_id version = study_data.version if version < REQUIRED_VERSION: return ( @@ -195,7 +206,7 @@ def _apply_config(self, study_data: FileStudyTreeConfig) -> t.Tuple[CommandOutpu area: Area = study_data.areas[self.area_id] # Check if the short-term storage already exists in the area - if any(s.id == self.storage_id for s in area.st_storages): + if any(s.id == storage_id for s in area.st_storages): return ( CommandOutput( status=False, @@ -205,14 +216,17 @@ def _apply_config(self, study_data: FileStudyTreeConfig) -> t.Tuple[CommandOutpu ) # Create a new short-term storage and add it to the area - area.st_storages.append(self.parameters) + storage_config = create_st_storage_config( + self.study_version, **self.parameters.model_dump(mode="json", by_alias=True) + ) + area.st_storages.append(storage_config) return ( CommandOutput( status=True, message=f"Short-term st_storage '{self.storage_name}' successfully added to area '{self.area_id}'.", ), - {"storage_id": self.storage_id}, + {"storage_id": storage_id}, ) def _apply(self, study_data: FileStudy, listener: t.Optional[ICommandListener] = None) -> CommandOutput: @@ -227,6 +241,7 @@ def _apply(self, study_data: FileStudy, listener: t.Optional[ICommandListener] = Returns: The output of the command execution. """ + storage_id = self.storage_id output, _ = self._apply_config(study_data.config) if not output.status: return output @@ -234,13 +249,13 @@ def _apply(self, study_data: FileStudy, listener: t.Optional[ICommandListener] = # Fill-in the "list.ini" file with the parameters. # On creation, it's better to write all the parameters in the file. config = study_data.tree.get(["input", "st-storage", "clusters", self.area_id, "list"]) - config[self.storage_id] = self.parameters.model_dump(mode="json", by_alias=True, exclude={"id"}) + config[storage_id] = self.parameters.model_dump(mode="json", by_alias=True) new_data: JSON = { "input": { "st-storage": { "clusters": {self.area_id: {"list": config}}, - "series": {self.area_id: {self.storage_id: {attr: getattr(self, attr) for attr in _MATRIX_NAMES}}}, + "series": {self.area_id: {storage_id: {attr: getattr(self, attr) for attr in _MATRIX_NAMES}}}, } } } @@ -256,12 +271,11 @@ def to_dto(self) -> CommandDTO: Returns: The DTO object representing the current command. """ - parameters = self.parameters.model_dump(mode="json", by_alias=True, exclude={"id"}) return CommandDTO( action=self.command_name.value, args={ "area_id": self.area_id, - "parameters": parameters, + "parameters": self.parameters.model_dump(mode="json", by_alias=True), **{attr: strip_matrix_protocol(getattr(self, attr)) for attr in _MATRIX_NAMES}, }, study_version=self.study_version, @@ -312,9 +326,10 @@ def _create_diff(self, other: "ICommand") -> t.List["ICommand"]: from antarest.study.storage.variantstudy.model.command.update_config import UpdateConfig other = t.cast(CreateSTStorage, other) + storage_id = self.storage_id commands: t.List[ICommand] = [ ReplaceMatrix( - target=f"input/st-storage/series/{self.area_id}/{self.storage_id}/{attr}", + target=f"input/st-storage/series/{self.area_id}/{storage_id}/{attr}", matrix=strip_matrix_protocol(getattr(other, attr)), command_context=self.command_context, study_version=self.study_version, @@ -322,12 +337,13 @@ def _create_diff(self, other: "ICommand") -> t.List["ICommand"]: for attr in _MATRIX_NAMES if getattr(self, attr) != getattr(other, attr) ] - if self.parameters != other.parameters: - data: t.Dict[str, t.Any] = other.parameters.model_dump(mode="json", by_alias=True, exclude={"id"}) + self_params = self.parameters.model_dump(mode="json", by_alias=True) + other_params = other.parameters.model_dump(mode="json", by_alias=True) + if self_params != other_params: commands.append( UpdateConfig( - target=f"input/st-storage/clusters/{self.area_id}/list/{self.storage_id}", - data=data, + target=f"input/st-storage/clusters/{self.area_id}/list/{storage_id}", + data=other_params, command_context=self.command_context, study_version=self.study_version, ) diff --git a/antarest/study/storage/variantstudy/model/command/remove_cluster.py b/antarest/study/storage/variantstudy/model/command/remove_cluster.py index eba5aec285..b64bbdfc39 100644 --- a/antarest/study/storage/variantstudy/model/command/remove_cluster.py +++ b/antarest/study/storage/variantstudy/model/command/remove_cluster.py @@ -12,6 +12,9 @@ import typing as t +from pydantic import Field + +from antarest.core.model import LowerCaseStr from antarest.study.storage.rawstudy.model.filesystem.config.model import Area, FileStudyTreeConfig from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.utils_binding_constraint import ( @@ -38,7 +41,7 @@ class RemoveCluster(ICommand): # ================== area_id: str - cluster_id: str + cluster_id: LowerCaseStr = Field(description="Cluster ID", pattern=r"[a-z0-9_(),& -]+") def _apply_config(self, study_data: FileStudyTreeConfig) -> t.Tuple[CommandOutput, t.Dict[str, t.Any]]: """ diff --git a/antarest/study/storage/variantstudy/model/command/remove_link.py b/antarest/study/storage/variantstudy/model/command/remove_link.py index e1cee3a518..9b372e906f 100644 --- a/antarest/study/storage/variantstudy/model/command/remove_link.py +++ b/antarest/study/storage/variantstudy/model/command/remove_link.py @@ -15,7 +15,8 @@ from pydantic import field_validator, model_validator from antarest.study.model import STUDY_VERSION_8_2 -from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig, transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.model import FileStudyTreeConfig from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.model.command.common import CommandName, CommandOutput from antarest.study.storage.variantstudy.model.command.icommand import MATCH_SIGNATURE_SEPARATOR, ICommand, OutputTuple @@ -46,7 +47,7 @@ class RemoveLink(ICommand): def _validate_id(cls, area: str) -> str: if isinstance(area, str): # Area IDs must be in lowercase and not empty. - area_id = transform_name_to_id(area, lower=True) + area_id = transform_name_to_id(area) if area_id: return area_id # Valid characters are `[a-zA-Z0-9_(),& -]` (including space). diff --git a/antarest/study/storage/variantstudy/model/command/remove_renewables_cluster.py b/antarest/study/storage/variantstudy/model/command/remove_renewables_cluster.py index bb378fc1d1..592b75bfbb 100644 --- a/antarest/study/storage/variantstudy/model/command/remove_renewables_cluster.py +++ b/antarest/study/storage/variantstudy/model/command/remove_renewables_cluster.py @@ -12,6 +12,10 @@ import typing as t +from pydantic import Field, field_validator + +from antarest.core.model import LowerCaseStr +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import validate_id_against_name from antarest.study.storage.rawstudy.model.filesystem.config.model import Area, FileStudyTreeConfig from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.model.command.common import CommandName, CommandOutput @@ -35,7 +39,7 @@ class RemoveRenewablesCluster(ICommand): # ================== area_id: str - cluster_id: str + cluster_id: LowerCaseStr = Field(description="Cluster ID", pattern=r"[a-z0-9_(),& -]+") def _apply_config(self, study_data: FileStudyTreeConfig) -> t.Tuple[CommandOutput, t.Dict[str, t.Any]]: """ diff --git a/antarest/study/storage/variantstudy/model/command/remove_st_storage.py b/antarest/study/storage/variantstudy/model/command/remove_st_storage.py index b369612ace..f27ebc5a58 100644 --- a/antarest/study/storage/variantstudy/model/command/remove_st_storage.py +++ b/antarest/study/storage/variantstudy/model/command/remove_st_storage.py @@ -14,6 +14,7 @@ from pydantic import Field +from antarest.core.model import LowerCaseStr from antarest.study.model import STUDY_VERSION_8_6 from antarest.study.storage.rawstudy.model.filesystem.config.model import Area, FileStudyTreeConfig from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy @@ -41,7 +42,7 @@ class RemoveSTStorage(ICommand): # ================== area_id: str = Field(description="Area ID", pattern=r"[a-z0-9_(),& -]+") - storage_id: str = Field(description="Short term storage ID", pattern=r"[a-z0-9_(),& -]+") + storage_id: LowerCaseStr = Field(description="Short term storage ID", pattern=r"[a-z0-9_(),& -]+") def _apply_config(self, study_data: FileStudyTreeConfig) -> t.Tuple[CommandOutput, t.Dict[str, t.Any]]: """ diff --git a/antarest/study/web/study_data_blueprint.py b/antarest/study/web/study_data_blueprint.py index 9f267df774..ba9d2034a4 100644 --- a/antarest/study/web/study_data_blueprint.py +++ b/antarest/study/web/study_data_blueprint.py @@ -21,7 +21,7 @@ from antarest.core.config import Config from antarest.core.jwt import JWTUser -from antarest.core.model import JSON, StudyPermissionType +from antarest.core.model import JSON, LowerCaseStr, StudyPermissionType from antarest.core.requests import RequestParameters from antarest.core.utils.utils import sanitize_uuid from antarest.core.utils.web import APITag @@ -81,7 +81,7 @@ BindingConstraintFrequency, BindingConstraintOperator, ) -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.config.ruleset_matrices import TableForm as SBTableForm logger = logging.getLogger(__name__) @@ -1955,7 +1955,7 @@ def create_renewable_cluster( def update_renewable_cluster( uuid: str, area_id: str, - cluster_id: str, + cluster_id: LowerCaseStr, cluster_data: RenewableClusterInput, current_user: JWTUser = Depends(auth.get_current_user), ) -> RenewableClusterOutput: @@ -2129,7 +2129,7 @@ def create_thermal_cluster( def update_thermal_cluster( uuid: str, area_id: str, - cluster_id: str, + cluster_id: LowerCaseStr, cluster_data: ThermalClusterInput, current_user: JWTUser = Depends(auth.get_current_user), ) -> ThermalClusterOutput: @@ -2556,8 +2556,8 @@ def duplicate_cluster( uuid: str, area_id: str, cluster_type: ClusterType, - source_cluster_id: str, - new_cluster_name: str = Query(..., alias="newName", title="New Cluster Name"), + source_cluster_id: LowerCaseStr, + new_cluster_name: LowerCaseStr = Query(..., alias="newName", title="New Cluster Name"), current_user: JWTUser = Depends(auth.get_current_user), ) -> t.Union[STStorageOutput, ThermalClusterOutput, RenewableClusterOutput]: logger.info( diff --git a/tests/integration/assets/base_study.zip b/tests/integration/assets/base_study.zip index 3b282beb16..9566532991 100644 Binary files a/tests/integration/assets/base_study.zip and b/tests/integration/assets/base_study.zip differ diff --git a/tests/integration/assets/variant_study.zip b/tests/integration/assets/variant_study.zip index 4526fc900f..9cfb20f19e 100644 Binary files a/tests/integration/assets/variant_study.zip and b/tests/integration/assets/variant_study.zip differ diff --git a/tests/integration/studies_blueprint/assets/test_synthesis/raw_study.synthesis.json b/tests/integration/studies_blueprint/assets/test_synthesis/raw_study.synthesis.json index c0d5635c70..a164d8a163 100644 --- a/tests/integration/studies_blueprint/assets/test_synthesis/raw_study.synthesis.json +++ b/tests/integration/studies_blueprint/assets/test_synthesis/raw_study.synthesis.json @@ -18,7 +18,7 @@ "thermals": [ { "id": "01_solar", - "group": "Other 1", + "group": "other 1", "name": "01_solar", "enabled": true, "unitcount": 1, @@ -42,7 +42,7 @@ }, { "id": "02_wind_on", - "group": "Other 1", + "group": "other 1", "name": "02_wind_on", "enabled": true, "unitcount": 1, @@ -66,7 +66,7 @@ }, { "id": "03_wind_off", - "group": "Other 1", + "group": "other 1", "name": "03_wind_off", "enabled": true, "unitcount": 1, @@ -90,7 +90,7 @@ }, { "id": "04_res", - "group": "Other 1", + "group": "other 1", "name": "04_res", "enabled": true, "unitcount": 1, @@ -114,7 +114,7 @@ }, { "id": "05_nuclear", - "group": "Other 1", + "group": "other 1", "name": "05_nuclear", "enabled": true, "unitcount": 1, @@ -138,7 +138,7 @@ }, { "id": "06_coal", - "group": "Other 1", + "group": "other 1", "name": "06_coal", "enabled": true, "unitcount": 1, @@ -162,7 +162,7 @@ }, { "id": "07_gas", - "group": "Other 1", + "group": "other 1", "name": "07_gas", "enabled": true, "unitcount": 1, @@ -186,7 +186,7 @@ }, { "id": "08_non-res", - "group": "Other 1", + "group": "other 1", "name": "08_non-res", "enabled": true, "unitcount": 1, @@ -210,7 +210,7 @@ }, { "id": "09_hydro_pump", - "group": "Other 1", + "group": "other 1", "name": "09_hydro_pump", "enabled": true, "unitcount": 1, @@ -258,7 +258,7 @@ "thermals": [ { "id": "01_solar", - "group": "Other 1", + "group": "other 1", "name": "01_solar", "enabled": true, "unitcount": 1, @@ -282,7 +282,7 @@ }, { "id": "02_wind_on", - "group": "Other 1", + "group": "other 1", "name": "02_wind_on", "enabled": true, "unitcount": 1, @@ -306,7 +306,7 @@ }, { "id": "03_wind_off", - "group": "Other 1", + "group": "other 1", "name": "03_wind_off", "enabled": true, "unitcount": 1, @@ -330,7 +330,7 @@ }, { "id": "04_res", - "group": "Other 1", + "group": "other 1", "name": "04_res", "enabled": true, "unitcount": 1, @@ -354,7 +354,7 @@ }, { "id": "05_nuclear", - "group": "Other 1", + "group": "other 1", "name": "05_nuclear", "enabled": true, "unitcount": 1, @@ -378,7 +378,7 @@ }, { "id": "06_coal", - "group": "Other 1", + "group": "other 1", "name": "06_coal", "enabled": true, "unitcount": 1, @@ -402,7 +402,7 @@ }, { "id": "07_gas", - "group": "Other 1", + "group": "other 1", "name": "07_gas", "enabled": true, "unitcount": 1, @@ -426,7 +426,7 @@ }, { "id": "08_non-res", - "group": "Other 1", + "group": "other 1", "name": "08_non-res", "enabled": true, "unitcount": 1, @@ -450,7 +450,7 @@ }, { "id": "09_hydro_pump", - "group": "Other 1", + "group": "other 1", "name": "09_hydro_pump", "enabled": true, "unitcount": 1, @@ -498,7 +498,7 @@ "thermals": [ { "id": "01_solar", - "group": "Other 1", + "group": "other 1", "name": "01_solar", "enabled": true, "unitcount": 1, @@ -522,7 +522,7 @@ }, { "id": "02_wind_on", - "group": "Other 1", + "group": "other 1", "name": "02_wind_on", "enabled": true, "unitcount": 1, @@ -546,7 +546,7 @@ }, { "id": "03_wind_off", - "group": "Other 1", + "group": "other 1", "name": "03_wind_off", "enabled": true, "unitcount": 1, @@ -570,7 +570,7 @@ }, { "id": "04_res", - "group": "Other 1", + "group": "other 1", "name": "04_res", "enabled": true, "unitcount": 1, @@ -594,7 +594,7 @@ }, { "id": "05_nuclear", - "group": "Other 1", + "group": "other 1", "name": "05_nuclear", "enabled": true, "unitcount": 1, @@ -618,7 +618,7 @@ }, { "id": "06_coal", - "group": "Other 1", + "group": "other 1", "name": "06_coal", "enabled": true, "unitcount": 1, @@ -642,7 +642,7 @@ }, { "id": "07_gas", - "group": "Other 1", + "group": "other 1", "name": "07_gas", "enabled": true, "unitcount": 1, @@ -666,7 +666,7 @@ }, { "id": "08_non-res", - "group": "Other 1", + "group": "other 1", "name": "08_non-res", "enabled": true, "unitcount": 1, @@ -690,7 +690,7 @@ }, { "id": "09_hydro_pump", - "group": "Other 1", + "group": "other 1", "name": "09_hydro_pump", "enabled": true, "unitcount": 1, @@ -726,7 +726,7 @@ "thermals": [ { "id": "01_solar", - "group": "Other 1", + "group": "other 1", "name": "01_solar", "enabled": true, "unitcount": 1, @@ -750,7 +750,7 @@ }, { "id": "02_wind_on", - "group": "Other 1", + "group": "other 1", "name": "02_wind_on", "enabled": true, "unitcount": 1, @@ -774,7 +774,7 @@ }, { "id": "03_wind_off", - "group": "Other 1", + "group": "other 1", "name": "03_wind_off", "enabled": true, "unitcount": 1, @@ -798,7 +798,7 @@ }, { "id": "04_res", - "group": "Other 1", + "group": "other 1", "name": "04_res", "enabled": true, "unitcount": 1, @@ -822,7 +822,7 @@ }, { "id": "05_nuclear", - "group": "Other 1", + "group": "other 1", "name": "05_nuclear", "enabled": true, "unitcount": 1, @@ -846,7 +846,7 @@ }, { "id": "06_coal", - "group": "Other 1", + "group": "other 1", "name": "06_coal", "enabled": true, "unitcount": 1, @@ -870,7 +870,7 @@ }, { "id": "07_gas", - "group": "Other 1", + "group": "other 1", "name": "07_gas", "enabled": true, "unitcount": 1, @@ -894,7 +894,7 @@ }, { "id": "08_non-res", - "group": "Other 1", + "group": "other 1", "name": "08_non-res", "enabled": true, "unitcount": 1, @@ -918,7 +918,7 @@ }, { "id": "09_hydro_pump", - "group": "Other 1", + "group": "other 1", "name": "09_hydro_pump", "enabled": true, "unitcount": 1, diff --git a/tests/integration/studies_blueprint/assets/test_synthesis/variant_study.synthesis.json b/tests/integration/studies_blueprint/assets/test_synthesis/variant_study.synthesis.json index 2d3c3baf2a..f16fba25eb 100644 --- a/tests/integration/studies_blueprint/assets/test_synthesis/variant_study.synthesis.json +++ b/tests/integration/studies_blueprint/assets/test_synthesis/variant_study.synthesis.json @@ -18,7 +18,7 @@ "thermals": [ { "id": "01_solar", - "group": "Other 1", + "group": "other 1", "name": "01_solar", "enabled": true, "unitcount": 1, @@ -42,7 +42,7 @@ }, { "id": "02_wind_on", - "group": "Other 1", + "group": "other 1", "name": "02_wind_on", "enabled": true, "unitcount": 1, @@ -66,7 +66,7 @@ }, { "id": "03_wind_off", - "group": "Other 1", + "group": "other 1", "name": "03_wind_off", "enabled": true, "unitcount": 1, @@ -90,7 +90,7 @@ }, { "id": "04_res", - "group": "Other 1", + "group": "other 1", "name": "04_res", "enabled": true, "unitcount": 1, @@ -114,7 +114,7 @@ }, { "id": "05_nuclear", - "group": "Other 1", + "group": "other 1", "name": "05_nuclear", "enabled": true, "unitcount": 1, @@ -138,7 +138,7 @@ }, { "id": "06_coal", - "group": "Other 1", + "group": "other 1", "name": "06_coal", "enabled": true, "unitcount": 1, @@ -162,7 +162,7 @@ }, { "id": "07_gas", - "group": "Other 1", + "group": "other 1", "name": "07_gas", "enabled": true, "unitcount": 1, @@ -186,7 +186,7 @@ }, { "id": "08_non-res", - "group": "Other 1", + "group": "other 1", "name": "08_non-res", "enabled": true, "unitcount": 1, @@ -210,7 +210,7 @@ }, { "id": "09_hydro_pump", - "group": "Other 1", + "group": "other 1", "name": "09_hydro_pump", "enabled": true, "unitcount": 1, @@ -258,7 +258,7 @@ "thermals": [ { "id": "01_solar", - "group": "Other 1", + "group": "other 1", "name": "01_solar", "enabled": true, "unitcount": 1, @@ -282,7 +282,7 @@ }, { "id": "02_wind_on", - "group": "Other 1", + "group": "other 1", "name": "02_wind_on", "enabled": true, "unitcount": 1, @@ -306,7 +306,7 @@ }, { "id": "03_wind_off", - "group": "Other 1", + "group": "other 1", "name": "03_wind_off", "enabled": true, "unitcount": 1, @@ -330,7 +330,7 @@ }, { "id": "04_res", - "group": "Other 1", + "group": "other 1", "name": "04_res", "enabled": true, "unitcount": 1, @@ -354,7 +354,7 @@ }, { "id": "05_nuclear", - "group": "Other 1", + "group": "other 1", "name": "05_nuclear", "enabled": true, "unitcount": 1, @@ -378,7 +378,7 @@ }, { "id": "06_coal", - "group": "Other 1", + "group": "other 1", "name": "06_coal", "enabled": true, "unitcount": 1, @@ -402,7 +402,7 @@ }, { "id": "07_gas", - "group": "Other 1", + "group": "other 1", "name": "07_gas", "enabled": true, "unitcount": 1, @@ -426,7 +426,7 @@ }, { "id": "08_non-res", - "group": "Other 1", + "group": "other 1", "name": "08_non-res", "enabled": true, "unitcount": 1, @@ -450,7 +450,7 @@ }, { "id": "09_hydro_pump", - "group": "Other 1", + "group": "other 1", "name": "09_hydro_pump", "enabled": true, "unitcount": 1, @@ -498,7 +498,7 @@ "thermals": [ { "id": "01_solar", - "group": "Other 1", + "group": "other 1", "name": "01_solar", "enabled": true, "unitcount": 1, @@ -522,7 +522,7 @@ }, { "id": "02_wind_on", - "group": "Other 1", + "group": "other 1", "name": "02_wind_on", "enabled": true, "unitcount": 1, @@ -546,7 +546,7 @@ }, { "id": "03_wind_off", - "group": "Other 1", + "group": "other 1", "name": "03_wind_off", "enabled": true, "unitcount": 1, @@ -570,7 +570,7 @@ }, { "id": "04_res", - "group": "Other 1", + "group": "other 1", "name": "04_res", "enabled": true, "unitcount": 1, @@ -594,7 +594,7 @@ }, { "id": "05_nuclear", - "group": "Other 1", + "group": "other 1", "name": "05_nuclear", "enabled": true, "unitcount": 1, @@ -618,7 +618,7 @@ }, { "id": "06_coal", - "group": "Other 1", + "group": "other 1", "name": "06_coal", "enabled": true, "unitcount": 1, @@ -642,7 +642,7 @@ }, { "id": "07_gas", - "group": "Other 1", + "group": "other 1", "name": "07_gas", "enabled": true, "unitcount": 1, @@ -666,7 +666,7 @@ }, { "id": "08_non-res", - "group": "Other 1", + "group": "other 1", "name": "08_non-res", "enabled": true, "unitcount": 1, @@ -690,7 +690,7 @@ }, { "id": "09_hydro_pump", - "group": "Other 1", + "group": "other 1", "name": "09_hydro_pump", "enabled": true, "unitcount": 1, @@ -726,7 +726,7 @@ "thermals": [ { "id": "01_solar", - "group": "Other 1", + "group": "other 1", "name": "01_solar", "enabled": true, "unitcount": 1, @@ -750,7 +750,7 @@ }, { "id": "02_wind_on", - "group": "Other 1", + "group": "other 1", "name": "02_wind_on", "enabled": true, "unitcount": 1, @@ -774,7 +774,7 @@ }, { "id": "03_wind_off", - "group": "Other 1", + "group": "other 1", "name": "03_wind_off", "enabled": true, "unitcount": 1, @@ -798,7 +798,7 @@ }, { "id": "04_res", - "group": "Other 1", + "group": "other 1", "name": "04_res", "enabled": true, "unitcount": 1, @@ -822,7 +822,7 @@ }, { "id": "05_nuclear", - "group": "Other 1", + "group": "other 1", "name": "05_nuclear", "enabled": true, "unitcount": 1, @@ -846,7 +846,7 @@ }, { "id": "06_coal", - "group": "Other 1", + "group": "other 1", "name": "06_coal", "enabled": true, "unitcount": 1, @@ -870,7 +870,7 @@ }, { "id": "07_gas", - "group": "Other 1", + "group": "other 1", "name": "07_gas", "enabled": true, "unitcount": 1, @@ -894,7 +894,7 @@ }, { "id": "08_non-res", - "group": "Other 1", + "group": "other 1", "name": "08_non-res", "enabled": true, "unitcount": 1, @@ -918,7 +918,7 @@ }, { "id": "09_hydro_pump", - "group": "Other 1", + "group": "other 1", "name": "09_hydro_pump", "enabled": true, "unitcount": 1, diff --git a/tests/integration/study_data_blueprint/test_binding_constraints.py b/tests/integration/study_data_blueprint/test_binding_constraints.py index c5e8d17b5b..36fe4602e5 100644 --- a/tests/integration/study_data_blueprint/test_binding_constraints.py +++ b/tests/integration/study_data_blueprint/test_binding_constraints.py @@ -192,8 +192,8 @@ def test_lifecycle__nominal(self, client: TestClient, user_access_token: str, st clusters_list = preparer.get_thermals(study_id, area1_id) assert len(clusters_list) == 1 assert clusters_list[0]["id"] == cluster_id - assert clusters_list[0]["name"] == "Cluster 1" - assert clusters_list[0]["group"] == "Nuclear" + assert clusters_list[0]["name"] == "cluster 1" + assert clusters_list[0]["group"] == "nuclear" if study_type == "variant": study_id = preparer.create_variant(study_id, name="Variant 1") @@ -635,7 +635,7 @@ def test_for_version_870(self, client: TestClient, user_access_token: str, study # Creation of bc with a group bc_id_w_group = "binding_constraint_2" args["operator"], operator_2 = "greater", "gt" - properties = preparer.create_binding_constraint(study_id, name=bc_id_w_group, group="specific_grp", **args) + properties = preparer.create_binding_constraint(study_id, name=bc_id_w_group, group="Specific_GRP", **args) assert properties["group"] == "specific_grp" # Creation of bc with a matrix @@ -805,13 +805,13 @@ def test_for_version_870(self, client: TestClient, user_access_token: str, study # ============================= # Add a group - grp_name = "random_grp" + grp_name = "RandOM_grp" res = client.put( f"/v1/studies/{study_id}/bindingconstraints/{bc_id_w_matrix}", json={"group": grp_name}, ) assert res.status_code == 200, res.json() - assert res.json()["group"] == grp_name + assert res.json()["group"] == grp_name.lower() # check that updating of a binding constraint that has an operator "equal" # with a greater matrix will raise an error 422 @@ -1131,15 +1131,15 @@ def test_for_version_870(self, client: TestClient, user_access_token: str, study res = client.get(f"/v1/studies/{study_id}/constraint-groups") assert res.status_code in {200, 201}, res.json() groups = res.json() - assert set(groups) == {"default", "random_grp", "Group 1", "Group 2"} - assert groups["Group 2"] == [ + assert set(groups) == {"default", "random_grp", "group 1", "group 2"} + assert groups["group 2"] == [ { "comments": "New API", "terms": [], "enabled": True, "filterSynthesis": "", "filterYearByYear": "", - "group": "Group 2", + "group": "group 2", "id": "second bc", "name": "Second BC", "operator": "less", diff --git a/tests/integration/study_data_blueprint/test_renewable.py b/tests/integration/study_data_blueprint/test_renewable.py index 8e3fb8051b..cc962d09c7 100644 --- a/tests/integration/study_data_blueprint/test_renewable.py +++ b/tests/integration/study_data_blueprint/test_renewable.py @@ -45,7 +45,7 @@ from antarest.core.tasks.model import TaskStatus from antarest.core.utils.string import to_camel_case -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.config.renewable import RenewableProperties from tests.integration.utils import wait_task_completion @@ -129,9 +129,14 @@ def test_lifecycle( ) assert res.status_code == 200, res.json() fr_solar_pv_id = res.json()["id"] - assert fr_solar_pv_id == transform_name_to_id(fr_solar_pv, lower=False) + assert fr_solar_pv_id == transform_name_to_id(fr_solar_pv) # noinspection SpellCheckingInspection - fr_solar_pv_cfg = {"id": fr_solar_pv_id, **fr_solar_pv_props} + fr_solar_pv_cfg = { + "id": fr_solar_pv_id, + **fr_solar_pv_props, + "name": fr_solar_pv.lower(), + "group": fr_solar_pv_props["group"].lower(), + } assert res.json() == fr_solar_pv_cfg # reading the properties of a renewable cluster @@ -177,18 +182,19 @@ def test_lifecycle( assert res.json() == EXISTING_CLUSTERS + [fr_solar_pv_cfg] # updating properties + old_name = "fr solar pv old 1" res = client.patch( f"/v1/studies/{internal_study_id}/areas/{area_id}/clusters/renewable/{fr_solar_pv_id}", headers={"Authorization": f"Bearer {user_access_token}"}, json={ - "name": "FR Solar pv old 1", + "name": old_name, "nominalCapacity": 5132, }, ) assert res.status_code == 200, res.json() fr_solar_pv_cfg = { **fr_solar_pv_cfg, - "name": "FR Solar pv old 1", + "name": old_name, "nominalCapacity": 5132, } assert res.json() == fr_solar_pv_cfg @@ -254,8 +260,8 @@ def test_lifecycle( # asserts the config is the same assert res.status_code in {200, 201}, res.json() duplicated_config = dict(fr_solar_pv_cfg) - duplicated_config["name"] = new_name - duplicated_id = transform_name_to_id(new_name, lower=False) + duplicated_config["name"] = new_name.lower() + duplicated_id = transform_name_to_id(new_name) duplicated_config["id"] = duplicated_id assert res.json() == duplicated_config @@ -425,7 +431,7 @@ def test_lifecycle( assert res.status_code == 200, res.json() obj = res.json() # If a group is not found, return the default group ("Other RES 1" by default). - assert obj["group"] == "Other RES 1" + assert obj["group"] == "other res 1" # Check PATCH with the wrong `area_id` res = client.patch( @@ -499,7 +505,7 @@ def test_lifecycle( assert res.status_code == 409, res.json() obj = res.json() description = obj["description"] - assert other_cluster_name.upper() in description + assert other_cluster_name.lower() in description assert obj["exception"] == "DuplicateRenewableCluster" @pytest.fixture(name="base_study_id") @@ -580,14 +586,14 @@ def test_variant_lifecycle(self, client: TestClient, user_access_token: str, var ) assert res.status_code in {200, 201}, res.json() cluster_cfg = res.json() - assert cluster_cfg["name"] == new_name + assert cluster_cfg["name"] == new_name.lower() new_id = cluster_cfg["id"] # Check that the duplicate has the right properties res = client.get(f"/v1/studies/{variant_id}/areas/{area_id}/clusters/renewable/{new_id}") assert res.status_code == 200, res.json() cluster_cfg = res.json() - assert cluster_cfg["group"] == "Wind Offshore" + assert cluster_cfg["group"] == "wind offshore" assert cluster_cfg["unitCount"] == 15 assert cluster_cfg["nominalCapacity"] == 42500 diff --git a/tests/integration/study_data_blueprint/test_st_storage.py b/tests/integration/study_data_blueprint/test_st_storage.py index 8d83d12f37..2780e098cf 100644 --- a/tests/integration/study_data_blueprint/test_st_storage.py +++ b/tests/integration/study_data_blueprint/test_st_storage.py @@ -21,7 +21,7 @@ from antarest.core.tasks.model import TaskStatus from antarest.study.business.areas.st_storage_management import create_storage_output -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.config.st_storage import create_st_storage_config from tests.integration.utils import wait_task_completion @@ -148,8 +148,7 @@ def test_lifecycle__nominal( # We can create a short-term storage with the following properties. # Unfilled properties will be set to their default values. siemens_properties = { - "name": siemens_battery, - "group": "Battery", + "group": "battery", "injectionNominalCapacity": 1450, "withdrawalNominalCapacity": 1350, "reservoirCapacity": 1500, @@ -157,12 +156,17 @@ def test_lifecycle__nominal( res = client.post( f"/v1/studies/{internal_study_id}/areas/{area_id}/storages", headers=user_headers, - json=siemens_properties, + json={"name": siemens_battery, **siemens_properties}, ) assert res.status_code == 200, res.json() siemens_battery_id = res.json()["id"] assert siemens_battery_id == transform_name_to_id(siemens_battery) - siemens_output = {**default_output, **siemens_properties, "id": siemens_battery_id} + siemens_output = { + **default_output, + **siemens_properties, + "id": siemens_battery_id, + "name": siemens_battery.lower(), + } assert res.json() == siemens_output # reading the properties of a short-term storage @@ -234,7 +238,7 @@ def test_lifecycle__nominal( assert res.status_code == 200, res.json() siemens_output = { **siemens_output, - "name": "New Siemens Battery", + "name": "new siemens battery", "reservoirCapacity": 2500, } assert res.json() == siemens_output @@ -300,7 +304,7 @@ def test_lifecycle__nominal( assert res.status_code in {200, 201}, res.json() # asserts the config is the same duplicated_output = dict(siemens_output) - duplicated_output["name"] = new_name + duplicated_output["name"] = new_name.lower() duplicated_id = transform_name_to_id(new_name) duplicated_output["id"] = duplicated_id assert res.json() == duplicated_output @@ -385,6 +389,10 @@ def test_lifecycle__nominal( assert res.status_code == 200, res.json() siemens_output = {**default_output, **siemens_properties, "id": siemens_battery_id} grand_maison_output = {**default_output, **grand_maison_properties, "id": grand_maison_id} + # assert we return name and group as lower values + for key in ["name", "group"]: + grand_maison_output[key] = grand_maison_properties[key].lower() + siemens_output[key] = siemens_properties[key].lower() assert res.json() == [duplicated_output, siemens_output, grand_maison_output] # We can delete the three short-term storages at once. @@ -612,7 +620,7 @@ def test__default_values( ) assert res.status_code == 200, res.json() tesla_battery_id = res.json()["id"] - tesla_output = {**default_output, "id": tesla_battery_id, "name": tesla_battery, "group": "Battery"} + tesla_output = {**default_output, "id": tesla_battery_id, "name": tesla_battery.lower(), "group": "battery"} assert res.json() == tesla_output # Use the Debug mode to make sure that the initialLevel and initialLevelOptim properties @@ -623,7 +631,7 @@ def test__default_values( ) assert res.status_code == 200, res.json() actual = res.json() - expected = {**default_config, "name": tesla_battery, "group": "Battery"} + expected = {**default_config, "name": tesla_battery.lower(), "group": "battery"} assert actual == expected # We want to make sure that the default properties are applied to a study variant. @@ -653,7 +661,7 @@ def test__default_values( "action": "create_st_storage", "args": { "area_id": "fr", - "parameters": {**default_config, "name": siemens_battery, "group": "Battery"}, + "parameters": {**default_config, "name": siemens_battery.lower(), "group": "battery"}, "pmax_injection": ANY, "pmax_withdrawal": ANY, "lower_rule_curve": ANY, @@ -728,8 +736,8 @@ def test__default_values( actual = res.json() expected = { **default_config, - "name": siemens_battery, - "group": "Battery", + "name": siemens_battery.lower(), + "group": "battery", "injectionnominalcapacity": 1600, "initiallevel": 0.0, } @@ -814,14 +822,14 @@ def test_variant_lifecycle(self, client: TestClient, user_access_token: str, var ) assert res.status_code in {200, 201}, res.json() cluster_cfg = res.json() - assert cluster_cfg["name"] == new_name + assert cluster_cfg["name"] == new_name.lower() new_id = cluster_cfg["id"] # Check that the duplicate has the right properties res = client.get(f"/v1/studies/{variant_id}/areas/{area_id}/storages/{new_id}") assert res.status_code == 200, res.json() cluster_cfg = res.json() - assert cluster_cfg["group"] == "Battery" + assert cluster_cfg["group"] == "battery" assert cluster_cfg["injectionNominalCapacity"] == 4500 assert cluster_cfg["withdrawalNominalCapacity"] == 4230 assert cluster_cfg["reservoirCapacity"] == 5600 diff --git a/tests/integration/study_data_blueprint/test_table_mode.py b/tests/integration/study_data_blueprint/test_table_mode.py index 17133f63be..a7a398ed52 100644 --- a/tests/integration/study_data_blueprint/test_table_mode.py +++ b/tests/integration/study_data_blueprint/test_table_mode.py @@ -412,7 +412,7 @@ def test_lifecycle__nominal( "enabled": True, "fixedCost": 0, "genTs": "use global", - "group": "Other 2", + "group": "other 2", "lawForced": "uniform", "lawPlanned": "uniform", "marginalCost": 10, @@ -439,7 +439,7 @@ def test_lifecycle__nominal( "enabled": True, "fixedCost": 0, "genTs": "use global", - "group": "Nuclear", + "group": "nuclear", "lawForced": "uniform", "lawPlanned": "uniform", "marginalCost": 20, @@ -484,42 +484,42 @@ def test_lifecycle__nominal( assert res.status_code == 200, res.json() expected: t.Dict[str, t.Dict[str, t.Any]] expected = { - "de / 01_solar": {"group": "Other 2", "nominalCapacity": 500000, "unitCount": 17}, - "de / 02_wind_on": {"group": "Nuclear", "nominalCapacity": 314159, "unitCount": 15}, - "de / 03_wind_off": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "de / 04_res": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "de / 05_nuclear": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "de / 06_coal": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "de / 07_gas": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "de / 08_non-res": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "de / 09_hydro_pump": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "es / 01_solar": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "es / 02_wind_on": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "es / 03_wind_off": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "es / 04_res": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "es / 05_nuclear": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "es / 06_coal": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "es / 07_gas": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "es / 08_non-res": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "es / 09_hydro_pump": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "fr / 01_solar": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "fr / 02_wind_on": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "fr / 03_wind_off": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "fr / 04_res": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "fr / 05_nuclear": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "fr / 06_coal": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "fr / 07_gas": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "fr / 08_non-res": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "fr / 09_hydro_pump": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "it / 01_solar": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "it / 02_wind_on": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "it / 03_wind_off": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "it / 04_res": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "it / 05_nuclear": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "it / 06_coal": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "it / 07_gas": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "it / 08_non-res": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, - "it / 09_hydro_pump": {"group": "Other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "de / 01_solar": {"group": "other 2", "nominalCapacity": 500000, "unitCount": 17}, + "de / 02_wind_on": {"group": "nuclear", "nominalCapacity": 314159, "unitCount": 15}, + "de / 03_wind_off": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "de / 04_res": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "de / 05_nuclear": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "de / 06_coal": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "de / 07_gas": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "de / 08_non-res": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "de / 09_hydro_pump": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "es / 01_solar": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "es / 02_wind_on": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "es / 03_wind_off": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "es / 04_res": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "es / 05_nuclear": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "es / 06_coal": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "es / 07_gas": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "es / 08_non-res": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "es / 09_hydro_pump": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "fr / 01_solar": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "fr / 02_wind_on": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "fr / 03_wind_off": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "fr / 04_res": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "fr / 05_nuclear": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "fr / 06_coal": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "fr / 07_gas": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "fr / 08_non-res": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "fr / 09_hydro_pump": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "it / 01_solar": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "it / 02_wind_on": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "it / 03_wind_off": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "it / 04_res": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "it / 05_nuclear": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "it / 06_coal": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "it / 07_gas": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "it / 08_non-res": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, + "it / 09_hydro_pump": {"group": "other 1", "nominalCapacity": 1000000, "unitCount": 1}, } if study_version >= 860: for key in expected: @@ -640,12 +640,12 @@ def test_lifecycle__nominal( ) assert res.status_code == 200, res.json() expected = { - "fr / Dieppe": {"enabled": False, "group": "Wind Offshore", "nominalCapacity": 8, "unitCount": 62}, - "fr / La Rochelle": {"enabled": True, "group": "Solar PV", "nominalCapacity": 3.1, "unitCount": 2}, - "fr / Oleron": {"enabled": True, "group": "Wind Offshore", "nominalCapacity": 15, "unitCount": 70}, - "it / Pouilles": {"enabled": False, "group": "Wind Onshore", "nominalCapacity": 11, "unitCount": 40}, - "it / Sardaigne": {"enabled": True, "group": "Wind Offshore", "nominalCapacity": 12, "unitCount": 86}, - "it / Sicile": {"enabled": True, "group": "Solar PV", "nominalCapacity": 1.8, "unitCount": 1}, + "fr / dieppe": {"enabled": False, "group": "wind offshore", "nominalCapacity": 8, "unitCount": 62}, + "fr / la rochelle": {"enabled": True, "group": "solar pv", "nominalCapacity": 3.1, "unitCount": 2}, + "fr / oleron": {"enabled": True, "group": "wind offshore", "nominalCapacity": 15, "unitCount": 70}, + "it / pouilles": {"enabled": False, "group": "wind onshore", "nominalCapacity": 11, "unitCount": 40}, + "it / sardaigne": {"enabled": True, "group": "wind offshore", "nominalCapacity": 12, "unitCount": 86}, + "it / sicile": {"enabled": True, "group": "solar pv", "nominalCapacity": 1.8, "unitCount": 1}, } actual = res.json() assert actual == expected @@ -748,7 +748,7 @@ def test_lifecycle__nominal( # "name": "Siemens", "efficiency": 1, "enabled": None, - "group": "Battery", + "group": "battery", "initialLevel": 0.5, "initialLevelOptim": False, "injectionNominalCapacity": 1550, @@ -760,7 +760,7 @@ def test_lifecycle__nominal( # "name": "Tesla", "efficiency": 0.75, "enabled": None, - "group": "Battery", + "group": "battery", "initialLevel": 0.89, "initialLevelOptim": False, "injectionNominalCapacity": 1200, @@ -772,7 +772,7 @@ def test_lifecycle__nominal( # "name": "storage3", "efficiency": 1, "enabled": None, - "group": "Pondage", + "group": "pondage", "initialLevel": 1, "initialLevelOptim": False, "injectionNominalCapacity": 1234, @@ -784,7 +784,7 @@ def test_lifecycle__nominal( # "name": "storage4", "efficiency": 1, "enabled": None, - "group": "PSP_open", + "group": "psp_open", "initialLevel": 0.5, "initialLevelOptim": True, "injectionNominalCapacity": 567, @@ -817,25 +817,25 @@ def test_lifecycle__nominal( assert res.status_code == 200, res.json() expected = { "fr / siemens": { - "group": "Battery", + "group": "battery", "injectionNominalCapacity": 1550, "reservoirCapacity": 1500, "withdrawalNominalCapacity": 1550, }, "fr / tesla": { - "group": "Battery", + "group": "battery", "injectionNominalCapacity": 1200, "reservoirCapacity": 1200, "withdrawalNominalCapacity": 1200, }, "it / storage3": { - "group": "Pondage", + "group": "pondage", "injectionNominalCapacity": 1234, "reservoirCapacity": 1357, "withdrawalNominalCapacity": 1020, }, "it / storage4": { - "group": "PSP_open", + "group": "psp_open", "injectionNominalCapacity": 567, "reservoirCapacity": 500, "withdrawalNominalCapacity": 456, @@ -859,7 +859,7 @@ def test_lifecycle__nominal( ) assert res.status_code == 200, res.json() cluster_id = res.json()["id"] - assert cluster_id == "Cluster 1" + assert cluster_id == "cluster 1" # Create Binding Constraints res = client.post( @@ -945,7 +945,7 @@ def test_lifecycle__nominal( if study_version >= 870: expected_binding["binding constraint 1"]["group"] = "default" - expected_binding["binding constraint 2"]["group"] = "My BC Group" + expected_binding["binding constraint 2"]["group"] = "my bc group" assert actual == expected_binding diff --git a/tests/integration/study_data_blueprint/test_thermal.py b/tests/integration/study_data_blueprint/test_thermal.py index 6328606247..97ceda384b 100644 --- a/tests/integration/study_data_blueprint/test_thermal.py +++ b/tests/integration/study_data_blueprint/test_thermal.py @@ -49,7 +49,7 @@ from starlette.testclient import TestClient from antarest.core.utils.string import to_camel_case -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.config.thermal import ThermalProperties from tests.integration.utils import wait_task_completion @@ -63,7 +63,7 @@ "enabled": True, "fixedCost": 0.0, "genTs": "use global", - "group": "Other 1", + "group": "other 1", "id": "01_solar", "lawForced": "uniform", "lawPlanned": "uniform", @@ -87,7 +87,7 @@ "enabled": True, "fixedCost": 0.0, "genTs": "use global", - "group": "Other 1", + "group": "other 1", "id": "02_wind_on", "lawForced": "uniform", "lawPlanned": "uniform", @@ -111,7 +111,7 @@ "enabled": True, "fixedCost": 0.0, "genTs": "use global", - "group": "Other 1", + "group": "other 1", "id": "03_wind_off", "lawForced": "uniform", "lawPlanned": "uniform", @@ -135,7 +135,7 @@ "enabled": True, "fixedCost": 0.0, "genTs": "use global", - "group": "Other 1", + "group": "other 1", "id": "04_res", "lawForced": "uniform", "lawPlanned": "uniform", @@ -159,7 +159,7 @@ "enabled": True, "fixedCost": 0.0, "genTs": "use global", - "group": "Other 1", + "group": "other 1", "id": "05_nuclear", "lawForced": "uniform", "lawPlanned": "uniform", @@ -183,7 +183,7 @@ "enabled": True, "fixedCost": 0.0, "genTs": "use global", - "group": "Other 1", + "group": "other 1", "id": "06_coal", "lawForced": "uniform", "lawPlanned": "uniform", @@ -207,7 +207,7 @@ "enabled": True, "fixedCost": 0.0, "genTs": "use global", - "group": "Other 1", + "group": "other 1", "id": "07_gas", "lawForced": "uniform", "lawPlanned": "uniform", @@ -231,7 +231,7 @@ "enabled": True, "fixedCost": 0.0, "genTs": "use global", - "group": "Other 1", + "group": "other 1", "id": "08_non-res", "lawForced": "uniform", "lawPlanned": "uniform", @@ -255,7 +255,7 @@ "enabled": True, "fixedCost": 0.0, "genTs": "use global", - "group": "Other 1", + "group": "other 1", "id": "09_hydro_pump", "lawForced": "uniform", "lawPlanned": "uniform", @@ -374,12 +374,14 @@ def test_lifecycle(self, client: TestClient, user_access_token: str, internal_st ) assert res.status_code == 200, res.json() fr_gas_conventional_id = res.json()["id"] - assert fr_gas_conventional_id == transform_name_to_id(fr_gas_conventional, lower=False) + assert fr_gas_conventional_id == transform_name_to_id(fr_gas_conventional) # noinspection SpellCheckingInspection fr_gas_conventional_cfg = { **fr_gas_conventional_props, "id": fr_gas_conventional_id, **{p: pollutants_values for p in pollutants_names}, + "name": fr_gas_conventional_props["name"].lower(), + "group": fr_gas_conventional_props["group"].lower(), } fr_gas_conventional_cfg = { **fr_gas_conventional_cfg, @@ -426,17 +428,18 @@ def test_lifecycle(self, client: TestClient, user_access_token: str, internal_st assert res.json() == EXISTING_CLUSTERS + [fr_gas_conventional_cfg] # updating properties + name = "FR_Gas conventional old 1" res = client.patch( f"/v1/studies/{internal_study_id}/areas/{area_id}/clusters/thermal/{fr_gas_conventional_id}", json={ - "name": "FR_Gas conventional old 1", + "name": name, "nominalCapacity": 32.1, }, ) assert res.status_code == 200, res.json() fr_gas_conventional_cfg = { **fr_gas_conventional_cfg, - "name": "FR_Gas conventional old 1", + "name": name.lower(), "nominalCapacity": 32.1, } assert res.json() == fr_gas_conventional_cfg @@ -509,8 +512,8 @@ def test_lifecycle(self, client: TestClient, user_access_token: str, internal_st assert res.status_code in {200, 201}, res.json() # asserts the config is the same duplicated_config = dict(fr_gas_conventional_cfg) - duplicated_config["name"] = new_name - duplicated_id = transform_name_to_id(new_name, lower=False) + duplicated_config["name"] = new_name.lower() + duplicated_id = transform_name_to_id(new_name) duplicated_config["id"] = duplicated_id # takes the update into account if version >= 860: @@ -626,7 +629,7 @@ def test_lifecycle(self, client: TestClient, user_access_token: str, internal_st ) assert res.status_code == 403, res.json() description = res.json()["description"] - assert all([elm in description for elm in [fr_gas_conventional, "binding constraint"]]) + assert all([elm in description for elm in [fr_gas_conventional.lower(), "binding constraint"]]) assert res.json()["exception"] == "ReferencedObjectDeletionNotAllowed" # delete the binding constraint @@ -667,7 +670,7 @@ def test_lifecycle(self, client: TestClient, user_access_token: str, internal_st assert res.status_code == 200, res.json() deleted_clusters = [other_cluster_id1, other_cluster_id2, fr_gas_conventional_id] for cluster in res.json(): - assert transform_name_to_id(cluster["name"], lower=False) not in deleted_clusters + assert transform_name_to_id(cluster["name"]) not in deleted_clusters # =========================== # THERMAL CLUSTER ERRORS @@ -756,7 +759,7 @@ def test_lifecycle(self, client: TestClient, user_access_token: str, internal_st assert res.status_code == 200, res.json() obj = res.json() # If a group is not found, return the default group ('OTHER1' by default). - assert obj["group"] == "Other 1" + assert obj["group"] == "other 1" # Check PATCH with the wrong `area_id` res = client.patch( @@ -836,7 +839,7 @@ def test_lifecycle(self, client: TestClient, user_access_token: str, internal_st assert res.status_code == 409, res.json() obj = res.json() description = obj["description"] - assert new_name.upper() in description + assert new_name.lower() in description assert obj["exception"] == "DuplicateThermalCluster" @pytest.fixture(name="base_study_id") @@ -918,14 +921,14 @@ def test_variant_lifecycle(self, client: TestClient, user_access_token: str, var ) assert res.status_code in {200, 201}, res.json() cluster_cfg = res.json() - assert cluster_cfg["name"] == new_name + assert cluster_cfg["name"] == new_name.lower() new_id = cluster_cfg["id"] # Check that the duplicate has the right properties res = client.get(f"/v1/studies/{variant_id}/areas/{area_id}/clusters/thermal/{new_id}") assert res.status_code == 200, res.json() cluster_cfg = res.json() - assert cluster_cfg["group"] == "Nuclear" + assert cluster_cfg["group"] == "nuclear" assert cluster_cfg["unitCount"] == 13 assert cluster_cfg["nominalCapacity"] == 42500 assert cluster_cfg["marginalCost"] == 0.2 diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index 37f34f5273..27cb8cf78a 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -532,19 +532,19 @@ def test_area_management(client: TestClient, admin_access_token: str) -> None: { "code-oi": None, "enabled": True, - "group": None, + "group": "other 1", "id": "cluster 1", - "marginal-cost": None, - "market-bid-cost": None, - "min-down-time": None, - "min-stable-power": None, - "min-up-time": None, + "marginal-cost": 0.0, + "market-bid-cost": 0.0, + "min-down-time": 1, + "min-stable-power": 0.0, + "min-up-time": 1, "name": "cluster 1", - "nominalcapacity": 0, - "spinning": None, - "spread-cost": None, + "nominalcapacity": 0.0, + "spinning": 0.0, + "spread-cost": 0.0, "type": None, - "unitcount": 0, + "unitcount": 1, } ], "type": "AREA", @@ -558,19 +558,19 @@ def test_area_management(client: TestClient, admin_access_token: str) -> None: { "code-oi": None, "enabled": True, - "group": None, + "group": "other 1", "id": "cluster 2", - "marginal-cost": None, - "market-bid-cost": None, - "min-down-time": None, - "min-stable-power": None, - "min-up-time": None, + "marginal-cost": 0.0, + "market-bid-cost": 0.0, + "min-down-time": 1, + "min-stable-power": 0.0, + "min-up-time": 1, "name": "cluster 2", "nominalcapacity": 2.5, - "spinning": None, - "spread-cost": None, + "spinning": 0.0, + "spread-cost": 0.0, "type": None, - "unitcount": 0, + "unitcount": 1, } ], "type": "AREA", @@ -1336,7 +1336,7 @@ def test_area_management(client: TestClient, admin_access_token: str) -> None: ) expected = { "enabled": False, - "group": "Other RES 1", # Default group used when not specified. + "group": "other res 1", # Default group used when not specified. "id": "cluster renewable 1", "name": "cluster renewable 1 renamed", "nominalCapacity": 3.0, @@ -1398,6 +1398,7 @@ def test_area_management(client: TestClient, admin_access_token: str) -> None: f"/v1/studies/{study_id}/areas/area 1/clusters/thermal/cluster 1/form", ) assert res.status_code == 200, res.json() + obj["group"] = obj["group"].lower() assert res.json() == {"id": "cluster 1", **obj} # Links @@ -1470,19 +1471,19 @@ def test_area_management(client: TestClient, admin_access_token: str) -> None: { "code-oi": None, "enabled": True, - "group": None, + "group": "other 1", "id": "cluster 2", - "marginal-cost": None, - "market-bid-cost": None, - "min-down-time": None, - "min-stable-power": None, - "min-up-time": None, + "marginal-cost": 0.0, + "market-bid-cost": 0.0, + "min-down-time": 1, + "min-stable-power": 0.0, + "min-up-time": 1, "name": "cluster 2", "nominalcapacity": 2.5, - "spinning": None, - "spread-cost": None, + "spinning": 0.0, + "spread-cost": 0.0, "type": None, - "unitcount": 0, + "unitcount": 1, } ], "type": "AREA", diff --git a/tests/integration/test_integration_token_end_to_end.py b/tests/integration/test_integration_token_end_to_end.py index 6bfefe0d3b..01b92788d4 100644 --- a/tests/integration/test_integration_token_end_to_end.py +++ b/tests/integration/test_integration_token_end_to_end.py @@ -96,7 +96,7 @@ def test_nominal_case_of_an_api_user(client: TestClient, admin_access_token: str "cluster_name": "mycluster", "parameters": { "group": "Gas", - "unitCount": 1, + "unitcount": 1, "marginal_cost": 50, }, }, @@ -116,15 +116,15 @@ def test_nominal_case_of_an_api_user(client: TestClient, admin_access_token: str "parameters": { "group": "Gas", "marginal-cost": 98, - "unitCount": 1, - "nominalCapacity": 250, - "minStablePower": 0.0, - "minUpTime": 2, - "minDownTime": 2, + "unitcount": 1, + "nominalcapacity": 250, + "min-stable-power": 0.0, + "min-up-time": 2, + "min-down-time": 2, "spinning": 5, - "spreadCost": 0.0, - "startupCost": 2500, - "marketBidCost": 85, + "spread-cost": 0.0, + "startup-cost": 2500, + "market-bid-cost": 85, "co2": 0.3, }, }, diff --git a/tests/integration/variant_blueprint/test_renewable_cluster.py b/tests/integration/variant_blueprint/test_renewable_cluster.py index 18e6af0c56..74fc2c18f7 100644 --- a/tests/integration/variant_blueprint/test_renewable_cluster.py +++ b/tests/integration/variant_blueprint/test_renewable_cluster.py @@ -17,7 +17,7 @@ from starlette.testclient import TestClient from antarest.core.tasks.model import TaskStatus -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from tests.integration.utils import wait_task_completion @@ -76,7 +76,7 @@ def test_lifecycle( area_fr_id = transform_name_to_id("FR") cluster_fr1 = "Oleron" - cluster_fr1_id = transform_name_to_id(cluster_fr1, lower=False) + cluster_fr1_id = transform_name_to_id(cluster_fr1) args = { "area_id": area_fr_id, "cluster_name": cluster_fr1_id, @@ -95,7 +95,7 @@ def test_lifecycle( res.raise_for_status() cluster_fr2 = "La_Rochelle" - cluster_fr2_id = transform_name_to_id(cluster_fr2, lower=False) + cluster_fr2_id = transform_name_to_id(cluster_fr2) args = { "area_id": area_fr_id, "cluster_name": cluster_fr2_id, @@ -124,9 +124,9 @@ def test_lifecycle( properties = res.json() expected = { "enabled": True, - "group": "Wind Offshore", - "id": "Oleron", - "name": cluster_fr1, + "group": "wind offshore", + "id": "oleron", + "name": cluster_fr1.lower(), "nominalCapacity": 2500.0, "tsInterpretation": "power-generation", "unitCount": 1, @@ -141,9 +141,9 @@ def test_lifecycle( properties = res.json() expected = { "enabled": False, - "group": "Solar PV", - "id": "La_Rochelle", - "name": cluster_fr2, + "group": "solar pv", + "id": "la_rochelle", + "name": cluster_fr2.lower(), "nominalCapacity": 3500.0, "tsInterpretation": "power-generation", "unitCount": 4, @@ -201,10 +201,10 @@ def test_lifecycle( area_it_id = transform_name_to_id("IT") cluster_it1 = "Oléron" - cluster_it1_id = transform_name_to_id(cluster_it1, lower=False) + cluster_it1_id = transform_name_to_id(cluster_it1) args = { "area_id": area_it_id, - "cluster_name": cluster_it1_id, + "cluster_name": cluster_it1, "parameters": { "group": "wind offshore", "name": cluster_it1, @@ -228,9 +228,9 @@ def test_lifecycle( properties = res.json() expected = { "enabled": True, - "group": "Wind Offshore", - "id": "Ol ron", - "name": cluster_it1, + "group": "wind offshore", + "id": "ol ron", + "name": cluster_it1.lower(), "nominalCapacity": 1000.0, "tsInterpretation": "production-factor", "unitCount": 1, @@ -274,9 +274,11 @@ def test_lifecycle( "list": { cluster_fr1_id: { "group": "wind offshore", - "name": cluster_fr1, + "name": cluster_fr1.lower(), "nominalcapacity": 2500, "ts-interpretation": "power-generation", + "unitcount": 1, + "enabled": True, }, } }, @@ -284,10 +286,11 @@ def test_lifecycle( "list": { cluster_it1_id: { "group": "wind offshore", - "name": cluster_it1, + "name": cluster_it1.lower(), "nominalcapacity": 1000, "ts-interpretation": "production-factor", "unitcount": 1, + "enabled": True, } } }, @@ -317,10 +320,11 @@ def test_lifecycle( "list": { cluster_it1_id: { "group": "wind offshore", - "name": cluster_it1, + "name": cluster_it1.lower(), "nominalcapacity": 1000, "ts-interpretation": "production-factor", "unitcount": 1, + "enabled": True, } } }, diff --git a/tests/integration/variant_blueprint/test_st_storage.py b/tests/integration/variant_blueprint/test_st_storage.py index b4092f0acb..7d48aa2c94 100644 --- a/tests/integration/variant_blueprint/test_st_storage.py +++ b/tests/integration/variant_blueprint/test_st_storage.py @@ -18,7 +18,7 @@ from starlette.testclient import TestClient from antarest.core.tasks.model import TaskStatus -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from tests.integration.utils import wait_task_completion diff --git a/tests/integration/variant_blueprint/test_thermal_cluster.py b/tests/integration/variant_blueprint/test_thermal_cluster.py index 7e5ce92677..ef20d9d3ab 100644 --- a/tests/integration/variant_blueprint/test_thermal_cluster.py +++ b/tests/integration/variant_blueprint/test_thermal_cluster.py @@ -20,7 +20,7 @@ from starlette.testclient import TestClient from antarest.core.tasks.model import TaskDTO, TaskStatus -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id def _create_thermal_params(cluster_name: str) -> t.Mapping[str, t.Any]: @@ -97,7 +97,7 @@ def test_cascade_update( "action": "create_cluster", "args": { "area_id": area_id, - "cluster_name": transform_name_to_id(cluster_name, lower=False), + "cluster_name": transform_name_to_id(cluster_name), "parameters": _create_thermal_params(cluster_name), "prepro": np.random.rand(8760, 6).tolist(), "modulation": np.random.rand(8760, 4).tolist(), diff --git a/tests/storage/business/test_study_version_upgrader.py b/tests/storage/business/test_study_version_upgrader.py index 39ce1da653..4d248539f2 100644 --- a/tests/storage/business/test_study_version_upgrader.py +++ b/tests/storage/business/test_study_version_upgrader.py @@ -25,7 +25,7 @@ from antarest.core.exceptions import UnsupportedStudyVersion from antarest.study.storage.rawstudy.ini_reader import IniReader -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.root.settings.generaldata import DUPLICATE_KEYS from antarest.study.storage.study_upgrader import ( InvalidUpgrade, diff --git a/tests/storage/repository/filesystem/config/test_utils.py b/tests/storage/repository/filesystem/config/test_utils.py index cf470df38f..de31252956 100644 --- a/tests/storage/repository/filesystem/config/test_utils.py +++ b/tests/storage/repository/filesystem/config/test_utils.py @@ -14,7 +14,7 @@ import pytest -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id VALID_CHARS = "azAZ09_-(),&" @@ -29,8 +29,7 @@ def test_transform_name_to_id__nominal_case(name, expected): @pytest.mark.parametrize("name", VALID_CHARS) def test_transform_name_to_id__valid_chars(name): - assert transform_name_to_id(name, lower=True) == name.lower() - assert transform_name_to_id(name, lower=False) == name + assert transform_name_to_id(name) == name.lower() @pytest.mark.parametrize("name", sorted(set(string.punctuation) - set(VALID_CHARS))) diff --git a/tests/study/business/areas/test_st_storage_management.py b/tests/study/business/areas/test_st_storage_management.py index 56a42d88a0..7826113112 100644 --- a/tests/study/business/areas/test_st_storage_management.py +++ b/tests/study/business/areas/test_st_storage_management.py @@ -158,7 +158,7 @@ def test_get_all_storages__nominal_case( "id": "storage1", "enabled": None, "group": STStorageGroup.BATTERY, - "name": "Storage1", + "name": "storage1", "injectionNominalCapacity": 1500.0, "withdrawalNominalCapacity": 1500.0, "reservoirCapacity": 20000.0, @@ -170,7 +170,7 @@ def test_get_all_storages__nominal_case( "id": "storage2", "enabled": None, "group": STStorageGroup.PSP_CLOSED, - "name": "Storage2", + "name": "storage2", "injectionNominalCapacity": 2000.0, "withdrawalNominalCapacity": 1500.0, "reservoirCapacity": 20000.0, @@ -182,7 +182,7 @@ def test_get_all_storages__nominal_case( "id": "storage3", "enabled": None, "group": STStorageGroup.PSP_CLOSED, - "name": "Storage3", + "name": "storage3", "injectionNominalCapacity": 1500.0, "withdrawalNominalCapacity": 1500.0, "reservoirCapacity": 21000.0, @@ -264,7 +264,7 @@ def test_get_st_storages__nominal_case( "initialLevel": 0.5, "initialLevelOptim": True, "injectionNominalCapacity": 1500.0, - "name": "Storage1", + "name": "storage1", "reservoirCapacity": 20000.0, "withdrawalNominalCapacity": 1500.0, "enabled": None, @@ -276,7 +276,7 @@ def test_get_st_storages__nominal_case( "initialLevel": 0.5, "initialLevelOptim": False, "injectionNominalCapacity": 2000.0, - "name": "Storage2", + "name": "storage2", "reservoirCapacity": 20000.0, "withdrawalNominalCapacity": 1500.0, "enabled": None, @@ -288,7 +288,7 @@ def test_get_st_storages__nominal_case( "initialLevel": 1, "initialLevelOptim": False, "injectionNominalCapacity": 1500.0, - "name": "Storage3", + "name": "storage3", "reservoirCapacity": 21000.0, "withdrawalNominalCapacity": 1500.0, "enabled": None, @@ -375,7 +375,7 @@ def test_get_st_storage__nominal_case( "initialLevel": 0.5, "initialLevelOptim": True, "injectionNominalCapacity": 1500.0, - "name": "Storage1", + "name": "storage1", "reservoirCapacity": 20000.0, "withdrawalNominalCapacity": 1500.0, "enabled": None, diff --git a/tests/study/business/areas/test_thermal_management.py b/tests/study/business/areas/test_thermal_management.py index 6bb2e34d7e..a0a053599d 100644 --- a/tests/study/business/areas/test_thermal_management.py +++ b/tests/study/business/areas/test_thermal_management.py @@ -373,7 +373,7 @@ def test_create_cluster__study_legacy( "fixedCost": 0.0, "genTs": LocalTSGenerationBehavior.USE_GLOBAL, "group": ThermalClusterGroup.NUCLEAR, - "id": "New Cluster", + "id": "new cluster", "lawForced": LawOption.UNIFORM, "lawPlanned": LawOption.UNIFORM, "marginalCost": 0.0, @@ -382,7 +382,7 @@ def test_create_cluster__study_legacy( "minStablePower": 0.0, "minUpTime": 15, "mustRun": False, - "name": "New Cluster", + "name": "new cluster", "nh3": None, "nmvoc": None, "nominalCapacity": 1000.0, @@ -430,7 +430,7 @@ def test_update_cluster( expected = { "id": "2 avail and must 1", "group": ThermalClusterGroup.GAS, - "name": "New name", + "name": "new name", "enabled": False, "unitCount": 100, "nominalCapacity": 2000.0, diff --git a/tests/study/storage/rawstudy/test_raw_study_service.py b/tests/study/storage/rawstudy/test_raw_study_service.py index a138da1735..4a0f4ca5c2 100644 --- a/tests/study/storage/rawstudy/test_raw_study_service.py +++ b/tests/study/storage/rawstudy/test_raw_study_service.py @@ -25,7 +25,7 @@ from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import RawStudy, StudyAdditionalData from antarest.study.storage.patch_service import PatchService -from antarest.study.storage.rawstudy.model.filesystem.config.st_storage import STStorageConfig, STStorageGroup +from antarest.study.storage.rawstudy.model.filesystem.config.st_storage import STStorageGroup from antarest.study.storage.rawstudy.raw_study_service import RawStudyService from antarest.study.storage.storage_service import StudyStorageService from antarest.study.storage.variantstudy.business.matrix_constants_generator import GeneratorMatrixConstants @@ -131,16 +131,15 @@ def test_export_study_flat( create_st_storage = CreateSTStorage( command_context=command_context, area_id="fr", - parameters=STStorageConfig( - id="", # will be calculated ;-) - name="Storage1", - group=STStorageGroup.BATTERY, - injection_nominal_capacity=1500, - withdrawal_nominal_capacity=1500, - reservoir_capacity=20000, - efficiency=0.94, - initial_level_optim=True, - ), + parameters={ + "name": "Storage1", + "group": STStorageGroup.BATTERY, + "injection_nominal_capacity": 1500, + "withdrawal_nominal_capacity": 1500, + "reservoir_capacity": 20000, + "efficiency": 0.94, + "initial_level_optim": True, + }, pmax_injection=pmax_injection.tolist(), inflows=inflows.tolist(), study_version=raw_study.version, diff --git a/tests/study/storage/variantstudy/test_snapshot_generator.py b/tests/study/storage/variantstudy/test_snapshot_generator.py index 70a05c391a..c2280d2b46 100644 --- a/tests/study/storage/variantstudy/test_snapshot_generator.py +++ b/tests/study/storage/variantstudy/test_snapshot_generator.py @@ -10,7 +10,6 @@ # # This file is part of the Antares project. -import configparser import datetime import json import logging @@ -33,6 +32,7 @@ from antarest.core.utils.fastapi_sqlalchemy import db from antarest.login.model import Group, Role, User from antarest.study.model import RawStudy, Study, StudyAdditionalData +from antarest.study.storage.rawstudy.ini_reader import IniReader from antarest.study.storage.rawstudy.raw_study_service import RawStudyService from antarest.study.storage.variantstudy.model.dbmodel import CommandBlock, VariantStudy, VariantStudySnapshot from antarest.study.storage.variantstudy.model.model import CommandDTO @@ -920,18 +920,46 @@ def test_generate__nominal_case( assert snapshot_dir.exists() assert (snapshot_dir / "study.antares").exists() assert (snapshot_dir / "input/areas/list.txt").read_text().splitlines(keepends=False) == ["North", "South"] - config = configparser.RawConfigParser() - config.read(snapshot_dir / "input/links/north/properties.ini") - assert config.sections() == ["south"] - assert config["south"], "The 'south' section must exist in the 'properties.ini' file." - config = configparser.RawConfigParser() - config.read(snapshot_dir / "input/thermal/clusters/south/list.ini") - assert config.sections() == ["gas_cluster"] - assert config["gas_cluster"] == { # type: ignore - "group": "Gas", - "unitcount": "1", - "nominalcapacity": "500", + reader = IniReader() + properties = reader.read(snapshot_dir / "input/links/north/properties.ini") + assert list(properties.keys()) == ["south"] + reader = IniReader() + cluster_props = reader.read(snapshot_dir / "input/thermal/clusters/south/list.ini") + assert list(cluster_props.keys()) == ["gas_cluster"] + assert cluster_props["gas_cluster"] == { + "co2": 0.0, + "enabled": True, + "fixed-cost": 0.0, + "gen-ts": "use global", + "group": "gas", + "law.forced": "uniform", + "law.planned": "uniform", + "marginal-cost": 0.0, + "market-bid-cost": 0.0, + "min-down-time": 1, + "min-stable-power": 0.0, + "min-up-time": 1, + "must-run": False, "name": "gas_cluster", + "nh3": 0.0, + "nmvoc": 0.0, + "nominalcapacity": 500.0, + "nox": 0.0, + "op1": 0.0, + "op2": 0.0, + "op3": 0.0, + "op4": 0.0, + "op5": 0.0, + "pm10": 0.0, + "pm2_5": 0.0, + "pm5": 0.0, + "so2": 0.0, + "spinning": 0.0, + "spread-cost": 0.0, + "startup-cost": 0.0, + "unitcount": 1, + "volatility.forced": 0.0, + "volatility.planned": 0.0, } # Check: the matrices are not denormalized (we should have links to matrices). diff --git a/tests/study/storage/variantstudy/test_variant_study_service.py b/tests/study/storage/variantstudy/test_variant_study_service.py index dc6b4a47d3..ba0cb93277 100644 --- a/tests/study/storage/variantstudy/test_variant_study_service.py +++ b/tests/study/storage/variantstudy/test_variant_study_service.py @@ -12,7 +12,6 @@ import datetime import re -import typing from pathlib import Path from unittest.mock import Mock @@ -30,7 +29,7 @@ from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import RawStudy, StudyAdditionalData from antarest.study.storage.patch_service import PatchService -from antarest.study.storage.rawstudy.model.filesystem.config.st_storage import STStorageConfig, STStorageGroup +from antarest.study.storage.rawstudy.model.filesystem.config.st_storage import STStorageGroup from antarest.study.storage.rawstudy.raw_study_service import RawStudyService from antarest.study.storage.storage_service import StudyStorageService from antarest.study.storage.variantstudy.business.matrix_constants_generator import GeneratorMatrixConstants @@ -197,16 +196,15 @@ def test_generate_task( create_st_storage = CreateSTStorage( command_context=command_context, area_id="fr", - parameters=STStorageConfig( - id="", # will be calculated ;-) - name="Storage1", - group=STStorageGroup.BATTERY, - injection_nominal_capacity=1500, - withdrawal_nominal_capacity=1500, - reservoir_capacity=20000, - efficiency=0.94, - initial_level_optim=True, - ), + parameters={ + "name": "Storage1", + "group": STStorageGroup.BATTERY, + "injection_nominal_capacity": 1500, + "withdrawal_nominal_capacity": 1500, + "reservoir_capacity": 20000, + "efficiency": 0.94, + "initial_level_optim": True, + }, pmax_injection=pmax_injection.tolist(), inflows=inflows.tolist(), study_version=study_version, diff --git a/tests/variantstudy/model/command/test_create_area.py b/tests/variantstudy/model/command/test_create_area.py index 87f8d1044c..08b3e7c451 100644 --- a/tests/variantstudy/model/command/test_create_area.py +++ b/tests/variantstudy/model/command/test_create_area.py @@ -18,7 +18,8 @@ from antarest.study.model import STUDY_VERSION_8_8 from antarest.study.storage.rawstudy.ini_reader import IniReader -from antarest.study.storage.rawstudy.model.filesystem.config.model import EnrModelling, transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.model import EnrModelling from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.command_reverter import CommandReverter from antarest.study.storage.variantstudy.model.command.create_area import CreateArea diff --git a/tests/variantstudy/model/command/test_create_cluster.py b/tests/variantstudy/model/command/test_create_cluster.py index 77c4d10f90..b6a03874f5 100644 --- a/tests/variantstudy/model/command/test_create_cluster.py +++ b/tests/variantstudy/model/command/test_create_cluster.py @@ -19,7 +19,8 @@ from pydantic import ValidationError from antarest.study.model import STUDY_VERSION_8_8 -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.thermal import Thermal870Properties from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.command_reverter import CommandReverter from antarest.study.storage.variantstudy.model.command.common import CommandName @@ -57,13 +58,15 @@ def test_init(self, command_context: CommandContext): prepro_id = command_context.matrix_service.create(prepro) modulation_id = command_context.matrix_service.create(modulation) assert cl.area_id == "foo" - assert cl.cluster_name == "Cluster1" - assert cl.parameters == {"group": "Nuclear", "nominalcapacity": 2400, "unitcount": 2} + assert cl.cluster_name == "cluster1" + assert cl.parameters.group == "nuclear" + assert cl.parameters.nominal_capacity == 2400 + assert cl.parameters.unit_count == 2 assert cl.prepro == f"matrix://{prepro_id}" assert cl.modulation == f"matrix://{modulation_id}" def test_validate_cluster_name(self, command_context: CommandContext): - with pytest.raises(ValidationError, match="cluster_name"): + with pytest.raises(ValidationError, match="name"): CreateCluster( area_id="fr", cluster_name="%", @@ -95,16 +98,16 @@ def test_validate_modulation(self, command_context: CommandContext): def test_apply(self, empty_study: FileStudy, command_context: CommandContext): study_path = empty_study.config.study_path area_name = "DE" - area_id = transform_name_to_id(area_name, lower=True) + area_id = transform_name_to_id(area_name) cluster_name = "Cluster-1" - cluster_id = transform_name_to_id(cluster_name, lower=True) + cluster_id = transform_name_to_id(cluster_name) CreateArea(area_name=area_name, command_context=command_context, study_version=STUDY_VERSION_8_8).apply( empty_study ) parameters = { - "group": "Other", + "group": "nuclear", "unitcount": "1", "nominalcapacity": "1000000", "marginal-cost": "30", @@ -113,6 +116,7 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): prepro = GEN.random((365, 6)).tolist() modulation = GEN.random((8760, 4)).tolist() + command = CreateCluster( area_id=area_id, cluster_name=cluster_name, @@ -133,12 +137,13 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): clusters = configparser.ConfigParser() clusters.read(study_path / "input" / "thermal" / "clusters" / area_id / "list.ini") - assert str(clusters[cluster_name]["name"]) == cluster_name - assert str(clusters[cluster_name]["group"]) == parameters["group"] - assert int(clusters[cluster_name]["unitcount"]) == int(parameters["unitcount"]) - assert float(clusters[cluster_name]["nominalcapacity"]) == float(parameters["nominalcapacity"]) - assert float(clusters[cluster_name]["marginal-cost"]) == float(parameters["marginal-cost"]) - assert float(clusters[cluster_name]["market-bid-cost"]) == float(parameters["market-bid-cost"]) + section = clusters[cluster_name.lower()] + assert str(section["name"]) == cluster_name.lower() + assert str(section["group"]) == parameters["group"] + assert int(section["unitcount"]) == int(parameters["unitcount"]) + assert float(section["nominalcapacity"]) == float(parameters["nominalcapacity"]) + assert float(section["marginal-cost"]) == float(parameters["marginal-cost"]) + assert float(section["market-bid-cost"]) == float(parameters["market-bid-cost"]) assert (study_path / "input" / "thermal" / "prepro" / area_id / cluster_id / "data.txt.link").exists() assert (study_path / "input" / "thermal" / "prepro" / area_id / cluster_id / "modulation.txt.link").exists() @@ -178,10 +183,11 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext): def test_to_dto(self, command_context: CommandContext): prepro = GEN.random((365, 6)).tolist() modulation = GEN.random((8760, 4)).tolist() + parameters = {"group": "Nuclear", "unitcount": 2, "nominalcapacity": 2400} command = CreateCluster( area_id="foo", cluster_name="Cluster1", - parameters={"group": "Nuclear", "unitcount": 2, "nominalcapacity": 2400}, + parameters=parameters, command_context=command_context, prepro=prepro, modulation=modulation, @@ -194,8 +200,10 @@ def test_to_dto(self, command_context: CommandContext): "action": "create_cluster", "args": { "area_id": "foo", - "cluster_name": "Cluster1", - "parameters": {"group": "Nuclear", "nominalcapacity": 2400, "unitcount": 2}, + "cluster_name": "cluster1", + "parameters": Thermal870Properties.model_validate({"name": "cluster1", **parameters}).model_dump( + mode="json", by_alias=True + ), "prepro": prepro_id, "modulation": modulation_id, }, @@ -309,7 +317,9 @@ def test_create_diff(command_context: CommandContext): ), UpdateConfig( target="input/thermal/clusters/foo/list/foo", - data={"nominalcapacity": "2400"}, + data=Thermal870Properties.model_validate({"name": "foo", "nominalcapacity": "2400"}).model_dump( + mode="json", by_alias=True + ), command_context=command_context, study_version=STUDY_VERSION_8_8, ), diff --git a/tests/variantstudy/model/command/test_create_link.py b/tests/variantstudy/model/command/test_create_link.py index bd4bac60ef..611c2918e1 100644 --- a/tests/variantstudy/model/command/test_create_link.py +++ b/tests/variantstudy/model/command/test_create_link.py @@ -21,7 +21,7 @@ from antarest.study.business.link_management import LinkInternal from antarest.study.model import STUDY_VERSION_8_8 from antarest.study.storage.rawstudy.ini_reader import IniReader -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.command_reverter import CommandReverter from antarest.study.storage.variantstudy.model.command.create_area import CreateArea diff --git a/tests/variantstudy/model/command/test_create_renewables_cluster.py b/tests/variantstudy/model/command/test_create_renewables_cluster.py index c23cb83f4f..aa4eeff60e 100644 --- a/tests/variantstudy/model/command/test_create_renewables_cluster.py +++ b/tests/variantstudy/model/command/test_create_renewables_cluster.py @@ -18,7 +18,9 @@ from pydantic import ValidationError from antarest.study.model import STUDY_VERSION_8_1, STUDY_VERSION_8_8 -from antarest.study.storage.rawstudy.model.filesystem.config.model import EnrModelling, transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.model import EnrModelling +from antarest.study.storage.rawstudy.model.filesystem.config.renewable import RenewableProperties from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.command_reverter import CommandReverter from antarest.study.storage.variantstudy.model.command.common import CommandName @@ -32,10 +34,11 @@ class TestCreateRenewablesCluster: # noinspection SpellCheckingInspection def test_init(self, command_context: CommandContext) -> None: + parameters = {"group": "Solar Thermal", "unitcount": 2, "nominalcapacity": 2400} cl = CreateRenewablesCluster( area_id="foo", cluster_name="Cluster1", - parameters={"group": "Solar Thermal", "unitcount": 2, "nominalcapacity": 2400}, + parameters=parameters, command_context=command_context, study_version=STUDY_VERSION_8_8, ) @@ -47,11 +50,13 @@ def test_init(self, command_context: CommandContext) -> None: # Check the command data assert cl.area_id == "foo" - assert cl.cluster_name == "Cluster1" - assert cl.parameters == {"group": "Solar Thermal", "nominalcapacity": 2400, "unitcount": 2} + assert cl.cluster_name == "cluster1" + assert cl.parameters.model_dump(by_alias=True) == RenewableProperties.model_validate( + {"name": "cluster1", **parameters} + ).model_dump(by_alias=True) def test_validate_cluster_name(self, command_context: CommandContext) -> None: - with pytest.raises(ValidationError, match="cluster_name"): + with pytest.raises(ValidationError, match="name"): CreateRenewablesCluster( area_id="fr", cluster_name="%", @@ -66,7 +71,7 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext) -> empty_study.config.version = study_version study_path = empty_study.config.study_path area_name = "DE" - area_id = transform_name_to_id(area_name, lower=True) + area_id = transform_name_to_id(area_name) cluster_name = "Cluster-1" CreateArea(area_name=area_name, command_context=command_context, study_version=study_version).apply(empty_study) @@ -94,8 +99,8 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext) -> clusters = configparser.ConfigParser() clusters.read(study_path / "input" / "renewables" / "clusters" / area_id / "list.ini") - assert str(clusters[cluster_name]["name"]) == cluster_name - assert str(clusters[cluster_name]["ts-interpretation"]) == parameters["ts-interpretation"] + assert str(clusters[cluster_name.lower()]["name"]) == cluster_name.lower() + assert str(clusters[cluster_name.lower()]["ts-interpretation"]) == parameters["ts-interpretation"] output = CreateRenewablesCluster( area_id=area_id, @@ -137,10 +142,11 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext) -> # noinspection SpellCheckingInspection def test_to_dto(self, command_context: CommandContext) -> None: + parameters = {"group": "Solar Thermal", "unitcount": 2, "nominalcapacity": 2400} command = CreateRenewablesCluster( area_id="foo", cluster_name="Cluster1", - parameters={"group": "Solar Thermal", "unitcount": 2, "nominalcapacity": 2400}, + parameters=parameters, command_context=command_context, study_version=STUDY_VERSION_8_8, ) @@ -149,8 +155,10 @@ def test_to_dto(self, command_context: CommandContext) -> None: "action": "create_renewables_cluster", # "renewables" with a final "s". "args": { "area_id": "foo", - "cluster_name": "Cluster1", - "parameters": {"group": "Solar Thermal", "nominalcapacity": 2400, "unitcount": 2}, + "cluster_name": "cluster1", + "parameters": RenewableProperties.model_validate({"name": "cluster1", **parameters}).model_dump( + by_alias=True + ), }, "id": None, "version": 1, @@ -221,17 +229,20 @@ def test_create_diff(command_context: CommandContext) -> None: command_context=command_context, study_version=STUDY_VERSION_8_8, ) + parameters = {"nominal_capacity": 1.2} other_match = CreateRenewablesCluster( area_id="foo", cluster_name="foo", - parameters={"a": "b"}, + parameters=parameters, command_context=command_context, study_version=STUDY_VERSION_8_8, ) assert base.create_diff(other_match) == [ UpdateConfig( target="input/renewables/clusters/foo/list/foo", - data={"a": "b"}, + data=RenewableProperties.model_validate({"name": "foo", **parameters}).model_dump( + mode="json", by_alias=True + ), command_context=command_context, study_version=STUDY_VERSION_8_8, ), diff --git a/tests/variantstudy/model/command/test_create_st_storage.py b/tests/variantstudy/model/command/test_create_st_storage.py index 81093faec8..a0b13410f2 100644 --- a/tests/variantstudy/model/command/test_create_st_storage.py +++ b/tests/variantstudy/model/command/test_create_st_storage.py @@ -9,16 +9,19 @@ # SPDX-License-Identifier: MPL-2.0 # # This file is part of the Antares project. - +import copy import re import numpy as np import pytest from pydantic import ValidationError -from antarest.study.model import STUDY_VERSION_8_8 -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id -from antarest.study.storage.rawstudy.model.filesystem.config.st_storage import STStorageConfig +from antarest.study.model import STUDY_VERSION_8_6, STUDY_VERSION_8_8 +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.st_storage import ( + STStorage880Properties, + STStorageProperties, +) from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.study_upgrader import StudyUpgrader from antarest.study.storage.variantstudy.business.utils import strip_matrix_protocol @@ -83,7 +86,7 @@ def test_init(self, command_context: CommandContext): cmd = CreateSTStorage( command_context=command_context, area_id="area_fr", - parameters=STStorageConfig(**PARAMETERS), + parameters=PARAMETERS, pmax_injection=pmax_injection.tolist(), # type: ignore inflows=inflows.tolist(), # type: ignore study_version=STUDY_VERSION_8_8, @@ -96,7 +99,7 @@ def test_init(self, command_context: CommandContext): assert cmd.command_context == command_context assert cmd.area_id == "area_fr" expected_parameters = {k: str(v) for k, v in PARAMETERS.items()} - assert cmd.parameters == STStorageConfig(**expected_parameters) + assert cmd.parameters == STStorage880Properties(**expected_parameters) # check the matrices links @@ -114,23 +117,15 @@ def test_init__invalid_storage_name(self, recent_study: FileStudy, command_conte CreateSTStorage( command_context=command_context, area_id="dummy", - parameters=STStorageConfig(**parameters), + parameters=parameters, study_version=STUDY_VERSION_8_8, ) # We get 2 errors because the `storage_name` is duplicated in the `parameters`: assert ctx.value.error_count() == 1 raised_error = ctx.value.errors()[0] - assert raised_error["type"] == "value_error" - assert raised_error["msg"] == "Value error, Invalid name '?%$$'." - assert raised_error["input"] == { - "efficiency": 0.94, - "group": "Battery", - "initialleveloptim": True, - "injectionnominalcapacity": 1500, - "name": "?%$$", - "reservoircapacity": 20000, - "withdrawalnominalcapacity": 1500, - } + assert raised_error["type"] == "string_pattern_mismatch" + assert raised_error["msg"] == "String should match pattern '[a-zA-Z0-9_(),& -]+'" + assert raised_error["input"] == "?%$$" def test_init__invalid_matrix_values(self, command_context: CommandContext): array = GEN.random((8760, 1)) @@ -139,7 +134,7 @@ def test_init__invalid_matrix_values(self, command_context: CommandContext): CreateSTStorage( command_context=command_context, area_id="area_fr", - parameters=STStorageConfig(**PARAMETERS), + parameters=PARAMETERS, pmax_injection=array.tolist(), # type: ignore study_version=STUDY_VERSION_8_8, ) @@ -156,7 +151,7 @@ def test_init__invalid_matrix_shape(self, command_context: CommandContext): CreateSTStorage( command_context=command_context, area_id="area_fr", - parameters=STStorageConfig(**PARAMETERS), + parameters=PARAMETERS, pmax_injection=array.tolist(), # type: ignore study_version=STUDY_VERSION_8_8, ) @@ -173,7 +168,7 @@ def test_init__invalid_nan_value(self, command_context: CommandContext): CreateSTStorage( command_context=command_context, area_id="area_fr", - parameters=STStorageConfig(**PARAMETERS), + parameters=PARAMETERS, pmax_injection=array.tolist(), # type: ignore study_version=STUDY_VERSION_8_8, ) @@ -188,7 +183,7 @@ def test_init__invalid_matrix_type(self, command_context: CommandContext): CreateSTStorage( command_context=command_context, area_id="area_fr", - parameters=STStorageConfig(**PARAMETERS), + parameters=PARAMETERS, pmax_injection=[1, 2, 3], study_version=STUDY_VERSION_8_8, ) @@ -198,24 +193,14 @@ def test_init__invalid_matrix_type(self, command_context: CommandContext): assert raised_error["msg"] == "Value error, Invalid matrix shape (3,), expected (8760, 1)" assert "pmax_injection" in raised_error["input"] - def test_apply_config__invalid_version(self, empty_study: FileStudy, command_context: CommandContext): - # Given an old study in version 720 - # When we apply the config to add a new ST Storage - create_st_storage = CreateSTStorage( - command_context=command_context, - area_id="foo", - parameters=STStorageConfig(**PARAMETERS), - study_version=empty_study.config.version, - ) - command_output = create_st_storage.apply_config(empty_study.config) - - # Then, the output should be an error - assert command_output.status is False - assert re.search( - rf"Invalid.*version {empty_study.config.version}", - command_output.message, - flags=re.IGNORECASE, - ) + def test_instantiate_with_invalid_version(self, empty_study: FileStudy, command_context: CommandContext): + with pytest.raises(ValidationError): + CreateSTStorage( + command_context=command_context, + area_id="foo", + parameters=PARAMETERS, + study_version=empty_study.config.version, + ) def test_apply_config__missing_area(self, recent_study: FileStudy, command_context: CommandContext): # Given a study without "unknown area" area @@ -223,7 +208,7 @@ def test_apply_config__missing_area(self, recent_study: FileStudy, command_conte create_st_storage = CreateSTStorage( command_context=command_context, area_id="unknown area", # bad ID - parameters=STStorageConfig(**PARAMETERS), + parameters=PARAMETERS, study_version=recent_study.config.version, ) command_output = create_st_storage.apply_config(recent_study.config) @@ -247,7 +232,7 @@ def test_apply_config__duplicate_storage(self, recent_study: FileStudy, command_ create_st_storage = CreateSTStorage( command_context=command_context, area_id=transform_name_to_id(create_area.area_name), - parameters=STStorageConfig(**PARAMETERS), + parameters=PARAMETERS, study_version=recent_study.config.version, ) command_output = create_st_storage.apply_config(recent_study.config) @@ -258,7 +243,7 @@ def test_apply_config__duplicate_storage(self, recent_study: FileStudy, command_ create_st_storage = CreateSTStorage( command_context=command_context, area_id=transform_name_to_id(create_area.area_name), - parameters=STStorageConfig(**parameters), + parameters=parameters, study_version=recent_study.config.version, ) command_output = create_st_storage.apply_config(recent_study.config) @@ -282,7 +267,7 @@ def test_apply_config__nominal_case(self, recent_study: FileStudy, command_conte create_st_storage = CreateSTStorage( command_context=command_context, area_id=transform_name_to_id(create_area.area_name), - parameters=STStorageConfig(**PARAMETERS), + parameters=PARAMETERS, study_version=recent_study.config.version, ) command_output = create_st_storage.apply_config(recent_study.config) @@ -309,7 +294,7 @@ def test_apply__nominal_case(self, recent_study: FileStudy, command_context: Com cmd = CreateSTStorage( command_context=command_context, area_id=transform_name_to_id(create_area.area_name), - parameters=STStorageConfig(**PARAMETERS), + parameters=PARAMETERS, pmax_injection=pmax_injection.tolist(), # type: ignore inflows=inflows.tolist(), # type: ignore study_version=recent_study.config.version, @@ -322,11 +307,11 @@ def test_apply__nominal_case(self, recent_study: FileStudy, command_context: Com expected = { "storage1": { "efficiency": 0.94, - "group": "Battery", + "group": "battery", "initiallevel": 0.5, "initialleveloptim": True, "injectionnominalcapacity": 1500, - "name": "Storage1", + "name": "storage1", "reservoircapacity": 20000, "withdrawalnominalcapacity": 1500, } @@ -350,29 +335,12 @@ def test_apply__nominal_case(self, recent_study: FileStudy, command_context: Com } assert config == expected - def test_apply__invalid_apply_config(self, empty_study: FileStudy, command_context: CommandContext): - # First, prepare a new Area - create_area = CreateArea( - area_name="Area FR", command_context=command_context, study_version=empty_study.config.version - ) - create_area.apply(empty_study) - - # Then, apply the command to create a new ST Storage - cmd = CreateSTStorage( - command_context=command_context, - area_id=transform_name_to_id(create_area.area_name), - parameters=STStorageConfig(**PARAMETERS), - study_version=empty_study.config.version, - ) - command_output = cmd.apply(empty_study) - assert not command_output.status # invalid study (too old) - # noinspection SpellCheckingInspection def test_to_dto(self, command_context: CommandContext): cmd = CreateSTStorage( command_context=command_context, area_id="area_fr", - parameters=STStorageConfig(**PARAMETERS), + parameters=PARAMETERS, study_version=STUDY_VERSION_8_8, ) @@ -381,6 +349,10 @@ def test_to_dto(self, command_context: CommandContext): expected_parameters = PARAMETERS.copy() # `initiallevel` = 0.5 (the default value) because `initialleveloptim` is True expected_parameters["initiallevel"] = 0.5 + expected_parameters["name"] = expected_parameters["name"].lower() + expected_parameters["group"] = expected_parameters["group"].lower() + # as we're using study version 8.8, we have the parameter `enabled` + expected_parameters["enabled"] = True constants = command_context.generator_matrix_constants assert actual == CommandDTO( @@ -401,7 +373,7 @@ def test_match_signature(self, command_context: CommandContext): cmd = CreateSTStorage( command_context=command_context, area_id="area_fr", - parameters=STStorageConfig(**PARAMETERS), + parameters=PARAMETERS, study_version=STUDY_VERSION_8_8, ) assert cmd.match_signature() == "create_st_storage%area_fr%storage1" @@ -417,16 +389,16 @@ def test_match( cmd1 = CreateSTStorage( command_context=command_context, area_id="area_fr", - parameters=STStorageConfig(**PARAMETERS), - study_version=STUDY_VERSION_8_8, + parameters=PARAMETERS, + study_version=STUDY_VERSION_8_6, ) cmd2 = CreateSTStorage( command_context=command_context, area_id=area_id, - parameters=STStorageConfig(**parameters), - study_version=STUDY_VERSION_8_8, + parameters=parameters, + study_version=STUDY_VERSION_8_6, ) - light_equal = area_id == cmd1.area_id and parameters["name"] == cmd1.storage_name + light_equal = area_id == cmd1.area_id and parameters["name"].lower() == cmd1.storage_name assert cmd1.match(cmd2, equal=False) == light_equal deep_equal = area_id == cmd1.area_id and parameters == PARAMETERS assert cmd1.match(cmd2, equal=True) == deep_equal @@ -435,7 +407,7 @@ def test_match__unknown_type(self, command_context: CommandContext): cmd1 = CreateSTStorage( command_context=command_context, area_id="area_fr", - parameters=STStorageConfig(**PARAMETERS), + parameters=PARAMETERS, study_version=STUDY_VERSION_8_8, ) # Always `False` when compared to another object type @@ -446,38 +418,41 @@ def test_create_diff__not_equals(self, command_context: CommandContext): cmd = CreateSTStorage( command_context=command_context, area_id="area_fr", - parameters=STStorageConfig(**PARAMETERS), - study_version=STUDY_VERSION_8_8, + parameters=PARAMETERS, + study_version=STUDY_VERSION_8_6, ) upper_rule_curve = GEN.random((8760, 1)) inflows = GEN.uniform(0, 1000, size=(8760, 1)) other = CreateSTStorage( command_context=command_context, area_id=cmd.area_id, - parameters=STStorageConfig(**OTHER_PARAMETERS), + parameters=OTHER_PARAMETERS, upper_rule_curve=upper_rule_curve.tolist(), # type: ignore inflows=inflows.tolist(), # type: ignore - study_version=STUDY_VERSION_8_8, + study_version=STUDY_VERSION_8_6, ) actual = cmd.create_diff(other) + expected_params = copy.deepcopy(OTHER_PARAMETERS) + expected_params["name"] = expected_params["name"].lower() + expected_params["group"] = expected_params["group"].lower() expected = [ ReplaceMatrix( command_context=command_context, target="input/st-storage/series/area_fr/storage1/upper_rule_curve", matrix=strip_matrix_protocol(other.upper_rule_curve), - study_version=STUDY_VERSION_8_8, + study_version=STUDY_VERSION_8_6, ), ReplaceMatrix( command_context=command_context, target="input/st-storage/series/area_fr/storage1/inflows", matrix=strip_matrix_protocol(other.inflows), - study_version=STUDY_VERSION_8_8, + study_version=STUDY_VERSION_8_6, ), UpdateConfig( command_context=command_context, target="input/st-storage/clusters/area_fr/list/storage1", - data=OTHER_PARAMETERS, - study_version=STUDY_VERSION_8_8, + data=expected_params, + study_version=STUDY_VERSION_8_6, ), ] assert actual == expected @@ -486,7 +461,7 @@ def test_create_diff__equals(self, command_context: CommandContext): cmd = CreateSTStorage( command_context=command_context, area_id="area_fr", - parameters=STStorageConfig(**PARAMETERS), + parameters=PARAMETERS, study_version=STUDY_VERSION_8_8, ) actual = cmd.create_diff(cmd) @@ -496,7 +471,7 @@ def test_get_inner_matrices(self, command_context: CommandContext): cmd = CreateSTStorage( command_context=command_context, area_id="area_fr", - parameters=STStorageConfig(**PARAMETERS), + parameters=PARAMETERS, study_version=STUDY_VERSION_8_8, ) actual = cmd.get_inner_matrices() diff --git a/tests/variantstudy/model/command/test_manage_binding_constraints.py b/tests/variantstudy/model/command/test_manage_binding_constraints.py index 1f3cb55971..fe3c3c697e 100644 --- a/tests/variantstudy/model/command/test_manage_binding_constraints.py +++ b/tests/variantstudy/model/command/test_manage_binding_constraints.py @@ -21,7 +21,7 @@ BindingConstraintFrequency, BindingConstraintOperator, ) -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.command_extractor import CommandExtractor from antarest.study.storage.variantstudy.business.command_reverter import CommandReverter diff --git a/tests/variantstudy/model/command/test_manage_district.py b/tests/variantstudy/model/command/test_manage_district.py index 532ec35c19..d67b2f8be7 100644 --- a/tests/variantstudy/model/command/test_manage_district.py +++ b/tests/variantstudy/model/command/test_manage_district.py @@ -13,8 +13,8 @@ from antarest.study.model import STUDY_VERSION_8_8 from antarest.study.storage.rawstudy.ini_reader import IniReader +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.config.files import build -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.command_reverter import CommandReverter from antarest.study.storage.variantstudy.model.command.create_area import CreateArea diff --git a/tests/variantstudy/model/command/test_remove_area.py b/tests/variantstudy/model/command/test_remove_area.py index b3de1c3342..7e016f88cd 100644 --- a/tests/variantstudy/model/command/test_remove_area.py +++ b/tests/variantstudy/model/command/test_remove_area.py @@ -18,7 +18,7 @@ BindingConstraintFrequency, BindingConstraintOperator, ) -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.model.command.create_area import CreateArea from antarest.study.storage.variantstudy.model.command.create_binding_constraint import CreateBindingConstraint diff --git a/tests/variantstudy/model/command/test_remove_cluster.py b/tests/variantstudy/model/command/test_remove_cluster.py index bf8db68e03..6cb8cf2be2 100644 --- a/tests/variantstudy/model/command/test_remove_cluster.py +++ b/tests/variantstudy/model/command/test_remove_cluster.py @@ -19,7 +19,7 @@ BindingConstraintFrequency, BindingConstraintOperator, ) -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.model.command.create_area import CreateArea from antarest.study.storage.variantstudy.model.command.create_binding_constraint import CreateBindingConstraint @@ -37,7 +37,7 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext) -> area_name = "Area_name" area_id = transform_name_to_id(area_name) cluster_name = "Cluster Name" - cluster_id = transform_name_to_id(cluster_name, lower=False) + cluster_id = transform_name_to_id(cluster_name) study_version = empty_study.config.version @@ -57,10 +57,10 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext) -> cluster_name=cluster_name, parameters={ "group": "group", - "unitcount": "unitcount", - "nominalcapacity": "nominalcapacity", - "marginal-cost": "marginal-cost", - "market-bid-cost": "market-bid-cost", + "unitcount": 4, + "nominalcapacity": 1.2, + "marginal-cost": 1.2, + "market-bid-cost": 1.2, }, command_context=command_context, prepro=[[0]], diff --git a/tests/variantstudy/model/command/test_remove_link.py b/tests/variantstudy/model/command/test_remove_link.py index 305d2f976f..4032c32384 100644 --- a/tests/variantstudy/model/command/test_remove_link.py +++ b/tests/variantstudy/model/command/test_remove_link.py @@ -22,8 +22,8 @@ from pydantic import ValidationError from antarest.study.model import STUDY_VERSION_8_8 +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.config.files import build -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.rawstudy.model.filesystem.root.filestudytree import FileStudyTree from antarest.study.storage.variantstudy.model.command.create_area import CreateArea @@ -98,7 +98,7 @@ def test_apply(self, tmpdir: Path, command_context: CommandContext, version: int study_version = empty_study.config.version # Create some areas - areas = {transform_name_to_id(area, lower=True): area for area in ["Area_X", "Area_Y", "Area_Z"]} + areas = {transform_name_to_id(area): area for area in ["Area_X", "Area_Y", "Area_Z"]} for area in areas.values(): output = CreateArea(area_name=area, command_context=command_context, study_version=study_version).apply( empty_study diff --git a/tests/variantstudy/model/command/test_remove_renewables_cluster.py b/tests/variantstudy/model/command/test_remove_renewables_cluster.py index a03308e5b3..26a4daade1 100644 --- a/tests/variantstudy/model/command/test_remove_renewables_cluster.py +++ b/tests/variantstudy/model/command/test_remove_renewables_cluster.py @@ -13,7 +13,8 @@ from checksumdir import dirhash from antarest.study.model import STUDY_VERSION_8_8 -from antarest.study.storage.rawstudy.model.filesystem.config.model import EnrModelling, transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.model import EnrModelling from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.model.command.create_area import CreateArea from antarest.study.storage.variantstudy.model.command.create_renewables_cluster import CreateRenewablesCluster @@ -32,7 +33,7 @@ def test_apply(self, empty_study: FileStudy, command_context: CommandContext) -> area_name = "Area_name" area_id = transform_name_to_id(area_name) cluster_name = "Cluster Name" - cluster_id = transform_name_to_id(cluster_name, lower=False) + cluster_id = transform_name_to_id(cluster_name) output = CreateArea(area_name=area_name, command_context=command_context, study_version=study_version).apply( empty_study diff --git a/tests/variantstudy/model/command/test_remove_st_storage.py b/tests/variantstudy/model/command/test_remove_st_storage.py index e58db6db55..63b3d76c3b 100644 --- a/tests/variantstudy/model/command/test_remove_st_storage.py +++ b/tests/variantstudy/model/command/test_remove_st_storage.py @@ -16,7 +16,7 @@ from pydantic import ValidationError from antarest.study.model import STUDY_VERSION_8_8 -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.study_upgrader import StudyUpgrader from antarest.study.storage.variantstudy.model.command.common import CommandName @@ -72,7 +72,7 @@ def test_init(self, command_context: CommandContext): assert cmd.area_id == "area_fr" assert cmd.storage_id == "storage_1" - def test_init__invalid_storage_id(self, recent_study: FileStudy, command_context: CommandContext): + def test_init__invalid_storage_id(self, command_context: CommandContext): # When we apply the config for a new ST Storage with a bad name with pytest.raises(ValidationError) as ctx: RemoveSTStorage( @@ -81,16 +81,11 @@ def test_init__invalid_storage_id(self, recent_study: FileStudy, command_context storage_id="?%$$", # bad name study_version=STUDY_VERSION_8_8, ) - assert ctx.value.errors() == [ - { - "ctx": {"pattern": "[a-z0-9_(),& -]+"}, - "input": "?%$$", - "loc": ("storage_id",), - "msg": "String should match pattern '[a-z0-9_(),& -]+'", - "type": "string_pattern_mismatch", - "url": "https://errors.pydantic.dev/2.8/v/string_pattern_mismatch", - } - ] + assert len(ctx.value.errors()) == 1 + error = ctx.value.errors()[0] + assert error["type"] == "string_pattern_mismatch" + assert error["loc"] == ("storage_id",) + assert error["msg"] == "String should match pattern '[a-z0-9_(),& -]+'" def test_apply_config__invalid_version(self, empty_study: FileStudy, command_context: CommandContext): # Given an old study in version 720 diff --git a/tests/variantstudy/model/command/test_replace_matrix.py b/tests/variantstudy/model/command/test_replace_matrix.py index 95c21eac94..5e38031b40 100644 --- a/tests/variantstudy/model/command/test_replace_matrix.py +++ b/tests/variantstudy/model/command/test_replace_matrix.py @@ -15,7 +15,7 @@ import numpy as np from antarest.study.model import STUDY_VERSION_8_8 -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.command_reverter import CommandReverter from antarest.study.storage.variantstudy.model.command.create_area import CreateArea diff --git a/tests/variantstudy/model/command/test_update_config.py b/tests/variantstudy/model/command/test_update_config.py index ceaff693c2..b4bd739b63 100644 --- a/tests/variantstudy/model/command/test_update_config.py +++ b/tests/variantstudy/model/command/test_update_config.py @@ -18,7 +18,7 @@ from antarest.core.exceptions import ChildNotFoundError from antarest.study.model import STUDY_VERSION_8_8 from antarest.study.storage.rawstudy.ini_reader import IniReader -from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id +from antarest.study.storage.rawstudy.model.filesystem.config.field_validators import transform_name_to_id from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.command_reverter import CommandReverter from antarest.study.storage.variantstudy.model.command.create_area import CreateArea diff --git a/tests/variantstudy/test_command_factory.py b/tests/variantstudy/test_command_factory.py index e031d07e1e..7efdddd4e5 100644 --- a/tests/variantstudy/test_command_factory.py +++ b/tests/variantstudy/test_command_factory.py @@ -19,7 +19,7 @@ import pytest from antarest.matrixstore.service import MatrixService -from antarest.study.model import STUDY_VERSION_8_8 +from antarest.study.model import STUDY_VERSION_8_2, STUDY_VERSION_8_6, STUDY_VERSION_8_8 from antarest.study.storage.patch_service import PatchService from antarest.study.storage.variantstudy.business.matrix_constants_generator import GeneratorMatrixConstants from antarest.study.storage.variantstudy.command_factory import CommandFactory @@ -156,26 +156,6 @@ study_version=STUDY_VERSION_8_8, ), CommandDTO(action=CommandName.REMOVE_BINDING_CONSTRAINT.value, args={"id": "id"}, study_version=STUDY_VERSION_8_8), - CommandDTO( - action=CommandName.REMOVE_BINDING_CONSTRAINT.value, args=[{"id": "id"}], study_version=STUDY_VERSION_8_8 - ), - CommandDTO( - action=CommandName.CREATE_THERMAL_CLUSTER.value, - args={ - "area_id": "area_name", - "cluster_name": "cluster_name", - "parameters": { - "group": "group", - "unitcount": "unitcount", - "nominalcapacity": "nominalcapacity", - "marginal-cost": "marginal-cost", - "market-bid-cost": "market-bid-cost", - }, - "prepro": "prepro", - "modulation": "modulation", - }, - study_version=STUDY_VERSION_8_8, - ), CommandDTO( action=CommandName.CREATE_THERMAL_CLUSTER.value, args=[ @@ -183,17 +163,33 @@ "area_id": "area_name", "cluster_name": "cluster_name", "parameters": { - "group": "group", - "unitcount": "unitcount", - "nominalcapacity": "nominalcapacity", - "marginal-cost": "marginal-cost", - "market-bid-cost": "market-bid-cost", + "co2": 0.0, + "enabled": True, + "fixed-cost": 0.0, + "gen-ts": "use global", + "group": "other 1", + "law.forced": "uniform", + "law.planned": "uniform", + "marginal-cost": 2.0, + "market-bid-cost": 2.0, + "min-down-time": 1, + "min-stable-power": 0.0, + "min-up-time": 1, + "must-run": False, + "name": "cluster_name", + "nominalcapacity": 2.0, + "spinning": 0.0, + "spread-cost": 0.0, + "startup-cost": 0.0, + "unitcount": 2, + "volatility.forced": 0.0, + "volatility.planned": 0.0, }, "prepro": "prepro", "modulation": "modulation", } ], - study_version=STUDY_VERSION_8_8, + study_version=STUDY_VERSION_8_2, ), CommandDTO( action=CommandName.REMOVE_THERMAL_CLUSTER.value, @@ -211,26 +207,16 @@ "area_id": "area_name", "cluster_name": "cluster_name", "parameters": { + "enabled": True, + "group": "other res 1", "name": "name", + "nominalcapacity": 0.0, "ts-interpretation": "power-generation", + "unitcount": 1, }, }, study_version=STUDY_VERSION_8_8, ), - CommandDTO( - action=CommandName.CREATE_RENEWABLES_CLUSTER.value, - args=[ - { - "area_id": "area_name", - "cluster_name": "cluster_name", - "parameters": { - "name": "name", - "ts-interpretation": "power-generation", - }, - } - ], - study_version=STUDY_VERSION_8_8, - ), CommandDTO( action=CommandName.REMOVE_RENEWABLES_CLUSTER.value, args={"area_id": "area_name", "cluster_id": "cluster_name"}, @@ -314,8 +300,8 @@ args={ "area_id": "area 1", "parameters": { - "name": "Storage 1", - "group": "Battery", + "name": "storage 1", + "group": "battery", "injectionnominalcapacity": 0, "withdrawalnominalcapacity": 0, "reservoircapacity": 0, @@ -329,7 +315,7 @@ "upper_rule_curve": "matrix://8ce614c8-c687-41af-8b24-df8a49cc52af", "inflows": "matrix://df9b25e1-e3f7-4a57-8182-0ff9791439e5", }, - study_version=STUDY_VERSION_8_8, + study_version=STUDY_VERSION_8_6, ), CommandDTO( action=CommandName.CREATE_ST_STORAGE.value, @@ -338,11 +324,11 @@ "area_id": "area 1", "parameters": { "efficiency": 1, - "group": "Battery", + "group": "battery", "initiallevel": 0, "initialleveloptim": False, "injectionnominalcapacity": 0, - "name": "Storage 1", + "name": "storage 1", "reservoircapacity": 0, "withdrawalnominalcapacity": 0, }, @@ -356,11 +342,11 @@ "area_id": "area 1", "parameters": { "efficiency": 0.94, - "group": "Battery", + "group": "battery", "initiallevel": 0, "initialleveloptim": False, "injectionnominalcapacity": 0, - "name": "Storage 2", + "name": "storage 2", "reservoircapacity": 0, "withdrawalnominalcapacity": 0, }, @@ -371,7 +357,7 @@ "inflows": "matrix://e8923768-9bdd-40c2-a6ea-2da2523be727", }, ], - study_version=STUDY_VERSION_8_8, + study_version=STUDY_VERSION_8_6, ), CommandDTO( action=CommandName.REMOVE_ST_STORAGE.value,