Skip to content

Commit

Permalink
feat(api): add read_links method (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
mehdiwahada authored Jan 9, 2025
1 parent e2c126a commit b0c1045
Show file tree
Hide file tree
Showing 11 changed files with 156 additions and 12 deletions.
6 changes: 6 additions & 0 deletions src/antares/craft/exceptions/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ def __init__(self, link_name: str, message: str) -> None:
super().__init__(self.message)


class LinksRetrievalError(Exception):
def __init__(self, study_id: str, message: str) -> None:
self.message = f"Could not retrieve links from study {study_id} : {message}"
super().__init__(self.message)


class ThermalCreationError(Exception):
def __init__(self, thermal_name: str, area_id: str, message: str) -> None:
self.message = f"Could not create the thermal cluster {thermal_name} inside area {area_id}: " + message
Expand Down
2 changes: 2 additions & 0 deletions src/antares/craft/model/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class DefaultLinkProperties(BaseModel, extra="forbid", populate_by_name=True, al
FilterOption.MONTHLY,
FilterOption.ANNUAL,
}
comments: str = ""


@all_optional_model
Expand All @@ -89,6 +90,7 @@ def ini_fields(self) -> Mapping[str, str]:
"filter-year-by-year": ", ".join(
filter_value for filter_value in sort_filter_values(self.filter_year_by_year)
),
"comments": f"{self.comments}".lower(),
}

def yield_link_properties(self) -> LinkProperties:
Expand Down
2 changes: 2 additions & 0 deletions src/antares/craft/model/study.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ def read_areas(self) -> list[Area]:
return area_list

def read_links(self) -> list[Link]:
link_list = self._link_service.read_links()
self._links = {link.id: link for link in link_list}
return self._link_service.read_links()

def get_areas(self) -> MappingProxyType[str, Area]:
Expand Down
45 changes: 43 additions & 2 deletions src/antares/craft/service/api_services/link_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# This file is part of the Antares project.

from typing import Optional
from typing import Any, Optional

import pandas as pd

Expand All @@ -22,6 +22,7 @@
LinkDeletionError,
LinkDownloadError,
LinkPropertiesUpdateError,
LinksRetrievalError,
LinkUiUpdateError,
LinkUploadError,
)
Expand Down Expand Up @@ -214,7 +215,47 @@ def create_capacity_indirect(self, series: pd.DataFrame, area_from: str, area_to
raise LinkUploadError(area_from, area_to, "indirectcapacity", e.message) from e

def read_links(self) -> list[Link]:
raise NotImplementedError
try:
url = f"{self._base_url}/studies/{self.study_id}/links"
json_links = self._wrapper.get(url).json()
links = []
for link in json_links:
link_object = self.convert_api_link_to_internal_link(link)
links.append(link_object)

links.sort(key=lambda link_obj: link_obj.area_from_id)
except APIError as e:
raise LinksRetrievalError(self.study_id, e.message) from e
return links

def convert_api_link_to_internal_link(self, api_link: dict[str, Any]) -> Link:
link_area_from_id = api_link.pop("area1")
link_area_to_id = api_link.pop("area2")

link_style = api_link.pop("linkStyle")
link_width = api_link.pop("linkWidth")
color_r = api_link.pop("colorr")
color_g = api_link.pop("colorg")
color_b = api_link.pop("colorb")

link_ui = LinkUi(link_style=link_style, link_width=link_width, colorr=color_r, colorg=color_g, colorb=color_b)

mapping = {
"hurdlesCost": "hurdles-cost",
"loopFlow": "loop-flow",
"usePhaseShifter": "use-phase-shifter",
"transmissionCapacities": "transmission-capacities",
"displayComments": "display-comments",
"filterSynthesis": "filter-synthesis",
"filterYearByYear": "filter-year-by-year",
"assetType": "asset-type",
}

api_link = {mapping.get(k, k): v for k, v in api_link.items()}
api_link["filter-synthesis"] = set(api_link["filter-synthesis"].split(", "))
api_link["filter-year-by-year"] = set(api_link["filter-year-by-year"].split(", "))
link_properties = LinkProperties(**api_link)
return Link(link_area_from_id, link_area_to_id, self, link_properties, link_ui)


def _join_filter_values_for_json(json_dict: dict, dict_to_extract: dict) -> dict:
Expand Down
1 change: 1 addition & 0 deletions src/antares/craft/service/local_services/link_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ def sort_link_properties_dict(ini_dict: Dict[str, str]) -> Dict[str, str]:
"display-comments",
"filter-synthesis",
"filter-year-by-year",
"comments",
]
return dict(sorted(ini_dict.items(), key=lambda item: dict_order.index(item[0])))

Expand Down
3 changes: 1 addition & 2 deletions tests/antares/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@

import pytest

from antares.craft import create_study_local
from antares.craft.model.area import Area
from antares.craft.model.study import Study
from antares.craft.model.study import Study, create_study_local


@pytest.fixture
Expand Down
3 changes: 1 addition & 2 deletions tests/antares/integration/test_local_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import numpy as np
import pandas as pd

