Skip to content

Commit

Permalink
feat(bc): used camelCase field names in binding constraints API
Browse files Browse the repository at this point in the history
  • Loading branch information
laurent-laporte-pro committed Mar 24, 2024
1 parent d5eecde commit 5142aa3
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 84 deletions.
70 changes: 39 additions & 31 deletions antarest/study/business/binding_constraint_management.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import collections
import itertools
import logging
from typing import Any, Dict, List, Mapping, Optional, Sequence, Union
from typing import Any, Dict, List, Mapping, MutableSequence, Optional, Sequence, Union

import numpy as np
from pydantic import BaseModel, root_validator, validator
from pydantic import BaseModel, Field, root_validator, validator
from requests.utils import CaseInsensitiveDict

from antarest.core.exceptions import (
Expand All @@ -19,7 +19,8 @@
MissingDataError,
NoConstraintError,
)
from antarest.study.business.utils import AllOptionalMetaclass, execute_or_add_commands
from antarest.core.utils.string import to_camel_case
from antarest.study.business.utils import AllOptionalMetaclass, camel_case_model, execute_or_add_commands
from antarest.study.model import Study
from antarest.study.storage.rawstudy.model.filesystem.config.binding_constraint import BindingConstraintFrequency
from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id
Expand Down Expand Up @@ -178,7 +179,7 @@ def accept(self, constraint: "BindingConstraintConfigType") -> bool:
return False

# Filter on terms
terms = constraint.constraints or []
terms = constraint.terms or []

if self.area_name:
all_areas = []
Expand Down Expand Up @@ -219,28 +220,28 @@ def accept(self, constraint: "BindingConstraintConfigType") -> bool:
return True


class BindingConstraintEditionModel(BaseModel, metaclass=AllOptionalMetaclass):
group: str
enabled: bool
time_step: BindingConstraintFrequency
operator: BindingConstraintOperator
filter_year_by_year: str
filter_synthesis: str
comments: str
@camel_case_model
class BindingConstraintEditionModel(BindingConstraintProperties870, metaclass=AllOptionalMetaclass, use_none=True):
coeffs: Dict[str, List[float]]


@camel_case_model
class BindingConstraintEdition(BindingConstraintMatrices, BindingConstraintEditionModel):
pass


@camel_case_model
class BindingConstraintCreation(BindingConstraintMatrices, BindingConstraintProperties870):
name: str
coeffs: Dict[str, List[float]]

# Ajout d'un root validator pour valider les dimensions des matrices
@root_validator(pre=True)
def check_matrices_dimensions(cls, values: Dict[str, Any]) -> Dict[str, Any]:
for _key in ["time_step", "less_term_matrix", "equal_term_matrix", "greater_term_matrix"]:
_camel = to_camel_case(_key)
values[_key] = values.pop(_camel, values.get(_key))

# The dimensions of the matrices depend on the frequency and the version of the study.
if values.get("time_step") is None:
return values
Expand Down Expand Up @@ -290,10 +291,18 @@ def check_matrices_dimensions(cls, values: Dict[str, Any]) -> Dict[str, Any]:
raise ValueError(err_msg)


class BindingConstraintConfig(BindingConstraintProperties):
@camel_case_model
class _BindingConstraintConfig(BindingConstraintProperties):
id: str
name: str
constraints: Optional[List[ConstraintTermDTO]]


class BindingConstraintConfig(_BindingConstraintConfig):
terms: MutableSequence[ConstraintTermDTO] = Field(
default_factory=lambda: [],
alias="constraints", # only for backport compatibility
title="Constraint terms",
)


