Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): added import_study method to study_api.py #72

Merged
merged 10 commits into from
Feb 5, 2025
6 changes: 6 additions & 0 deletions src/antares/craft/exceptions/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,12 @@ def __init__(self, study_id: str, new_folder_name: str, message: str) -> None:
super().__init__(self.message)


class StudyImportError(Exception):
def __init__(self, study_id: str, message: str):
self.message = f"Could not import the study {study_id} : {message}"
super().__init__(self.message)


class ThermalMatrixDownloadError(Exception):
def __init__(self, area_name: str, cluster_name: str, matrix_name: str, message: str) -> None:
self.message = (
Expand Down
38 changes: 36 additions & 2 deletions src/antares/craft/model/study.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# SPDX-License-Identifier: MPL-2.0
#
# This file is part of the Antares project.

import io
import logging
import os
import time
Expand All @@ -23,7 +23,13 @@
from antares.craft.api_conf.api_conf import APIconf
from antares.craft.api_conf.request_wrapper import RequestWrapper
from antares.craft.config.local_configuration import LocalConfiguration
from antares.craft.exceptions.exceptions import APIError, LinkCreationError, StudyCreationError, StudyMoveError
from antares.craft.exceptions.exceptions import (
APIError,
LinkCreationError,
StudyCreationError,
StudyImportError,
StudyMoveError,
)
from antares.craft.model.area import Area, AreaProperties, AreaUi
from antares.craft.model.binding_constraint import (
BindingConstraint,
Expand Down Expand Up @@ -89,6 +95,34 @@ def create_study_api(
raise StudyCreationError(study_name, e.message) from e


def import_study_api(api_config: APIconf, study_path: Path, destination_path: Optional[Path]) -> "Study":
def has_valid_extension(path: Path) -> bool:
valid_extensions = {".zip", ".7z"}
return path.suffix in valid_extensions

session = api_config.set_up_api_conf()
wrapper = RequestWrapper(session)
base_url = f"{api_config.get_host()}/api/v1"

if not has_valid_extension(study_path):
MartinBelthle marked this conversation as resolved.
Show resolved Hide resolved
raise StudyImportError(
study_path.name, f"File doesn't have the right extensions (.zip/.7z): {study_path.suffix}"
)

try:
files = {"study": io.BytesIO(study_path.read_bytes())}
url = f"{base_url}/studies/_import"
study_id = wrapper.post(url, files=files).json()

study = read_study_api(api_config, study_id)
if destination_path is not None:
study.move(destination_path)

return study
except APIError as e:
raise StudyImportError(study_path.name, e.message) from e


def create_study_local(
study_name: str,
version: str,
Expand Down
80 changes: 79 additions & 1 deletion tests/antares/services/api_services/test_study_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
SimulationFailedError,
SimulationTimeOutError,
StudyCreationError,
StudyImportError,
StudyMoveError,
StudySettingsUpdateError,
StudyVariantCreationError,
Expand All @@ -52,7 +53,7 @@
from antares.craft.model.settings.general import GeneralParameters
from antares.craft.model.settings.study_settings import StudySettings
from antares.craft.model.simulation import AntaresSimulationParameters, Job, JobStatus, Solver
from antares.craft.model.study import Study, create_study_api, create_variant_api, read_study_api
from antares.craft.model.study import Study, create_study_api, create_variant_api, import_study_api, read_study_api
from antares.craft.service.api_services.output_api import OutputApiService
from antares.craft.service.service_factory import ServiceFactory

Expand Down Expand Up @@ -713,3 +714,80 @@ def test_generate_thermal_timeseries_failure(self):

with pytest.raises(ThermalTimeseriesGenerationError, match=error_message):
self.study.generate_thermal_timeseries()

def test_import_study_success(self, tmp_path):
json_study = {
"id": "22c52f44-4c2a-407b-862b-490887f93dd8",
"name": "test_read_areas",
"version": "880",
"folder": None,
}

study_path = tmp_path.joinpath("test.zip")
study_path.touch()
new_path = Path("/new/path/test")
base_url = "https://antares.com/api/v1"

url = f"{base_url}/studies/{self.study_id}"
area_url = f"{url}/areas"
area_props_url = f"{area_url}/zone/properties/form"
thermal_url = f"{area_url}/zone/clusters/thermal"
renewable_url = f"{area_url}/zone/clusters/renewable"
storage_url = f"{area_url}/zone/storages"
output_url = f"{url}/outputs"
constraints_url = f"{base_url}/studies/{self.study_id}/bindingconstraints"
config_urls = re.compile(f"{base_url}/studies/{self.study_id}/config/.*")

url_import = f"{base_url}/studies/_import"
url_move = f"{base_url}/studies/{self.study_id}/move?folder_dest={new_path}"
url_study = f"{base_url}/studies/{self.study_id}"

with requests_mock.Mocker() as mocker:
mocker.post(url_import, status_code=200, json=self.study_id)

mocker.get(url, json=json_study)
mocker.get(config_urls, json={})
mocker.get(area_url, json={})
mocker.get(area_props_url, json={})
mocker.get(renewable_url, json=[])
mocker.get(thermal_url, json=[])
mocker.get(storage_url, json=[])
mocker.get(
output_url,
json=[],
)
mocker.get(constraints_url, json=[])

# deux mockers
MartinBelthle marked this conversation as resolved.
Show resolved Hide resolved
mocker.put(url_move)
mocker.get(url_study, json=json_study)

actual_study = import_study_api(self.api, study_path, new_path)

assert actual_study.name == json_study["name"]
assert actual_study.service.study_id == json_study["id"]

def test_import_study_fail_wrong_extension(self, tmp_path):
study_path = tmp_path.joinpath("test.rar")
study_path.touch()
new_path = Path("/new/path/test")
MartinBelthle marked this conversation as resolved.
Show resolved Hide resolved

with pytest.raises(Exception, match=re.escape("File doesn't have the right extensions (.zip/.7z): .rar")):
import_study_api(self.api, study_path, new_path)

def test_import_study_fail_api_error(self, tmp_path):
study_path = tmp_path.joinpath("test.zip")
MartinBelthle marked this conversation as resolved.
Show resolved Hide resolved
study_path.touch()
new_path = Path("/new/path/test")

base_url = "https://antares.com/api/v1"
url_import = f"{base_url}/studies/_import"
url_read_study = f"{base_url}/studies/{self.study_id}"

with requests_mock.Mocker() as mocker:
mocker.post(url_import, json=self.study_id)
mocker.get(url_read_study, json={"description": self.antares_web_description_msg}, status_code=404)
with pytest.raises(
StudyImportError, match=f"Could not import the study test.zip : {self.antares_web_description_msg}"
):
import_study_api(self.api, study_path, new_path)
28 changes: 26 additions & 2 deletions tests/integration/test_web_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
# This file is part of the Antares project.
import pytest

import shutil

from pathlib import Path, PurePath

import numpy as np
Expand Down Expand Up @@ -40,7 +42,7 @@
from antares.craft.model.settings.study_settings import PlaylistParameters, StudySettings
from antares.craft.model.simulation import AntaresSimulationParameters, Job, JobStatus
from antares.craft.model.st_storage import STStorageGroup, STStorageMatrixName, STStorageProperties
from antares.craft.model.study import create_study_api, create_variant_api, read_study_api
from antares.craft.model.study import create_study_api, create_variant_api, import_study_api, read_study_api
from antares.craft.model.thermal import ThermalClusterGroup, ThermalClusterProperties

from tests.integration.antares_web_desktop import AntaresWebDesktop
Expand All @@ -56,7 +58,7 @@ def antares_web() -> AntaresWebDesktop:

# todo add integration tests for matrices
class TestWebClient:
def test_creation_lifecycle(self, antares_web: AntaresWebDesktop):
def test_creation_lifecycle(self, antares_web: AntaresWebDesktop, tmp_path):
api_config = APIconf(api_host=antares_web.url, token="", verify=False)

study = create_study_api("antares-craft-test", "880", api_config)
Expand Down Expand Up @@ -658,3 +660,25 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop):
moved_study = read_study_api(api_config, study.service.study_id)
assert moved_study.path == study.path
assert moved_study.name == study.name

# testing import study
# creating a test path to not affect the internal studies created
test_path = Path(antares_web.desktop_path.joinpath("internal_studies").joinpath(study.service.study_id))
copy_dir = tmp_path / test_path.name

tmp_path_zip = tmp_path / copy_dir.name
shutil.copytree(test_path, copy_dir)

zip_study = Path(shutil.make_archive(str(tmp_path_zip), "zip", copy_dir))

# importing without moving the study
imported_study = import_study_api(api_config, zip_study, None)

assert imported_study.path == PurePath(".")

# importing with moving the study
path_test = Path("/new/test/studies")
imported_study = import_study_api(api_config, zip_study, path_test)

assert imported_study.path == path_test / f"{imported_study.service.study_id}"
assert list(imported_study.get_areas()) == list(study.get_areas())