From 8459474c1d173b3f4f19e418c7cfd7e157d956b3 Mon Sep 17 00:00:00 2001 From: Ali Date: Wed, 31 Jul 2024 17:30:31 +0200 Subject: [PATCH] Implemented version control --- firecrest/AsyncClient.py | 11 ++++++- firecrest/BasicClient.py | 11 ++++++- firecrest/FirecrestException.py | 4 +++ firecrest/utilities.py | 51 +++++++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 2 deletions(-) diff --git a/firecrest/AsyncClient.py b/firecrest/AsyncClient.py index 340879d..8c62f0c 100644 --- a/firecrest/AsyncClient.py +++ b/firecrest/AsyncClient.py @@ -28,7 +28,7 @@ import firecrest.FirecrestException as fe import firecrest.types as t from firecrest.AsyncExternalStorage import AsyncExternalUpload, AsyncExternalDownload -from firecrest.utilities import time_block, slurm_state_completed +from firecrest.utilities import time_block, slurm_state_completed, async_validate_api_version_compatibility if sys.version_info >= (3, 8): @@ -247,6 +247,10 @@ def set_api_version(self, api_version: str) -> None: """Set the version of the api of firecrest. By default it will be assumed that you are using version 1.13.1 or compatible. The version is parsed by the `packaging` library. """ + if parse(api_version) < parse("1.13.0"): + raise ValueError( + f"API version {api_version} is no longer supported by this client" + ) self._api_version = parse(api_version) async def close_session(self) -> None: @@ -727,6 +731,7 @@ async def filesystems(self, system_name: Optional[str] = None) -> dict[str, List return self._json_response([resp], 200)["out"] # Utilities + @async_validate_api_version_compatibility(recursive=True) async def list_files( self, machine: str, target_path: str, show_hidden: bool = False, recursive: bool = False @@ -860,6 +865,7 @@ async def copy(self, machine: str, source_path: str, target_path: str) -> str: self._json_response([resp], 201) return target_path + @async_validate_api_version_compatibility() async def compress( self, machine: str, @@ -958,6 +964,7 @@ async def compress( return target_path + @async_validate_api_version_compatibility() async def extract( self, machine: str, @@ -1812,6 +1819,7 @@ async def submit_copy_job( result.update({"system": job_info[1]}) return result + @async_validate_api_version_compatibility() async def submit_compress_job( self, machine: str, @@ -1865,6 +1873,7 @@ async def submit_compress_job( result.update({"system": job_info[1]}) return result + @async_validate_api_version_compatibility() async def submit_extract_job( self, machine: str, diff --git a/firecrest/BasicClient.py b/firecrest/BasicClient.py index 7e1768a..dff1b1f 100644 --- a/firecrest/BasicClient.py +++ b/firecrest/BasicClient.py @@ -25,7 +25,7 @@ import firecrest.FirecrestException as fe import firecrest.types as t from firecrest.ExternalStorage import ExternalUpload, ExternalDownload -from firecrest.utilities import time_block, slurm_state_completed +from firecrest.utilities import time_block, slurm_state_completed, validate_api_version_compatibility if sys.version_info >= (3, 8): from typing import Literal @@ -162,6 +162,10 @@ def set_api_version(self, api_version: str) -> None: """Set the version of the api of firecrest. By default it will be assumed that you are using version 1.13.1 or compatible. The version is parsed by the `packaging` library. """ + if parse(api_version) < parse("1.13.0"): + raise ValueError( + f"API version {api_version} is no longer supported by this client" + ) self._api_version = parse(api_version) def log(self, level: int, msg: Any) -> None: @@ -481,6 +485,7 @@ def filesystems(self, system_name: Optional[str] = None) -> dict[str, List[t.Fil return self._json_response([resp], 200)["out"] # Utilities + @validate_api_version_compatibility(recursive=True) def list_files( self, machine: str, target_path: str, show_hidden: bool = False, recursive: bool = False @@ -612,6 +617,7 @@ def copy(self, machine: str, source_path: str, target_path: str) -> str: self._json_response([resp], 201) return target_path + @validate_api_version_compatibility() def compress( self, machine: str, @@ -711,6 +717,7 @@ def compress( return target_path + @validate_api_version_compatibility() def extract( self, machine: str, @@ -1726,6 +1733,7 @@ def external_download(self, machine: str, source_path: str) -> ExternalDownload: self, self._json_response([resp], 201)["task_id"], [resp] ) + @validate_api_version_compatibility() def submit_compress_job( self, machine: str, @@ -1779,6 +1787,7 @@ def submit_compress_job( result.update({"system": transfer_info[1]}) return result + @validate_api_version_compatibility() def submit_extract_job( self, machine: str, diff --git a/firecrest/FirecrestException.py b/firecrest/FirecrestException.py index fddf6dd..108f5c8 100644 --- a/firecrest/FirecrestException.py +++ b/firecrest/FirecrestException.py @@ -112,3 +112,7 @@ def __str__(self): f"is exhausted. Update `polling_sleep_times` of the client " f"to increase the number of polling attempts." ) + + +class NotImplementedOnAPIversion(Exception): + """Exception raised when a feature is not developed yet for the current API version""" diff --git a/firecrest/utilities.py b/firecrest/utilities.py index 5bc1cba..91431cd 100644 --- a/firecrest/utilities.py +++ b/firecrest/utilities.py @@ -1,5 +1,6 @@ import time from contextlib import contextmanager +import firecrest.FirecrestException as fe @contextmanager @@ -28,3 +29,53 @@ def slurm_state_completed(state): return all(s in completion_states for s in state.split(',')) return False + + +def validate_api_version_compatibility(**expected_flags): + def decorator(func): + def wrapper(self, *args, **kwargs): + missing_features = missing_api_features.get(self._api_version, {}).get(func.__name__, []) + + if 'ALL' in missing_features: + raise fe.NotImplementedOnAPIversion(f"All features for {func.__name__}" + " are not developed yet for the current API version.") + + for flag, value in expected_flags.items(): + if kwargs.get(flag) == value and flag in missing_features: + raise fe.NotImplementedOnAPIversion(f"The flag {flag}={value} is not developed" + " yet for {func.__name__} for the current API version.") + + return func(self, *args, **kwargs) + return wrapper + return decorator + + +def async_validate_api_version_compatibility(**expected_flags): + def decorator(func): + async def wrapper(self, *args, **kwargs): + missing_features = missing_api_features.get(self._api_version, {}).get(func.__name__, []) + + if 'ALL' in missing_features: + raise fe.NotImplementedOnAPIversion(f"All features for {func.__name__} are " + "not developed yet for the current API version.") + + for flag, value in expected_flags.items(): + if kwargs.get(flag) == value and flag in missing_features: + raise fe.NotImplementedOnAPIversion(f"The flag {flag}={value} is not developed" + " yet for {func.__name__} for the current API version.") + + return await func(self, *args, **kwargs) + return wrapper + return decorator + + +missing_api_features = { + '1.15.0': { + 'list_files': ['recursive'], + 'compress': ['ALL'], + 'extract': ['ALL'], + 'submit_compress_job': ['ALL'], + 'submit_extract_job': ['ALL'] + }, + '1.16.0': {}, +}