class BindingConstraintConfig870(BindingConstraintConfig):
Expand Down Expand Up @@ -361,9 +370,9 @@ def parse_constraint(key: str, value: str, char: str, new_config: BindingConstra
if len(weight_and_offset) == 2:
weight = float(weight_and_offset[0])
offset = float(weight_and_offset[1])
if new_config.constraints is None:
new_config.constraints = []
new_config.constraints.append(
if new_config.terms is None:
new_config.terms = []
new_config.terms.append(
ConstraintTermDTO(
id=key,
weight=weight,
Expand All @@ -387,18 +396,17 @@ def process_constraint(constraint_value: Dict[str, Any], version: int) -> Bindin
args = {
"id": constraint_value["id"],
"name": constraint_value["name"],
"enabled": constraint_value["enabled"],
"time_step": constraint_value["type"],
"operator": constraint_value["operator"],
"comments": constraint_value.get("comments", None),
"enabled": constraint_value.get("enabled", True),
"time_step": constraint_value.get("type", BindingConstraintFrequency.HOURLY),
"operator": constraint_value.get("operator", BindingConstraintOperator.EQUAL),
"comments": constraint_value.get("comments", ""),
"filter_year_by_year": constraint_value.get("filter-year-by-year", ""),
"filter_synthesis": constraint_value.get("filter-synthesis", ""),
"constraints": None,
}
if version < 870:
new_config: BindingConstraintConfigType = BindingConstraintConfig(**args)
else:
args["group"] = constraint_value.get("group")
args["group"] = constraint_value.get("group", DEFAULT_GROUP)
new_config = BindingConstraintConfig870(**args)

for key, value in constraint_value.items():
Expand All @@ -413,8 +421,8 @@ def constraints_to_coeffs(
constraint: BindingConstraintConfigType,
) -> Dict[str, List[float]]:
coeffs: Dict[str, List[float]] = {}
if constraint.constraints is not None:
for term in constraint.constraints:
if constraint.terms is not None:
for term in constraint.terms:
if term.id is not None and term.weight is not None:
coeffs[term.id] = [term.weight]
if term.offset is not None:
Expand Down Expand Up @@ -640,7 +648,7 @@ def update_constraint_term(
if not isinstance(constraint, BindingConstraintConfig) and not isinstance(constraint, BindingConstraintConfig):
raise BindingConstraintNotFoundError(study.id)

constraint_terms = constraint.constraints # existing constraint terms
constraint_terms = constraint.terms # existing constraint terms
if constraint_terms is None:
raise NoConstraintError(study.id)

Expand Down Expand Up @@ -695,11 +703,11 @@ def add_new_constraint_term(
raise MissingDataError("Add new constraint term : data is missing")

constraint_id = constraint_term.data.generate_id()
constraints_term = constraint.constraints or []
if find_constraint_term_id(constraints_term, constraint_id) >= 0:
constraint_terms = constraint.terms or []
if find_constraint_term_id(constraint_terms, constraint_id) >= 0:
raise ConstraintAlreadyExistError(study.id)

constraints_term.append(
constraint_terms.append(
ConstraintTermDTO(
id=constraint_id,
weight=constraint_term.weight if constraint_term.weight is not None else 0.0,
Expand All @@ -708,7 +716,7 @@ def add_new_constraint_term(
)
)
coeffs = {}
for term in constraints_term:
for term in constraint_terms:
coeffs[term.id] = [term.weight]
if term.offset is not None:
coeffs[term.id].append(term.offset)
Expand Down Expand Up @@ -759,7 +767,7 @@ def _replace_matrices_according_to_frequency_and_version(
return args


def find_constraint_term_id(constraints_term: List[ConstraintTermDTO], constraint_term_id: str) -> int:
def find_constraint_term_id(constraints_term: Sequence[ConstraintTermDTO], constraint_term_id: str) -> int:
try:
index = [elm.id for elm in constraints_term].index(constraint_term_id)
return index
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ def check_matrix_values(time_step: BindingConstraintFrequency, values: MatrixTyp
raise ValueError("Matrix values cannot contain NaN")


class BindingConstraintProperties(BaseModel, extra=Extra.forbid):
class BindingConstraintProperties(BaseModel, extra=Extra.forbid, allow_population_by_field_name=True):
enabled: bool = True
time_step: BindingConstraintFrequency
operator: BindingConstraintOperator
filter_year_by_year: t.Optional[str] = None
filter_synthesis: t.Optional[str] = None
comments: t.Optional[str] = None
time_step: BindingConstraintFrequency = BindingConstraintFrequency.HOURLY
operator: BindingConstraintOperator = BindingConstraintOperator.EQUAL
comments: str = ""
filter_year_by_year: str = ""
filter_synthesis: str = ""


class BindingConstraintProperties870(BindingConstraintProperties):
Expand Down
23 changes: 22 additions & 1 deletion antarest/study/web/study_data_blueprint.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import enum
import logging
import warnings
from http import HTTPStatus
from typing import Any, Dict, List, Mapping, Optional, Sequence, Union, cast

import typing_extensions as te
from fastapi import APIRouter, Body, Depends, Query
from starlette.responses import RedirectResponse

Expand Down Expand Up @@ -68,6 +70,13 @@
logger = logging.getLogger(__name__)


class BCKeyValueType(te.TypedDict):
"""Deprecated type for binding constraint key-value pair (used for update)"""

key: str
value: Union[str, int, float, bool]


class ClusterType(str, enum.Enum):
"""
Cluster type:
Expand Down Expand Up @@ -972,7 +981,7 @@ def get_binding_constraint(
def update_binding_constraint(
uuid: str,
binding_constraint_id: str,
data: BindingConstraintEdition,
data: Union[BCKeyValueType, BindingConstraintEdition],
current_user: JWTUser = Depends(auth.get_current_user),
) -> BindingConstraintConfigType:
logger.info(
Expand All @@ -981,6 +990,18 @@ def update_binding_constraint(
)
params = RequestParameters(user=current_user)
study = study_service.check_study_access(uuid, StudyPermissionType.WRITE, params)

if isinstance(data, dict):
warnings.warn(
"Using key / value format for binding constraint data is deprecated."
" Please use the BindingConstraintEdition format instead.",
DeprecationWarning,
)
_obj = {data["key"]: data["value"]}
if "filterByYear" in _obj:
_obj["filterYearByYear"] = _obj.pop("filterByYear")
data = BindingConstraintEdition(**_obj)

return study_service.binding_constraint_manager.update_binding_constraint(study, binding_constraint_id, data)

@bp.get(
Expand Down
Loading

0 comments on commit 5142aa3

Please sign in to comment.