diff --git a/src/antares/exceptions/exceptions.py b/src/antares/exceptions/exceptions.py index 7ce1446a..460e9935 100644 --- a/src/antares/exceptions/exceptions.py +++ b/src/antares/exceptions/exceptions.py @@ -54,6 +54,15 @@ def __init__(self, area_name: str, message: str) -> None: super().__init__(self.message) +class InvalidNameError(Exception): + def __init__(self, name: str) -> None: + self.message = ( + f"The name {name} contains one or more unauthorized characters." + + "\nNames can only contain: a-z, A-Z, 0-9, (, ), &, _, - and , (comma)." + ) + super().__init__(self.message) + + class LinkCreationError(Exception): def __init__(self, area_from: str, area_to: str, message: str) -> None: self.message = f"Could not create the link {area_from} / {area_to}: " + message diff --git a/src/antares/model/study.py b/src/antares/model/study.py index c6a973fc..5e19521f 100644 --- a/src/antares/model/study.py +++ b/src/antares/model/study.py @@ -32,6 +32,7 @@ from antares.service.api_services.study_api import _returns_study_settings from antares.service.base_services import BaseStudyService from antares.service.service_factory import ServiceFactory +from antares.tools.contents_tool import check_if_name_is_valid from antares.tools.ini_tool import IniFile, IniFileTypes """ @@ -92,6 +93,8 @@ def create_study_local( Raises: FileExistsError if the study already exists in the given location """ + check_if_name_is_valid(study_name) + local_config = LocalConfiguration(Path(parent_directory), study_name) study_directory = local_config.local_path / study_name diff --git a/src/antares/service/local_services/area_local.py b/src/antares/service/local_services/area_local.py index 8b8bc388..42edb5ea 100644 --- a/src/antares/service/local_services/area_local.py +++ b/src/antares/service/local_services/area_local.py @@ -12,7 +12,6 @@ import logging import os -import re from configparser import ConfigParser, DuplicateSectionError from typing import Any, Dict, List, Optional @@ -32,7 +31,7 @@ BaseShortTermStorageService, BaseThermalService, ) -from antares.tools.contents_tool import transform_name_to_id +from antares.tools.contents_tool import check_if_name_is_valid, transform_name_to_id from antares.tools.ini_tool import IniFile, IniFileTypes from antares.tools.matrix_tool import read_timeseries from antares.tools.prepro_folder import PreproFolder @@ -77,6 +76,8 @@ def create_thermal_cluster( thermal_name: str, properties: Optional[ThermalClusterProperties] = None, ) -> ThermalCluster: + check_if_name_is_valid(thermal_name) + properties = properties or ThermalClusterProperties() args = {"thermal_name": thermal_name, **properties.model_dump(mode="json", exclude_none=True)} local_thermal_properties = ThermalClusterPropertiesLocal.model_validate(args) @@ -116,6 +117,8 @@ def create_renewable_cluster( properties: Optional[RenewableClusterProperties] = None, series: Optional[pd.DataFrame] = None, ) -> RenewableCluster: + check_if_name_is_valid(renewable_name) + properties = properties or RenewableClusterProperties() args = {"renewable_name": renewable_name, **properties.model_dump(mode="json", exclude_none=True)} local_properties = RenewableClusterPropertiesLocal.model_validate(args) @@ -139,6 +142,8 @@ def _write_timeseries(self, series: pd.DataFrame, ts_file_type: TimeSeriesFileTy def create_st_storage( self, area_id: str, st_storage_name: str, properties: Optional[STStorageProperties] = None ) -> STStorage: + check_if_name_is_valid(st_storage_name) + properties = properties or STStorageProperties() args = {"st_storage_name": st_storage_name, **properties.model_dump(mode="json", exclude_none=True)} local_st_storage_properties = STStoragePropertiesLocal.model_validate(args) @@ -202,12 +207,7 @@ def create_area( Returns: area name if success or Error if area can not be created """ - unauthorized_characters = r"[^a-zA-Z0-9\-_()& ,]+" - if re.search(unauthorized_characters, area_name): - raise AreaCreationError( - area_name=area_name, - message=f"The name {area_name} contains one or more unauthorized characters.\nArea names can only contain: a-z, A-Z, 0-9, (, ), &, _, - and , (comma).", - ) + check_if_name_is_valid(area_name) def _line_exists_in_file(file_content: str, line_to_add: str) -> bool: """ diff --git a/src/antares/service/local_services/binding_constraint_local.py b/src/antares/service/local_services/binding_constraint_local.py index 96556e3e..106090fd 100644 --- a/src/antares/service/local_services/binding_constraint_local.py +++ b/src/antares/service/local_services/binding_constraint_local.py @@ -26,6 +26,7 @@ ConstraintTerm, ) from antares.service.base_services import BaseBindingConstraintService +from antares.tools.contents_tool import check_if_name_is_valid from antares.tools.ini_tool import IniFile, IniFileTypes from antares.tools.matrix_tool import df_save from antares.tools.time_series_tool import TimeSeriesFileType @@ -47,6 +48,8 @@ def create_binding_constraint( equal_term_matrix: Optional[pd.DataFrame] = None, greater_term_matrix: Optional[pd.DataFrame] = None, ) -> BindingConstraint: + check_if_name_is_valid(name) + constraint = BindingConstraint( name=name, binding_constraint_service=self, diff --git a/src/antares/tools/contents_tool.py b/src/antares/tools/contents_tool.py index 1d9a25b3..f89013a5 100644 --- a/src/antares/tools/contents_tool.py +++ b/src/antares/tools/contents_tool.py @@ -19,6 +19,7 @@ from pydantic import BaseModel +from antares.exceptions.exceptions import InvalidNameError from antares.tools.custom_raw_config_parser import CustomRawConfigParser # Invalid chars was taken from Antares Simulator (C++). @@ -124,3 +125,9 @@ def sort_ini_sections(ini_to_sort: CustomRawConfigParser) -> CustomRawConfigPars for section in sorted(ini_to_sort.sections()): sorted_ini[section] = ini_to_sort[section] return sorted_ini + + +def check_if_name_is_valid(name: str) -> None: + unauthorized_characters = r"[^a-zA-Z0-9\-_()& ,]+" + if re.search(unauthorized_characters, name): + raise InvalidNameError(name) diff --git a/tests/integration/test_local_client.py b/tests/integration/test_local_client.py index 31049923..7879e6ad 100644 --- a/tests/integration/test_local_client.py +++ b/tests/integration/test_local_client.py @@ -17,7 +17,7 @@ import pandas as pd from antares import create_study_local -from antares.exceptions.exceptions import AreaCreationError, LinkCreationError +from antares.exceptions.exceptions import AreaCreationError, InvalidNameError, LinkCreationError from antares.model.area import AdequacyPatchMode, Area, AreaProperties, AreaUi from antares.model.commons import FilterOption from antares.model.link import Link, LinkProperties, LinkUi @@ -105,12 +105,11 @@ def test_local_study(self, tmp_path, other_area): # Forbidden character errors area_name = "BE?" with pytest.raises( - AreaCreationError, + InvalidNameError, match=( re.escape( - f"Could not create the area {area_name}: " - + f"The name {area_name} contains one or more unauthorized characters." - + "\nArea names can only contain: a-z, A-Z, 0-9, (, ), &, _, - and , (comma)." + f"The name {area_name} contains one or more unauthorized characters." + + "\nNames can only contain: a-z, A-Z, 0-9, (, ), &, _, - and , (comma)." ) ), ):