Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

chore(lower-case): read and write cluster and bc groups in lower case #2283

Open
wants to merge 15 commits into
base: dev
Choose a base branch
from
Open
4 changes: 4 additions & 0 deletions antarest/core/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion antarest/launcher/extensions/adequacy_patch/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,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__)
Expand Down
3 changes: 2 additions & 1 deletion antarest/study/business/area_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 14 additions & 16 deletions antarest/study/business/areas/renewable_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down
14 changes: 5 additions & 9 deletions antarest/study/business/areas/st_storage_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
)
Expand Down Expand Up @@ -551,8 +551,7 @@ def duplicate_cluster(self, study: Study, area_id: str, source_id: str, new_clus
DuplicateSTStorage: 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
Expand All @@ -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
Expand Down
49 changes: 23 additions & 26 deletions antarest/study/business/areas/thermal_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -431,9 +431,8 @@ def duplicate_cluster(
Raises:
DuplicateThermalCluster: 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
Expand All @@ -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]
Expand All @@ -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")
Expand Down
6 changes: 3 additions & 3 deletions antarest/study/business/binding_constraint_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 (
Expand Down Expand Up @@ -343,7 +343,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
Expand Down
2 changes: 1 addition & 1 deletion antarest/study/business/district_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions antarest/study/business/table_mode_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,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"})
Expand All @@ -232,7 +234,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"})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

from pydantic import Field

from antarest.core.model import LowerCaseStr
from antarest.core.serialization import AntaresBaseModel


Expand All @@ -47,7 +48,7 @@ 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_(),& -]+")

Expand All @@ -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.lower()).__lt__((other.group, other.name.lower()))
return NotImplemented


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# SPDX-License-Identifier: MPL-2.0
#
# This file is part of the Antares project.

import re
import typing as t

_ALL_FILTERING = ["hourly", "daily", "weekly", "monthly", "annual"]
Expand Down Expand Up @@ -87,3 +87,21 @@ 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()
Loading
Loading