diff --git a/client/qiskit_serverless/core/client.py b/client/qiskit_serverless/core/client.py index 566ab5b24..c945c0d4f 100644 --- a/client/qiskit_serverless/core/client.py +++ b/client/qiskit_serverless/core/client.py @@ -72,9 +72,10 @@ def __init__( # pylint: disable=too-many-positional-arguments self.host = host self.token = token - @classmethod - def from_dict(cls, dictionary: dict): - return BaseClient(**dictionary) + # BaseClient cannot be instanced should we have it like class abstract method? + # @classmethod + # def from_dict(cls, dictionary: dict): + # return BaseClient(**dictionary) def __eq__(self, other): if isinstance(other, BaseClient): diff --git a/client/qiskit_serverless/core/clients/ray_client.py b/client/qiskit_serverless/core/clients/ray_client.py index 29b513cef..e67696faf 100644 --- a/client/qiskit_serverless/core/clients/ray_client.py +++ b/client/qiskit_serverless/core/clients/ray_client.py @@ -104,7 +104,7 @@ def run( "`run` doesn't support program str yet. " "Send a QiskitFunction instead. " ) - return NotImplementedError + raise NotImplementedError arguments = arguments or {} entrypoint = f"python {program.entrypoint}" diff --git a/client/qiskit_serverless/core/clients/serverless_client.py b/client/qiskit_serverless/core/clients/serverless_client.py index 0b1f357e7..4c1e70b62 100644 --- a/client/qiskit_serverless/core/clients/serverless_client.py +++ b/client/qiskit_serverless/core/clients/serverless_client.py @@ -56,7 +56,11 @@ ) from qiskit_serverless.core.function import QiskitFunction from qiskit_serverless.exception import QiskitServerlessException -from qiskit_serverless.utils.json import safe_json_request +from qiskit_serverless.utils.json import ( + safe_json_request_as_dict, + safe_json_request_as_list, + safe_json_request, +) from qiskit_serverless.utils.formatting import format_provider_name_and_title from qiskit_serverless.serializers.program_serializers import ( QiskitObjectsEncoder, @@ -141,7 +145,7 @@ def get_jobs(self, **kwargs) -> List[Job]: offset = kwargs.get("offset", 0) kwargs["offset"] = offset - response_data = safe_json_request( + response_data = safe_json_request_as_dict( request=lambda: requests.get( f"{self.host}/api/{self.version}/jobs", params=kwargs, @@ -159,7 +163,7 @@ def get_job(self, job_id: str) -> Optional[Job]: tracer = trace.get_tracer("client.tracer") with tracer.start_as_current_span("job.get"): url = f"{self.host}/api/{self.version}/jobs/{job_id}/" - response_data = safe_json_request( + response_data = safe_json_request_as_dict( request=lambda: requests.get( url, headers={"Authorization": f"Bearer {self.token}"}, @@ -182,23 +186,25 @@ def run( program: Union[QiskitFunction, str], arguments: Optional[Dict[str, Any]] = None, config: Optional[Configuration] = None, + provider: Optional[str] = None, ) -> Job: if isinstance(program, QiskitFunction): title = program.title + provider = program.provider else: title = str(program) tracer = trace.get_tracer("client.tracer") with tracer.start_as_current_span("job.run") as span: span.set_attribute("program", title) - span.set_attribute("provider", program.provider) + span.set_attribute("provider", provider) span.set_attribute("arguments", str(arguments)) url = f"{self.host}/api/{self.version}/programs/run/" data = { "title": title, - "provider": program.provider, + "provider": provider, "arguments": json.dumps(arguments or {}, cls=QiskitObjectsEncoder), } # type: Dict[str, Any] if config: @@ -206,7 +212,7 @@ def run( else: data["config"] = asdict(Configuration()) - response_data = safe_json_request( + response_data = safe_json_request_as_dict( request=lambda: requests.post( url=url, json=data, @@ -223,7 +229,7 @@ def status(self, job_id: str): tracer = trace.get_tracer("client.tracer") with tracer.start_as_current_span("job.status"): default_status = "Unknown" - response_data = safe_json_request( + response_data = safe_json_request_as_dict( request=lambda: requests.get( f"{self.host}/api/{self.version}/jobs/{job_id}/", headers={"Authorization": f"Bearer {self.token}"}, @@ -244,7 +250,7 @@ def stop(self, job_id: str, service: Optional[QiskitRuntimeService] = None): data = { "service": None, } - response_data = safe_json_request( + response_data = safe_json_request_as_dict( request=lambda: requests.post( f"{self.host}/api/{self.version}/jobs/{job_id}/stop/", headers={"Authorization": f"Bearer {self.token}"}, @@ -258,7 +264,7 @@ def stop(self, job_id: str, service: Optional[QiskitRuntimeService] = None): def result(self, job_id: str): tracer = trace.get_tracer("client.tracer") with tracer.start_as_current_span("job.result"): - response_data = safe_json_request( + response_data = safe_json_request_as_dict( request=lambda: requests.get( f"{self.host}/api/{self.version}/jobs/{job_id}/", headers={"Authorization": f"Bearer {self.token}"}, @@ -272,7 +278,7 @@ def result(self, job_id: str): def logs(self, job_id: str): tracer = trace.get_tracer("client.tracer") with tracer.start_as_current_span("job.logs"): - response_data = safe_json_request( + response_data = safe_json_request_as_dict( request=lambda: requests.get( f"{self.host}/api/{self.version}/jobs/{job_id}/logs/", headers={"Authorization": f"Bearer {self.token}"}, @@ -333,7 +339,7 @@ def get_functions(self, **kwargs) -> List[QiskitFunction]: """Returns list of available programs.""" tracer = trace.get_tracer("client.tracer") with tracer.start_as_current_span("program.list"): - response_data = safe_json_request( + response_data = safe_json_request_as_list( request=lambda: requests.get( f"{self.host}/api/{self.version}/programs", headers={"Authorization": f"Bearer {self.token}"}, @@ -341,6 +347,7 @@ def get_functions(self, **kwargs) -> List[QiskitFunction]: timeout=REQUESTS_TIMEOUT, ) ) + return [ QiskitFunction( program.get("title"), @@ -362,7 +369,7 @@ def get_function( tracer = trace.get_tracer("client.tracer") with tracer.start_as_current_span("program.get_by_title"): - response_data = safe_json_request( + response_data = safe_json_request_as_dict( request=lambda: requests.get( f"{self.host}/api/{self.version}/programs/get_by_title/{title}", headers={"Authorization": f"Bearer {self.token}"}, @@ -482,7 +489,7 @@ def _upload_with_docker_image( Returns: str: uploaded function name """ - response_data = safe_json_request( + response_data = safe_json_request_as_dict( request=lambda: requests.post( url=url, data={ @@ -550,7 +557,7 @@ def _upload_with_artifact( ) with open(artifact_file_path, "rb") as file: - response_data = safe_json_request( + response_data = safe_json_request_as_dict( request=lambda: requests.post( url=url, data={ diff --git a/client/qiskit_serverless/core/files.py b/client/qiskit_serverless/core/files.py index 2d7939a36..fe32525fb 100644 --- a/client/qiskit_serverless/core/files.py +++ b/client/qiskit_serverless/core/files.py @@ -34,7 +34,7 @@ from tqdm import tqdm from qiskit_serverless.core.constants import REQUESTS_TIMEOUT -from qiskit_serverless.utils.json import safe_json_request +from qiskit_serverless.utils.json import safe_json_request_as_dict class GatewayFilesClient: @@ -106,7 +106,7 @@ def list(self, provider: Optional[str] = None) -> List[str]: """Returns list of available files to download produced by programs,""" tracer = trace.get_tracer("client.tracer") with tracer.start_as_current_span("files.list"): - response_data = safe_json_request( + response_data = safe_json_request_as_dict( request=lambda: requests.get( f"{self.host}/api/{self.version}/files/", params={"provider": provider}, @@ -120,7 +120,7 @@ def delete(self, file: str, provider: Optional[str] = None) -> Optional[str]: """Deletes file uploaded or produced by the programs,""" tracer = trace.get_tracer("client.tracer") with tracer.start_as_current_span("files.delete"): - response_data = safe_json_request( + response_data = safe_json_request_as_dict( request=lambda: requests.delete( f"{self.host}/api/{self.version}/files/delete/", data={"file": file, "provider": provider}, diff --git a/client/qiskit_serverless/utils/json.py b/client/qiskit_serverless/utils/json.py index f611b9f6a..4ba3e7086 100644 --- a/client/qiskit_serverless/utils/json.py +++ b/client/qiskit_serverless/utils/json.py @@ -28,7 +28,7 @@ import json from abc import ABC from json import JSONEncoder -from typing import Optional, Type, Callable, Dict, Any +from typing import List, Optional, Type, Callable, Dict, Any, Union import requests @@ -74,7 +74,49 @@ def is_jsonable(data, cls: Optional[Type[JSONEncoder]] = None): return False -def safe_json_request(request: Callable, verbose: bool = False) -> Dict[str, Any]: +def safe_json_request_as_list(request: Callable, verbose: bool = False) -> List[Any]: + """Returns parsed json data from request. + + Args: + request: callable for request. + verbose: post reason in error message + + Example: + >>> safe_json_request(request=lambda: requests.get("https://ibm.com")) + + Returns: + parsed json response as list structure + """ + response = safe_json_request(request, verbose) + if isinstance(response, List): + return response + raise TypeError("JSON is not a List") + + +def safe_json_request_as_dict( + request: Callable, verbose: bool = False +) -> Dict[str, Any]: + """Returns parsed json data from request. + + Args: + request: callable for request. + verbose: post reason in error message + + Example: + >>> safe_json_request(request=lambda: requests.get("https://ibm.com")) + + Returns: + parsed json response as dict structure + """ + response = safe_json_request(request, verbose) + if isinstance(response, Dict): + return response + raise TypeError("JSON is not a Dict") + + +def safe_json_request( + request: Callable, verbose: bool = False +) -> Union[Dict[str, Any], List[Any]]: """Returns parsed json data from request. Args: