Skip to content

Commit

Permalink
feat(api): add import_study_api method (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
mehdiwahada authored Feb 5, 2025
1 parent 9eef4cf commit 081b711
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 7 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 @@ -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
34 changes: 32 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,30 @@ 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] = None) -> "Study":
session = api_config.set_up_api_conf()
wrapper = RequestWrapper(session)
base_url = f"{api_config.get_host()}/api/v1"

if study_path.suffix not in {".zip", ".7z"}:
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
74 changes: 73 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,74 @@ 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=[])

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):
with pytest.raises(Exception, match=re.escape("File doesn't have the right extensions (.zip/.7z): .rar")):
import_study_api(self.api, Path("test.rar"))

def test_import_study_fail_api_error(self, tmp_path):
study_path = tmp_path.joinpath("test.zip")
study_path.touch()

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)
30 changes: 26 additions & 4 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 @@ -504,8 +506,6 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop):
# Really important note. To instance such object with value you must respect camel case.
# Another way to do so is to instance the object and then fill its values
new_settings.general_parameters = GeneralParameters(nbYears=4)
# To create a variant with horizon as string
new_settings.general_parameters.horizon = "2018"
new_settings.advanced_parameters = AdvancedParameters()
new_settings.advanced_parameters.unit_commitment_mode = UnitCommitmentMode.MILP
new_study.update_settings(new_settings)
Expand Down Expand Up @@ -668,3 +668,25 @@ def test_creation_lifecycle(self, antares_web: AntaresWebDesktop):
study_aggregated.create_area("area_without_renewables")
# read_study_api does not raise an error
read_study_api(api_config, study_aggregated.service.study_id)

# 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())

0 comments on commit 081b711

Please sign in to comment.