Skip to content

Commit

Permalink
fix(variant): fixed TS deletion of renewable clusters and short-term …
Browse files Browse the repository at this point in the history
…storage (#1693)
  • Loading branch information
laurent-laporte-pro authored Aug 9, 2023
1 parent 857dfc2 commit c3f9634
Show file tree
Hide file tree
Showing 9 changed files with 608 additions and 413 deletions.
4 changes: 1 addition & 3 deletions antarest/study/business/areas/renewable_management.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
from enum import Enum
from pathlib import PurePosixPath
from typing import Any, Dict, List, Optional

from pydantic import Field

from antarest.study.business.enum_ignore_case import EnumIgnoreCase
from antarest.study.business.utils import (
FieldInfo,
Expand All @@ -15,6 +12,7 @@
from antarest.study.storage.variantstudy.model.command.update_config import (
UpdateConfig,
)
from pydantic import Field


class TimeSeriesInterpretation(EnumIgnoreCase):
Expand Down
120 changes: 101 additions & 19 deletions antarest/study/storage/rawstudy/model/filesystem/ini_file_node.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import contextlib
import functools
import io
import logging
import os
import json
import tempfile
import zipfile
from json import JSONDecodeError
from pathlib import Path
from typing import List, Optional, cast, Dict, Any, Union
from typing import List, Optional, cast, Dict, Any, Union, Callable

from filelock import FileLock

Expand All @@ -27,6 +29,44 @@
)


class IniFileNodeWarning(UserWarning):
"""
Custom User Warning subclass for INI file-related warnings.
This warning class is designed to provide more informative warning messages for INI file errors.
Args:
config: The configuration associated with the INI file.
message: The specific warning message.
"""

def __init__(self, config: FileStudyTreeConfig, message: str) -> None:
relpath = config.path.relative_to(config.study_path).as_posix()
super().__init__(f"INI File error '{relpath}': {message}")


def log_warning(f: Callable[..., Any]) -> Callable[..., Any]:
"""
Decorator to suppress `UserWarning` exceptions by logging them as warnings.
Args:
f: The function or method to be decorated.
Returns:
Callable[..., Any]: The decorated function.
"""

@functools.wraps(f)
def wrapper(*args: Any, **kwargs: Any) -> Any:
try:
return f(*args, **kwargs)
except UserWarning as w:
# noinspection PyUnresolvedReferences
logging.getLogger(f.__module__).warning(str(w))

return wrapper


class IniFileNode(INode[SUB_JSON, SUB_JSON, JSON]):
def __init__(
self,
Expand Down Expand Up @@ -125,27 +165,69 @@ def save(self, data: SUB_JSON, url: Optional[List[str]] = None) -> None:
info = cast(JSON, obj)
self.writer.write(info, self.path)

@log_warning
def delete(self, url: Optional[List[str]] = None) -> None:
url = url or []
if len(url) == 0:
if self.config.path.exists():
self.config.path.unlink()
elif len(url) > 0:
data = self.reader.read(self.path) if self.path.exists() else {}
"""
Deletes the specified section or key from the INI file,
or the entire INI file if no URL is provided.
Args:
url: A list containing the URL components [section_name, key_name].
Raises:
IniFileNodeWarning:
If the specified section or key cannot be deleted due to errors such as
missing configuration file, non-resolved URL, or non-existent section/key.
"""
if not self.path.exists():
raise IniFileNodeWarning(
self.config,
"fCannot delete item {url!r}: Config file not found",
)

if not url:
self.config.path.unlink()
return

url_len = len(url)
if url_len > 2:
raise IniFileNodeWarning(
self.config,
f"Cannot delete item {url!r}: URL should be fully resolved",
)

data = self.reader.read(self.path)

if url_len == 1:
section_name = url[0]
if len(url) == 1:
with contextlib.suppress(KeyError):
del data[section_name]
elif len(url) == 2:
# remove dict key
key_name = url[1]
with contextlib.suppress(KeyError):
del data[section_name][key_name]
try:
del data[section_name]
except KeyError:
raise IniFileNodeWarning(
self.config,
f"Cannot delete section: Section [{section_name}] not found",
) from None

elif url_len == 2:
section_name, key_name = url
try:
section = data[section_name]
except KeyError:
raise IniFileNodeWarning(
self.config,
f"Cannot delete key: Section [{section_name}] not found",
) from None
else:
raise ValueError(
f"url should be fully resolved when arrives on {self.__class__.__name__}"
)
self.writer.write(data, self.path)
try:
del section[key_name]
except KeyError:
raise IniFileNodeWarning(
self.config,
f"Cannot delete key: Key '{key_name}'"
f" not found in section [{section_name}]",
) from None

self.writer.write(data, self.path)

def check_errors(
self,
Expand Down
46 changes: 27 additions & 19 deletions antarest/study/storage/variantstudy/model/command/create_area.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Dict, List, Optional, Tuple
from typing import Any, Dict, List, Tuple

from antarest.core.model import JSON
from antarest.study.common.default_values import (
Expand All @@ -9,6 +9,7 @@
Area,
FileStudyTreeConfig,
transform_name_to_id,
ENR_MODELLING,
)
from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy
from antarest.study.storage.variantstudy.model.command.common import (
Expand All @@ -22,6 +23,22 @@
from antarest.study.storage.variantstudy.model.model import CommandDTO


# noinspection SpellCheckingInspection
def _generate_new_thermal_areas_ini(
file_study: FileStudy,
area_id: str,
*,
unserverdenergycost: float,
spilledenergycost: float,
) -> JSON:
new_areas: JSON = file_study.tree.get(["input", "thermal", "areas"])
if unserverdenergycost is not None:
new_areas["unserverdenergycost"][area_id] = unserverdenergycost
if spilledenergycost is not None:
new_areas["spilledenergycost"][area_id] = spilledenergycost
return new_areas


class CreateArea(ICommand):
area_name: str

Expand All @@ -32,23 +49,6 @@ def __init__(self, **data: Any) -> None:
**data,
)

def _generate_new_thermal_areas_ini(
self,
file_study: FileStudy,
area_id: str,
unserverdenergycost: Optional[float] = None,
spilledenergycost: Optional[float] = None,
) -> JSON:
new_areas: JSON = file_study.tree.get(
url=["input", "thermal", "areas"]
)
if unserverdenergycost is not None:
new_areas["unserverdenergycost"][area_id] = unserverdenergycost
if spilledenergycost is not None:
new_areas["spilledenergycost"][area_id] = spilledenergycost

return new_areas

def _apply_config(
self, study_data: FileStudyTreeConfig
) -> Tuple[CommandOutput, Dict[str, Any]]:
Expand Down Expand Up @@ -198,7 +198,7 @@ def _apply(self, study_data: FileStudy) -> CommandOutput:
},
"thermal": {
"clusters": {area_id: {"list": {}}},
"areas": self._generate_new_thermal_areas_ini(
"areas": _generate_new_thermal_areas_ini(
study_data,
area_id,
unserverdenergycost=NodalOptimization.UNSERVERDDENERGYCOST,
Expand Down Expand Up @@ -247,6 +247,14 @@ def _apply(self, study_data: FileStudy) -> CommandOutput:
)
# fmt: on

if (
version >= 810
and study_data.config.enr_modelling == ENR_MODELLING.CLUSTERS.value
):
new_area_data["input"]["renewables"] = {
"clusters": {area_id: {"list": {}}},
}

if version >= 830:
new_area_data["input"]["areas"][area_id]["adequacy_patch"] = {
"adequacy-patch": {"adequacy-patch-mode": "outside"}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
from typing import Dict, List, Any, cast, Tuple

from pydantic import validator
from typing import Any, Dict, List, Tuple, cast

from antarest.core.model import JSON
from antarest.study.storage.rawstudy.model.filesystem.config.model import (
ENR_MODELLING,
Cluster,
transform_name_to_id,
FileStudyTreeConfig,
ENR_MODELLING,
transform_name_to_id,
)
from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy
from antarest.study.storage.variantstudy.model.command.common import (
CommandOutput,
CommandName,
CommandOutput,
)
from antarest.study.storage.variantstudy.model.command.icommand import (
ICommand,
MATCH_SIGNATURE_SEPARATOR,
ICommand,
)
from antarest.study.storage.variantstudy.model.model import CommandDTO
from pydantic import validator


class CreateRenewablesCluster(ICommand):
Expand Down Expand Up @@ -46,40 +45,31 @@ def _apply_config(
self, study_data: FileStudyTreeConfig
) -> Tuple[CommandOutput, Dict[str, Any]]:
if study_data.enr_modelling != ENR_MODELLING.CLUSTERS.value:
return (
CommandOutput(
status=False,
message=f"enr_modelling must be {ENR_MODELLING.CLUSTERS.value}",
),
dict(),
message = (
f"Parameter 'renewable-generation-modelling'"
f" must be set to '{ENR_MODELLING.CLUSTERS.value}'"
f" instead of '{study_data.enr_modelling}'"
)
return CommandOutput(status=False, message=message), {}

if self.area_id not in study_data.areas:
return (
CommandOutput(
status=False,
message=f"Area '{self.area_id}' does not exist",
),
dict(),
)
message = f"Area '{self.area_id}' does not exist"
return CommandOutput(status=False, message=message), {}

cluster_id = transform_name_to_id(self.cluster_name)
for cluster in study_data.areas[self.area_id].renewables:
if cluster.id == cluster_id:
return (
CommandOutput(
status=False,
message=f"Renewable cluster '{self.cluster_name}' already exist",
),
dict(),
message = (
f"Renewable cluster '{self.cluster_name}' already exist"
)
return CommandOutput(status=False, message=message), {}

study_data.areas[self.area_id].renewables.append(
Cluster(id=cluster_id, name=self.cluster_name)
)
message = f"Renewable cluster '{self.cluster_name}' added to area '{self.area_id}'"
return (
CommandOutput(
status=True,
message=f"Renewable cluster '{self.cluster_name}' added to area '{self.area_id}'",
),
CommandOutput(status=True, message=message),
{"cluster_id": cluster_id},
)

Expand Down
Loading

0 comments on commit c3f9634

Please sign in to comment.