diff --git a/antarest/study/business/link_management.py b/antarest/study/business/link_management.py index 1f380c8110..40b8d92301 100644 --- a/antarest/study/business/link_management.py +++ b/antarest/study/business/link_management.py @@ -18,7 +18,7 @@ from antarest.core.exceptions import ConfigFileNotFound, LinkNotFound, LinkValidationError from antarest.core.model import JSON from antarest.study.business.all_optional_meta import all_optional_model, camel_case_model -from antarest.study.business.model.link_model import LinkDTO, LinkInternal +from antarest.study.business.model.link_model import LinkDTO, LinkDtoForUpdate, LinkInternal from antarest.study.business.utils import execute_or_add_commands from antarest.study.model import RawStudy, Study from antarest.study.storage.rawstudy.model.filesystem.config.links import LinkProperties @@ -79,17 +79,19 @@ def create_link(self, study: Study, link_creation_dto: LinkDTO) -> LinkDTO: return link_creation_dto - def update_link(self, study: RawStudy, link_dto: LinkDTO) -> LinkDTO: + def update_link(self, study: RawStudy, area_from: str, area_to: str, link_update_dto: LinkDtoForUpdate) -> LinkDTO: + link_dto = LinkDTO(area1=area_from, area2=area_to, **link_update_dto.model_dump()) + link = link_dto.to_internal(StudyVersion.parse(study.version)) file_study = self.storage_service.get_storage(study).get_raw(study) self.get_link_if_exists(file_study, link) command = UpdateLink( - area1=link.area1, - area2=link.area2, + area1=area_from, + area2=area_to, parameters=link.model_dump( - include=link_dto.model_fields_set, exclude={"area1", "area2"}, exclude_none=True + include=link_update_dto.model_fields_set, exclude={"area1", "area2"}, exclude_none=True ), command_context=self.storage_service.variant_study_service.command_factory.command_context, study_version=file_study.config.version, diff --git a/antarest/study/business/model/link_model.py b/antarest/study/business/model/link_model.py index 1b7a9c925d..a171631cba 100644 --- a/antarest/study/business/model/link_model.py +++ b/antarest/study/business/model/link_model.py @@ -13,6 +13,7 @@ from antares.study.version import StudyVersion from pydantic import ConfigDict, Field, model_validator +from pydantic.json_schema import SkipJsonSchema from antarest.core.exceptions import LinkValidationError from antarest.core.serialization import AntaresBaseModel @@ -51,7 +52,11 @@ def validate_areas(self) -> t.Self: class LinkDTO(Area): - model_config = ConfigDict(alias_generator=to_camel_case, populate_by_name=True, extra="forbid") + model_config = ConfigDict( + alias_generator=to_camel_case, + populate_by_name=True, + extra="forbid", + ) hurdles_cost: bool = False loop_flow: bool = False @@ -81,6 +86,11 @@ def to_internal(self, version: StudyVersion) -> "LinkInternal": return LinkInternal(**data) +class LinkDtoForUpdate(LinkDTO): + area1: SkipJsonSchema[str] = Field("a", exclude=True) + area2: SkipJsonSchema[str] = Field("b", exclude=True) + + class LinkInternal(AntaresBaseModel): model_config = ConfigDict(alias_generator=to_kebab_case, populate_by_name=True, extra="forbid") diff --git a/antarest/study/service.py b/antarest/study/service.py index 49759e1d3c..ebab8bac69 100644 --- a/antarest/study/service.py +++ b/antarest/study/service.py @@ -89,7 +89,7 @@ from antarest.study.business.general_management import GeneralManager from antarest.study.business.link_management import LinkManager from antarest.study.business.matrix_management import MatrixManager, MatrixManagerError -from antarest.study.business.model.link_model import LinkDTO +from antarest.study.business.model.link_model import LinkDTO, LinkDtoForUpdate from antarest.study.business.optimization_management import OptimizationManager from antarest.study.business.playlist_management import PlaylistManager from antarest.study.business.scenario_builder_management import ScenarioBuilderManager @@ -1908,13 +1908,15 @@ def create_link( def update_link( self, uuid: str, - link_update_dto: LinkDTO, + area_from: str, + area_to: str, + link_update_dto: LinkDtoForUpdate, params: RequestParameters, ) -> LinkDTO: study = self.get_study(uuid) assert_permission(params.user, study, StudyPermissionType.WRITE) self._assert_study_unarchived(study) - updated_link = self.links_manager.update_link(study, link_update_dto) + updated_link = self.links_manager.update_link(study, area_from, area_to, link_update_dto) self.event_bus.push( Event( type=EventType.STUDY_DATA_EDITED, diff --git a/antarest/study/web/study_data_blueprint.py b/antarest/study/web/study_data_blueprint.py index f856e19adc..e1a9ed90e9 100644 --- a/antarest/study/web/study_data_blueprint.py +++ b/antarest/study/web/study_data_blueprint.py @@ -68,7 +68,7 @@ ) from antarest.study.business.district_manager import DistrictCreationDTO, DistrictInfoDTO, DistrictUpdateDTO from antarest.study.business.general_management import GeneralFormFields -from antarest.study.business.model.link_model import LinkDTO +from antarest.study.business.model.link_model import LinkDTO, LinkDtoForUpdate from antarest.study.business.optimization_management import OptimizationFormFields from antarest.study.business.playlist_management import PlaylistColumns from antarest.study.business.scenario_builder_management import Rulesets, ScenarioType @@ -198,22 +198,24 @@ def create_link( return study_service.create_link(uuid, link_creation_info, params) @bp.put( - "/studies/{uuid}/links", + "/studies/{uuid}/links/{area_from}/{area_to}", tags=[APITag.study_data], summary="Update a link", response_model=LinkDTO, ) def update_link( uuid: str, - link_update_info: LinkDTO, + area_from: str, + area_to: str, + link_update_dto: LinkDtoForUpdate, current_user: JWTUser = Depends(auth.get_current_user), ) -> t.Any: logger.info( - f"Updating link {link_update_info.area1} -> {link_update_info.area2} for study {uuid}", + f"Updating link {area_from} -> {area_to} for study {uuid}", extra={"user": current_user.id}, ) params = RequestParameters(user=current_user) - return study_service.update_link(uuid, link_update_info, params) + return study_service.update_link(uuid, area_from, area_to, link_update_dto, params) @bp.put( "/studies/{uuid}/areas/{area_id}/ui", diff --git a/tests/integration/study_data_blueprint/test_link.py b/tests/integration/study_data_blueprint/test_link.py index 2e69c473ad..027aaea21a 100644 --- a/tests/integration/study_data_blueprint/test_link.py +++ b/tests/integration/study_data_blueprint/test_link.py @@ -36,8 +36,8 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ area2_id = preparer.create_area(study_id, name="Area 2")["id"] client.post(f"/v1/studies/{study_id}/links", json={"area1": area1_id, "area2": area2_id, "hurdlesCost": True}) res = client.put( - f"/v1/studies/{study_id}/links", - json={"area1": area1_id, "area2": area2_id, "colorr": 150}, + f"/v1/studies/{study_id}/links/{area1_id}/{area2_id}", + json={"colorr": 150}, ) assert res.status_code == 200 @@ -63,12 +63,8 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ # Test update link same area res = client.put( - f"/v1/studies/{study_id}/links", - json={ - "area1": area1_id, - "area2": area1_id, - "hurdlesCost": False, - }, + f"/v1/studies/{study_id}/links/{area1_id}/{area1_id}", + json={"hurdlesCost": False}, ) assert res.status_code == 422 expected = { @@ -80,12 +76,8 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ # Test update link with non existing area res = client.put( - f"/v1/studies/{study_id}/links", - json={ - "area1": area1_id, - "area2": "id_do_not_exist", - "hurdlesCost": False, - }, + f"/v1/studies/{study_id}/links/{area1_id}/id_do_not_exist", + json={"hurdlesCost": False}, ) assert res.status_code == 404 expected = { @@ -117,12 +109,8 @@ def test_link_update(self, client: TestClient, user_access_token: str, study_typ if study_type == "variant": res = client.put( - f"/v1/studies/{study_id}/links", - json={ - "area1": area1_id, - "area2": area2_id, - "hurdlesCost": False, - }, + f"/v1/studies/{study_id}/links/{area1_id}/{area2_id}", + json={"hurdlesCost": False}, ) assert res.status_code == 200