diff --git a/src/antares/api_conf/request_wrapper.py b/src/antares/api_conf/request_wrapper.py index cbf6d7f5..0f86f41d 100644 --- a/src/antares/api_conf/request_wrapper.py +++ b/src/antares/api_conf/request_wrapper.py @@ -11,6 +11,7 @@ # This file is part of the Antares project. import json + from typing import Any, Dict, Optional, Union import requests diff --git a/src/antares/model/binding_constraint.py b/src/antares/model/binding_constraint.py index 2b55d406..e5572f22 100644 --- a/src/antares/model/binding_constraint.py +++ b/src/antares/model/binding_constraint.py @@ -14,6 +14,7 @@ from typing import Any, Dict, List, Optional, Union import pandas as pd + from pydantic import BaseModel, Field, model_validator from pydantic.alias_generators import to_camel diff --git a/src/antares/model/study.py b/src/antares/model/study.py index a041c3c8..0335c8fd 100644 --- a/src/antares/model/study.py +++ b/src/antares/model/study.py @@ -34,7 +34,7 @@ from antares.model.settings import StudySettings from antares.service.api_services.study_api import _returns_study_settings from antares.service.service_factory import ServiceFactory, ServiceReader -from antares.tools.ini_tool import IniFile +from antares.tools.ini_tool import IniFile, IniFileTypes """ The study module defines the data model for antares study. @@ -45,17 +45,20 @@ """ +def verify_study_already_exists(study_directory: Path) -> None: + if os.path.exists(study_directory): + raise FileExistsError(f"Failed to create study. Study {study_directory} already exists") + + def create_study_api( study_name: str, version: str, api_config: APIconf, settings: Optional[StudySettings] = None ) -> "Study": """ Args: - study_name: antares study name to be created version: antares version api_config: host and token config for API settings: study settings. If not provided, AntaresWeb will use its default values. - Raises: MissingTokenError if api_token is missing StudyCreationError if an HTTP Exception occurs @@ -77,21 +80,6 @@ def create_study_api( return Study(study_name, version, ServiceFactory(api_config, study_id), study_settings) -def _verify_study_already_exists(study_directory: Path) -> None: - if os.path.exists(study_directory): - raise FileExistsError(f"Study {study_directory} already exists.") - - -def _directories_can_be_read(local_path: Path) -> None: - if local_path.is_dir(): - try: - for item in local_path.iterdir(): - if item.is_dir(): - next(item.iterdir()) - except PermissionError: - raise PermissionError(f"Some content cannot be accessed in {local_path}") - - def create_study_local( study_name: str, version: str, local_config: LocalConfiguration, settings: Optional[StudySettings] = None ) -> "Study": @@ -102,25 +90,55 @@ def create_study_local( version: antares version for study local_config: Local configuration options for example directory in which to story the study settings: study settings. If not provided, AntaresWeb will use its default values. - Raises: FileExistsError if the study already exists in the given location - ValueError if the provided directory does not exist - """ - def _directory_not_exists(local_path: Path) -> None: - if local_path is None or not os.path.exists(local_path): - raise ValueError(f"Provided directory {local_path} does not exist.") - - _directory_not_exists(local_config.local_path) + def _create_directory_structure(study_path: Path) -> None: + subdirectories = [ + "input/hydro/allocation", + "input/hydro/common/capacity", + "input/hydro/series", + "input/links", + "input/load/series", + "input/misc-gen", + "input/reserves", + "input/solar/series", + "input/thermal/clusters", + "input/thermal/prepro", + "input/thermal/series", + "input/wind/series", + "layers", + "output", + "settings/resources", + "settings/simulations", + "user", + ] + for subdirectory in subdirectories: + (study_path / subdirectory).mkdir(parents=True, exist_ok=True) + + def _correlation_defaults() -> dict[str, dict[str, str]]: + return { + "general": {"mode": "annual"}, + "annual": {}, + "0": {}, + "1": {}, + "2": {}, + "3": {}, + "4": {}, + "5": {}, + "6": {}, + "7": {}, + "8": {}, + "9": {}, + "10": {}, + "11": {}, + } study_directory = local_config.local_path / study_name - - _verify_study_already_exists(study_directory) - - # Create the main study directory - os.makedirs(study_directory, exist_ok=True) + verify_study_already_exists(study_directory) + # Create the directory structure + _create_directory_structure(study_directory) # Create study.antares file with timestamps and study_name antares_file_path = os.path.join(study_directory, "study.antares") @@ -145,11 +163,18 @@ def _directory_not_exists(local_path: Path) -> None: with open(desktop_ini_path, "w") as desktop_ini_file: desktop_ini_file.write(desktop_ini_content) - # Create subdirectories - subdirectories = ["input", "layers", "output", "setting", "user"] - for subdirectory in subdirectories: - subdirectory_path = os.path.join(study_directory, subdirectory) - os.makedirs(subdirectory_path, exist_ok=True) + # Create various .ini files for the study + correlation_inis_to_create = [ + ("solar_correlation", IniFileTypes.SOLAR_CORRELATION_INI), + ("wind_correlation", IniFileTypes.WIND_CORRELATION_INI), + ("load_correlation", IniFileTypes.LOAD_CORRELATION_INI), + ] + ini_files = { + correlation: IniFile(study_directory, file_type, ini_contents=_correlation_defaults()) + for (correlation, file_type) in correlation_inis_to_create + } + for ini_file in ini_files.keys(): + ini_files[ini_file].write_ini_file() logging.info(f"Study successfully created: {study_name}") return Study( @@ -157,6 +182,7 @@ def _directory_not_exists(local_path: Path) -> None: version=version, service_factory=ServiceFactory(config=local_config, study_name=study_name), settings=settings, + ini_files=ini_files, ) @@ -174,6 +200,15 @@ def read_study_local(study_name: str, version: str, local_config: LocalConfigura """ + def _directories_can_be_read(local_path: Path) -> None: + if local_path.is_dir(): + try: + for item in local_path.iterdir(): + if item.is_dir(): + next(item.iterdir()) + except PermissionError: + raise PermissionError(f"Some content cannot be accessed in {local_path}") + def _directory_not_exists(local_path: Path) -> None: if local_path is None or not os.path.exists(local_path): raise ValueError(f"Provided directory {local_path} does not exist.") @@ -195,7 +230,7 @@ def __init__( version: str, service_factory: Union[ServiceFactory, ServiceReader], settings: Optional[StudySettings] = None, - ini_files: Optional[dict[str, IniFile]] = None, + # ini_files: Optional[dict[str, IniFile]] = None, **kwargs: Any, ): self.name = name diff --git a/tests/antares/services/local_services/test_study.py b/tests/antares/services/local_services/test_study.py index 2fe1d73a..2ad9cc2f 100644 --- a/tests/antares/services/local_services/test_study.py +++ b/tests/antares/services/local_services/test_study.py @@ -14,6 +14,7 @@ import logging import os +import re import time from configparser import ConfigParser @@ -130,13 +131,17 @@ def test_verify_study_already_exists_error(self, monkeypatch, tmp_path, caplog): def mock_verify_study_already_exists(study_directory): raise FileExistsError(f"Failed to create study. Study {study_directory} already exists") - monkeypatch.setattr("antares.model.study._verify_study_already_exists", mock_verify_study_already_exists) + monkeypatch.setattr("antares.model.study.verify_study_already_exists", mock_verify_study_already_exists) + + current_dir = Path(tmp_path) + relative_path = Path(f"{study_name}") + full_path = re.escape(str(current_dir / relative_path)) + + expected_message = f"Failed to create study. Study {full_path} already exists" # When with caplog.at_level(logging.ERROR): - with pytest.raises( - FileExistsError, match=f"Failed to create study. Study {tmp_path}/{study_name} already exists" - ): + with pytest.raises(FileExistsError, match=expected_message): create_study_local(study_name, version, LocalConfiguration(tmp_path, study_name)) def test_solar_correlation_ini_exists(self, local_study_with_hydro): diff --git a/tests/antares/services/local_services/test_study_read.py b/tests/antares/services/local_services/test_study_read.py index e5c83ab1..d70b63bd 100644 --- a/tests/antares/services/local_services/test_study_read.py +++ b/tests/antares/services/local_services/test_study_read.py @@ -13,7 +13,9 @@ import pytest import logging +import re +from pathlib import Path from unittest import mock from antares.config.local_configuration import LocalConfiguration @@ -22,12 +24,16 @@ class TestReadStudy: def test_directory_not_exists_error(self, caplog): - local_path = r"fake/path/" study_name = "study_name" + + current_dir = Path.cwd() + relative_path = Path("fake/path/") + full_path = current_dir / relative_path + escaped_full_path = re.escape(str(full_path)) + with caplog.at_level(logging.ERROR): - regex_pattern = r"Provided directory fake\\path does not exist\." - with pytest.raises(ValueError, match=regex_pattern): - read_study_local(study_name, "880", LocalConfiguration(local_path, study_name)) + with pytest.raises(ValueError, match=escaped_full_path): + read_study_local(study_name, "880", LocalConfiguration(full_path, study_name)) def test_directory_permission_denied(self, tmp_path, caplog): # Given @@ -50,9 +56,13 @@ def test_directory_permission_denied(self, tmp_path, caplog): read_study_local(study_name, "880", LocalConfiguration(tmp_path, study_name)) def test_read_study_service(self, caplog): - local_path = r"../../studies_samples/" study_name = "hydro_stockage" - content = read_study_local(study_name, "880", LocalConfiguration(local_path, study_name)) + + current_dir = Path.cwd() + relative_path = Path("../../studies_samples/") + full_path = current_dir / relative_path + + content = read_study_local(study_name, "880", LocalConfiguration(full_path, study_name)) areas = content.service.read_areas() study = content.service.read_study(areas) @@ -67,10 +77,13 @@ def test_read_study_service(self, caplog): ), f"La clé '{not_expected_key}' ne devrait pas être présente dans le dictionnaire 'study'" def test_directory_renewable_thermique(self, caplog): - local_path = r"../../studies_samples/" study_name = "renewable_thermique" - content = read_study_local(study_name, "880", LocalConfiguration(local_path, study_name)) + current_dir = Path.cwd() + relative_path = Path("../../studies_samples/") + full_path = current_dir / relative_path + + content = read_study_local(study_name, "880", LocalConfiguration(full_path, study_name)) areas = content.service.read_areas() study = content.service.read_study(areas) assert study["thermals"].get("zone_rt").get("list") == { @@ -138,10 +151,13 @@ def test_directory_renewable_thermique(self, caplog): } def test_directory_hydro_stockage(self, caplog): - local_path = r"../../studies_samples/" study_name = "hydro_stockage" - content = read_study_local(study_name, "880", LocalConfiguration(local_path, study_name)) + current_dir = Path.cwd() + relative_path = Path("../../studies_samples/") + full_path = current_dir / relative_path + + content = read_study_local(study_name, "880", LocalConfiguration(full_path, study_name)) areas = content.service.read_areas() study = content.service.read_study(areas)