from antares.craft import create_study_local
from antares.craft.exceptions.exceptions import AreaCreationError, LinkCreationError
from antares.craft.model.area import AdequacyPatchMode, Area, AreaProperties, AreaUi
from antares.craft.model.binding_constraint import BindingConstraintProperties, ClusterData, ConstraintTerm, LinkData
Expand All @@ -25,7 +24,7 @@
from antares.craft.model.settings.playlist_parameters import PlaylistParameters
from antares.craft.model.settings.study_settings import StudySettingsLocal
from antares.craft.model.st_storage import STStorageGroup, STStorageProperties
from antares.craft.model.study import Study
from antares.craft.model.study import Study, create_study_local
from antares.craft.model.thermal import ThermalCluster, ThermalClusterGroup, ThermalClusterProperties
from antares.craft.tools.ini_tool import IniFile, IniFileTypes

Expand Down
84 changes: 80 additions & 4 deletions tests/antares/services/api_services/test_link_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from antares.craft.exceptions.exceptions import (
LinkDownloadError,
LinkPropertiesUpdateError,
LinksRetrievalError,
LinkUiUpdateError,
LinkUploadError,
)
Expand All @@ -29,6 +30,38 @@
from antares.craft.service.service_factory import ServiceFactory


@pytest.fixture()
def expected_link():
area_from_name = "zone1 auto"
area_to_name = "zone4auto"
api = APIconf("https://antares.com", "token", verify=False)
study_id = "22c52f44-4c2a-407b-862b-490887f93dd8"
link_service = ServiceFactory(api, study_id).create_link_service()
properties = {
"hurdles-cost": False,
"loop-flow": False,
"use-phase-shifter": False,
"transmission-capacities": "enabled",
"asset-type": "ac",
"display-comments": True,
"colorr": 112,
"colorb": 112,
"colorg": 112,
"linkWidth": 1,
"linkStyle": "plain",
"filter-synthesis": set("hourly, daily, weekly, monthly, annual".split(", ")),
"filter-year-by-year": set("hourly, daily, weekly, monthly, annual".split(", ")),
}
color_r = properties.pop("colorr")
color_b = properties.pop("colorb")
color_g = properties.pop("colorg")
link_width = properties.pop("linkWidth")
link_style = properties.pop("linkStyle")
link_properties = LinkProperties(**properties)
link_ui = LinkUi(colorg=color_g, colorb=color_b, colorr=color_r, link_style=link_style, link_width=link_width)
return Link(area_from_name, area_to_name, link_service, link_properties, link_ui)


class TestCreateAPI:
api = APIconf("https://antares.com", "token", verify=False)
study_id = "22c52f44-4c2a-407b-862b-490887f93dd8"
Expand Down Expand Up @@ -182,7 +215,7 @@ def test_create_indirect_capacity_fail(self):
f"input/links/{self.area_from.id}/capacities/{self.area_to.id}_indirect"
)

mocker.post(raw_url, status_code=404)
mocker.post(raw_url, json={"description": self.antares_web_description_msg}, status_code=404)

with pytest.raises(
LinkUploadError,
Expand All @@ -208,7 +241,7 @@ def test_get_parameters_fail(self):
f"input/links/{self.area_from.id}/{self.area_to.id}_parameters"
)

mocker.get(raw_url, status_code=404)
mocker.get(raw_url, json={"description": self.antares_web_description_msg}, status_code=404)

with pytest.raises(
LinkDownloadError,
Expand All @@ -234,7 +267,7 @@ def test_get_indirect_capacity_fail(self):
f"input/links/{self.area_from.id}/capacities/{self.area_to.id}_indirect"
)

mocker.get(raw_url, status_code=404)
mocker.get(raw_url, json={"description": self.antares_web_description_msg}, status_code=404)

with pytest.raises(
LinkDownloadError,
Expand All @@ -260,10 +293,53 @@ def test_get_direct_capacity_fail(self):
f"input/links/{self.area_from.id}/capacities/{self.area_to.id}_direct"
)

mocker.get(raw_url, status_code=404)
mocker.get(raw_url, json={"description": self.antares_web_description_msg}, status_code=404)

with pytest.raises(
LinkDownloadError,
match=f"Could not download directcapacity matrix for link {self.area_from.id}/{self.area_to.id}",
):
self.link.get_capacity_direct()

def test_read_links(self, expected_link):
url_read_links = f"https://antares.com/api/v1/studies/{self.study_id}/links"

json_links = [
{
"hurdlesCost": False,
"loopFlow": False,
"usePhaseShifter": False,
"transmissionCapacities": "enabled",
"assetType": "ac",
"displayComments": True,
"colorr": 112,
"colorb": 112,
"colorg": 112,
"linkWidth": 1,
"linkStyle": "plain",
"filterSynthesis": "hourly, daily, weekly, monthly, annual",
"filterYearByYear": "hourly, daily, weekly, monthly, annual",
"area1": "zone1 auto",
"area2": "zone4auto",
}
]

with requests_mock.Mocker() as mocker:
mocker.get(url_read_links, json=json_links)
actual_link_list = self.study.read_links()
assert len(actual_link_list) == 1
actual_link = actual_link_list[0]
assert expected_link.id == actual_link.id
assert expected_link.properties == actual_link.properties
assert expected_link.ui == actual_link.ui

def test_read_links_fail(self):
with requests_mock.Mocker() as mocker:
raw_url = f"https://antares.com/api/v1/studies/{self.study_id}/links"
mocker.get(raw_url, json={"description": self.antares_web_description_msg}, status_code=404)

with pytest.raises(
LinksRetrievalError,
match=f"Could not retrieve links from study {self.study_id} : {self.antares_web_description_msg}",
):
self.study.read_links()
9 changes: 9 additions & 0 deletions tests/antares/services/local_services/test_study.py
Original file line number Diff line number Diff line change
Expand Up @@ -1683,6 +1683,7 @@ def test_create_link_sets_ini_content(self, tmp_path, local_study_w_areas):
display-comments = true
filter-synthesis = hourly, daily, weekly, monthly, annual
filter-year-by-year = hourly, daily, weekly, monthly, annual
comments =
"""

Expand Down Expand Up @@ -1717,6 +1718,7 @@ def test_created_link_has_default_local_properties(self, tmp_path, local_study_w
display-comments = true
filter-synthesis = hourly, daily, weekly, monthly, annual
filter-year-by-year = hourly, daily, weekly, monthly, annual
comments =
"""
expected_ini = ConfigParser()
Expand Down Expand Up @@ -1765,6 +1767,7 @@ def test_created_link_has_custom_properties(self, tmp_path, local_study_w_areas)
display-comments = true
filter-synthesis = hourly, daily, weekly, monthly, annual
filter-year-by-year = daily, weekly
comments =
"""
expected_ini = ConfigParser()
Expand Down Expand Up @@ -1804,6 +1807,7 @@ def test_multiple_links_created_from_same_area(self, tmp_path, local_study_w_are
display-comments = true
filter-synthesis = hourly, daily, weekly, monthly, annual
filter-year-by-year = hourly, daily, weekly, monthly, annual
comments =
[it]
hurdles-cost = false
Expand All @@ -1819,6 +1823,7 @@ def test_multiple_links_created_from_same_area(self, tmp_path, local_study_w_are
display-comments = true
filter-synthesis = hourly, daily, weekly, monthly, annual
filter-year-by-year = hourly, daily, weekly, monthly, annual
comments =
"""
expected_ini = ConfigParser()
Expand Down Expand Up @@ -1861,6 +1866,7 @@ def test_multiple_links_created_from_same_area_are_alphabetical(self, tmp_path,
display-comments = true
filter-synthesis = hourly, daily, weekly, monthly, annual
filter-year-by-year = hourly, daily, weekly, monthly, annual
comments =
[it]
hurdles-cost = false
Expand All @@ -1876,6 +1882,7 @@ def test_multiple_links_created_from_same_area_are_alphabetical(self, tmp_path,
display-comments = true
filter-synthesis = hourly, daily, weekly, monthly, annual
filter-year-by-year = hourly, daily, weekly, monthly, annual
comments =
"""
expected_ini = ConfigParser()
Expand Down Expand Up @@ -1933,6 +1940,7 @@ def test_created_link_has_default_ui_values(self, tmp_path, local_study_w_areas)
display-comments = true
filter-synthesis = hourly, daily, weekly, monthly, annual
filter-year-by-year = hourly, daily, weekly, monthly, annual
comments =
"""
expected_ini = ConfigParser()
Expand Down Expand Up @@ -1970,6 +1978,7 @@ def test_created_link_with_custom_ui_values(self, tmp_path, local_study_w_areas)
display-comments = true
filter-synthesis = hourly, weekly, monthly
filter-year-by-year = hourly, daily, weekly, monthly, annual
comments =
"""
expected_ini = ConfigParser()
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/antares_web_desktop.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ def kill(self):
It also kills the AntaresWebDesktop instance.
"""
session = requests.Session()
res = session.get(self.url + "/v1/studies")
res = session.get(self.url + "/api/v1/studies")
studies = res.json()
for study in studies:
session.delete(self.url + f"/v1/studies/{study}?children=True")
session.delete(self.url + f"/api/v1/studies/{study}?children=True")
self.process.terminate()
self.process.wait()
pids = subprocess.run(["pgrep AntaresWeb"], capture_output=True, shell=True).stdout.split()
Expand Down
9 changes: 9 additions & 0 deletions tests/integration/test_web_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,15 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop):
thermal_fr.update_properties(new_props)
assert thermal_fr.properties.group == ThermalClusterGroup.NUCLEAR

# assert study got all links
links = study.read_links()
assert len(links) == 2
test_link_be_fr = links[0]
test_link_de_fr = links[1]
assert test_link_be_fr.id == link_be_fr.id
assert test_link_be_fr.properties == link_be_fr.properties
assert test_link_de_fr.id == link_de_fr.id

# tests renewable properties update
new_props = RenewableClusterProperties()
new_props.ts_interpretation = TimeSeriesInterpretation.POWER_GENERATION
Expand Down

0 comments on commit b0c1045

Please sign in to comment.