From 4424cdcb5d1ecc6c81d93390a1a87208524a4e18 Mon Sep 17 00:00:00 2001 From: Rathish Cholarajan Date: Thu, 21 Oct 2021 16:00:40 -0400 Subject: [PATCH 1/4] Accept JSON schema as program metadata --- qiskit_ibm/api/clients/runtime.py | 14 +- qiskit_ibm/api/rest/runtime.py | 20 +-- qiskit_ibm/runtime/ibm_runtime_service.py | 108 ++++++--------- .../program/program_metadata_sample.json | 45 +++++-- qiskit_ibm/runtime/runtime_program.py | 124 ++++++++---------- ...metadata-json-schema-46f034ada7443cf9.yaml | 10 ++ test/ibm/runtime/fake_runtime_client.py | 18 ++- test/ibm/runtime/test_runtime.py | 86 +++++++----- 8 files changed, 208 insertions(+), 217 deletions(-) create mode 100644 releasenotes/notes/upgrade-metadata-json-schema-46f034ada7443cf9.yaml diff --git a/qiskit_ibm/api/clients/runtime.py b/qiskit_ibm/api/clients/runtime.py index ddc71bff9..9844f2b76 100644 --- a/qiskit_ibm/api/clients/runtime.py +++ b/qiskit_ibm/api/clients/runtime.py @@ -54,10 +54,7 @@ def program_create( description: str, max_execution_time: int, is_public: Optional[bool] = False, - backend_requirements: Optional[Dict] = None, - parameters: Optional[Dict] = None, - return_values: Optional[List] = None, - interim_results: Optional[List] = None + spec: Optional[Dict] = None ) -> Dict: """Create a new program. @@ -67,10 +64,7 @@ def program_create( description: Program description. max_execution_time: Maximum execution time. is_public: Whether the program should be public. - backend_requirements: Backend requirements. - parameters: Program parameters. - return_values: Program return values. - interim_results: Program interim results. + spec: Backend requirements, parameters, interim results, return values, etc. Returns: Server response. @@ -79,9 +73,7 @@ def program_create( program_data=program_data, name=name, description=description, max_execution_time=max_execution_time, - is_public=is_public, backend_requirements=backend_requirements, - parameters=parameters, return_values=return_values, - interim_results=interim_results + is_public=is_public, spec=spec ) def program_get(self, program_id: str) -> Dict: diff --git a/qiskit_ibm/api/rest/runtime.py b/qiskit_ibm/api/rest/runtime.py index d5ceff21e..01626efee 100644 --- a/qiskit_ibm/api/rest/runtime.py +++ b/qiskit_ibm/api/rest/runtime.py @@ -70,10 +70,7 @@ def create_program( description: str, max_execution_time: int, is_public: Optional[bool] = False, - backend_requirements: Optional[Dict] = None, - parameters: Optional[Dict] = None, - return_values: Optional[List] = None, - interim_results: Optional[List] = None + spec: Optional[Dict] = None ) -> Dict: """Upload a new program. @@ -83,10 +80,7 @@ def create_program( description: Program description. max_execution_time: Maximum execution time. is_public: Whether the program should be public. - backend_requirements: Backend requirements. - parameters: Program parameters. - return_values: Program return values. - interim_results: Program interim results. + spec: Backend requirements, parameters, interim results, return values, etc. Returns: JSON response. @@ -98,14 +92,8 @@ def create_program( 'description': description.encode(), 'max_execution_time': max_execution_time, 'is_public': is_public} - if backend_requirements: - data['backendRequirements'] = json.dumps(backend_requirements) - if parameters: - data['parameters'] = json.dumps({"doc": parameters}) - if return_values: - data['returnValues'] = json.dumps(return_values) - if interim_results: - data['interimResults'] = json.dumps(interim_results) + if spec is not None: + data['spec'] = json.dumps(spec) response = self.session.post(url, data=data).json() return response diff --git a/qiskit_ibm/runtime/ibm_runtime_service.py b/qiskit_ibm/runtime/ibm_runtime_service.py index cd6a45897..1e3cc75b5 100644 --- a/qiskit_ibm/runtime/ibm_runtime_service.py +++ b/qiskit_ibm/runtime/ibm_runtime_service.py @@ -15,14 +15,13 @@ import logging from typing import Dict, Callable, Optional, Union, List, Any, Type import json -import copy import re from qiskit.providers.exceptions import QiskitBackendNotFoundError from qiskit_ibm import ibm_provider # pylint: disable=unused-import from .runtime_job import RuntimeJob -from .runtime_program import RuntimeProgram, ProgramParameter, ProgramResult, ParameterNamespace +from .runtime_program import RuntimeProgram, ParameterNamespace from .utils import RuntimeEncoder, RuntimeDecoder, to_base64_string from .exceptions import (QiskitRuntimeError, RuntimeDuplicateProgramError, RuntimeProgramNotFound, RuntimeJobNotFound) @@ -182,21 +181,26 @@ def _to_program(self, response: Dict) -> RuntimeProgram: Returns: A ``RuntimeProgram`` instance. """ - backend_req = json.loads(response.get('backendRequirements', '{}')) - params = json.loads(response.get('parameters', '{}')).get("doc", []) - ret_vals = json.loads(response.get('returnValues', '{}')) - interim_results = json.loads(response.get('interimResults', '{}')) + backend_requirements = {} + parameters = {} + return_values = {} + interim_results = {} + if "spec" in response: + backend_requirements = response["spec"].get('backend_requirements', {}) + parameters = response["spec"].get('parameters', {}) + return_values = response["spec"].get('return_values', {}) + interim_results = response["spec"].get('interim_results', {}) return RuntimeProgram(program_name=response['name'], program_id=response['id'], description=response.get('description', ""), - parameters=params, - return_values=ret_vals, + parameters=parameters, + return_values=return_values, interim_results=interim_results, max_execution_time=response.get('cost', 0), creation_date=response.get('creation_date', ""), update_date=response.get('update_date', ""), - backend_requirements=backend_req, + backend_requirements=backend_requirements, is_public=response.get('is_public', False)) def run( @@ -267,15 +271,7 @@ def run( def upload_program( self, data: str, - metadata: Optional[Union[Dict, str]] = None, - name: Optional[str] = None, - is_public: Optional[bool] = False, - max_execution_time: Optional[int] = None, - description: Optional[str] = None, - backend_requirements: Optional[str] = None, - parameters: Optional[List[ProgramParameter]] = None, - return_values: Optional[List[ProgramResult]] = None, - interim_results: Optional[List[ProgramResult]] = None + metadata: Optional[Union[Dict, str]] = None ) -> str: """Upload a runtime program. @@ -298,17 +294,22 @@ def upload_program( Args: data: Program data or path of the file containing program data to upload. metadata: Name of the program metadata file or metadata dictionary. - A metadata file needs to be in the JSON format. - See :file:`program/program_metadata_sample.yaml` for an example. - name: Name of the program. Required if not specified via `metadata`. - max_execution_time: Maximum execution time in seconds. Required if - not specified via `metadata`. - is_public: Whether the runtime program should be visible to the public. - description: Program description. Required if not specified via `metadata`. - backend_requirements: Backend requirements. - parameters: A list of program input parameters. - return_values: A list of program return values. - interim_results: A list of program interim results. + A metadata file needs to be in the JSON format. The ``parameters``, + ``return_values``, and ``interim_results`` should be defined as JSON Schema. + See :file:`program/program_metadata_sample.yaml` for an example. The + fields in metadata are explained below. + + * name: Name of the program. Required. + * max_execution_time: Maximum execution time in seconds. Required. + * description: Program description. Required. + * is_public: Whether the runtime program should be visible to the public. + * spec: Specifications for backend characteristics and input parameters + required to run the program, interim results and final result. + + * backend_requirements: Backend requirements. + * parameters: Program input parameters in JSON schema format. + * return_values: Program return values in JSON schema format. + * interim_results: Program interim results in JSON schema format. Returns: Program ID. @@ -319,14 +320,7 @@ def upload_program( IBMNotAuthorizedError: If you are not authorized to upload programs. QiskitRuntimeError: If the upload failed. """ - program_metadata = self._merge_metadata( - initial={}, - metadata=metadata, - name=name, max_execution_time=max_execution_time, - is_public=is_public, description=description, - backend_requirements=backend_requirements, - parameters=parameters, - return_values=return_values, interim_results=interim_results) + program_metadata = self._read_metadata(metadata=metadata) for req in ['name', 'description', 'max_execution_time']: if req not in program_metadata or not program_metadata[req]: @@ -351,21 +345,17 @@ def upload_program( raise QiskitRuntimeError(f"Failed to create program: {ex}") from None return response['id'] - def _merge_metadata( + def _read_metadata( self, - initial: Dict, - metadata: Optional[Union[Dict, str]] = None, - **kwargs: Any + metadata: Optional[Union[Dict, str]] = None ) -> Dict: - """Merge multiple copies of metadata. + """Read metadata. Args: - initial: The initial metadata. This may be mutated. metadata: Name of the program metadata file or metadata dictionary. - **kwargs: Additional metadata fields to overwrite. Returns: - Merged metadata. + Return metadata. """ upd_metadata: dict = {} if metadata is not None: @@ -373,33 +363,11 @@ def _merge_metadata( with open(metadata, 'r') as file: upd_metadata = json.load(file) else: - upd_metadata = copy.deepcopy(metadata) - - self._tuple_to_dict(initial) - initial.update(upd_metadata) - - self._tuple_to_dict(kwargs) - for key, val in kwargs.items(): - if val is not None: - initial[key] = val - + upd_metadata = metadata # TODO validate metadata format metadata_keys = ['name', 'max_execution_time', 'description', - 'backend_requirements', 'parameters', 'return_values', - 'interim_results', 'is_public'] - return {key: val for key, val in initial.items() if key in metadata_keys} - - def _tuple_to_dict(self, metadata: Dict) -> None: - """Convert fields in metadata from named tuples to dictionaries. - - Args: - metadata: Metadata to be converted. - """ - for key in ['parameters', 'return_values', 'interim_results']: - doc_list = metadata.pop(key, None) - if not doc_list or isinstance(doc_list[0], dict): - continue - metadata[key] = [dict(elem._asdict()) for elem in doc_list] + 'spec', 'is_public'] + return {key: val for key, val in upd_metadata.items() if key in metadata_keys} def update_program( self, diff --git a/qiskit_ibm/runtime/program/program_metadata_sample.json b/qiskit_ibm/runtime/program/program_metadata_sample.json index 5df24385f..893387761 100644 --- a/qiskit_ibm/runtime/program/program_metadata_sample.json +++ b/qiskit_ibm/runtime/program/program_metadata_sample.json @@ -2,15 +2,38 @@ "name": "runtime-simple", "description": "Simple runtime program used for testing.", "max_execution_time": 300, - "backend_requirements": {"min_num_qubits": 5}, - "parameters": [ - {"name": "iterations", "description": "Number of iterations to run. Each iteration generates a runs a random circuit.", "type": "integer", "required": true} - ], - "return_values": [ - {"name": "-", "description": "A string that says 'All done!'.", "type": "string"} - ], - "interim_results": [ - {"name": "iteration", "description": "Iteration number.", "type": "int"}, - {"name": "counts", "description": "Histogram data of the circuit result.", "type": "dict"} - ] + "spec": { + "backend_requirements": { + "min_num_qubits": 5 + }, + "parameters": { + "type": "object", + "properties": { + "iterations": { + "description": "Number of iterations to run. Each iteration generates a runs a random circuit.", + "type": "integer" + } + }, + "required": [ + "iterations" + ] + }, + "return_values": { + "type": "string", + "description": "A string that says 'All done!'." + }, + "interim_results": { + "type": "object", + "properties": { + "iteration": { + "description": "Iteration number.", + "type": "int" + }, + "counts": { + "description": "Histogram data of the circuit result.", + "type": "dict" + } + } + } + } } \ No newline at end of file diff --git a/qiskit_ibm/runtime/runtime_program.py b/qiskit_ibm/runtime/runtime_program.py index 9b6cde746..5e648244e 100644 --- a/qiskit_ibm/runtime/runtime_program.py +++ b/qiskit_ibm/runtime/runtime_program.py @@ -13,7 +13,8 @@ """Qiskit runtime program.""" import logging -from typing import Optional, List, NamedTuple, Dict +import re +from typing import Optional, List, Dict from types import SimpleNamespace from qiskit_ibm.exceptions import IBMInputValueError @@ -47,9 +48,9 @@ def __init__( program_name: str, program_id: str, description: str, - parameters: Optional[List] = None, - return_values: Optional[List] = None, - interim_results: Optional[List] = None, + parameters: Optional[Dict] = None, + return_values: Optional[Dict] = None, + interim_results: Optional[Dict] = None, max_execution_time: int = 0, backend_requirements: Optional[Dict] = None, creation_date: str = "", @@ -76,49 +77,44 @@ def __init__( self._description = description self._max_execution_time = max_execution_time self._backend_requirements = backend_requirements or {} - self._parameters: List[ProgramParameter] = [] - self._return_values: List[ProgramResult] = [] - self._interim_results: List[ProgramResult] = [] + self._parameters = parameters or {} + self._return_values = return_values or {} + self._interim_results = interim_results or {} self._creation_date = creation_date self._update_date = update_date self._is_public = is_public - if parameters: - for param in parameters: - self._parameters.append( - ProgramParameter(name=param['name'], - description=param['description'], - type=param['type'], - required=param['required'])) - if return_values is not None: - for ret in return_values: - self._return_values.append(ProgramResult(name=ret['name'], - description=ret['description'], - type=ret['type'])) - if interim_results is not None: - for intret in interim_results: - self._interim_results.append(ProgramResult(name=intret['name'], - description=intret['description'], - type=intret['type'])) - def __str__(self) -> str: - def _format_common(items: List) -> None: - """Add name, description, and type to `formatted`.""" - for item in items: - formatted.append(" "*4 + "- " + item.name + ":") - formatted.append(" "*6 + "Description: " + item.description) - formatted.append(" "*6 + "Type: " + item.type) - if hasattr(item, 'required'): - formatted.append(" "*6 + "Required: " + str(item.required)) + def _format_common(schema: Dict) -> None: + """Add title, description and property details to `formatted`.""" + if "description" in schema: + formatted.append(" "*4 + "Description: {}".format(schema["description"])) + if "type" in schema: + formatted.append(" "*4 + "Type: {}".format(str(schema["type"]))) + if "properties" in schema: + formatted.append(" "*4 + "Properties:") + for property_name, property_value in schema["properties"].items(): + formatted.append(" "*8 + "- " + property_name + ":") + for key, value in property_value.items(): + formatted.append(" "*12 + "{}: {}".format(sentence_case(key), str(value))) + formatted.append(" "*12 + "Required: " + + str(property_name in schema.get("required", []))) + + def sentence_case(camel_case_text: str) -> str: + """Converts camelCase to Sentence case""" + if camel_case_text == '': + return camel_case_text + sentence_case_text = re.sub('([A-Z])', r' \1', camel_case_text) + return sentence_case_text[:1].upper() + sentence_case_text[1:].lower() formatted = [f'{self.program_id}:', f" Name: {self.name}", f" Description: {self.description}", f" Creation date: {self.creation_date}", f" Update date: {self.update_date}", - f" Max execution time: {self.max_execution_time}", - f" Input parameters:"] + f" Max execution time: {self.max_execution_time}"] + formatted.append(" Input parameters:") if self._parameters: _format_common(self._parameters) else: @@ -198,7 +194,7 @@ def description(self) -> str: return self._description @property - def return_values(self) -> List['ProgramResult']: + def return_values(self) -> Dict: """Program return value definitions. Returns: @@ -207,7 +203,7 @@ def return_values(self) -> List['ProgramResult']: return self._return_values @property - def interim_results(self) -> List['ProgramResult']: + def interim_results(self) -> Dict: """Program interim result definitions. Returns: @@ -263,21 +259,6 @@ def is_public(self) -> bool: return self._is_public -class ProgramParameter(NamedTuple): - """Program parameter.""" - name: str - description: str - type: str - required: bool - - -class ProgramResult(NamedTuple): - """Program result.""" - name: str - description: str - type: str - - class ParameterNamespace(SimpleNamespace): """ A namespace for program parameters with validation. @@ -285,26 +266,26 @@ class ParameterNamespace(SimpleNamespace): and validation support. """ - def __init__(self, params: List[ProgramParameter]): + def __init__(self, parameters: Dict): """ParameterNamespace constructor. Args: - params: The program's input parameters. + parameters: The program's input parameters. """ super().__init__() - # Allow access to the raw program parameters list - self.__metadata = params + # Allow access to the raw program parameters dict + self.__metadata = parameters # For localized logic, create store of parameters in dictionary self.__program_params: dict = {} - for param in params: + for parameter_name, parameter_value in parameters.get("properties", {}).items(): # (1) Add parameters to a dict by name - setattr(self, param.name, None) + setattr(self, parameter_name, None) # (2) Store the program params for validation - self.__program_params[param.name] = param + self.__program_params[parameter_name] = parameter_value @property - def metadata(self) -> List[ProgramParameter]: + def metadata(self) -> Dict: """Returns the parameter metadata""" return self.__metadata @@ -320,12 +301,12 @@ def validate(self) -> None: """ # Iterate through the user's stored inputs - for param_name, program_param in self.__program_params.items(): - # Set invariants: User-specified parameter value (value) and whether it's required (req) - value = getattr(self, param_name, None) + for parameter_name, parameter_value in self.__program_params.items(): + # Set invariants: User-specified parameter value (value) and if it's required (req) + value = getattr(self, parameter_name, None) # Check there exists a program parameter of that name. - if value is None and program_param.required: - raise IBMInputValueError('Param (%s) missing required value!' % param_name) + if value is None and parameter_name in self.metadata.get("required", []): + raise IBMInputValueError('Param (%s) missing required value!' % parameter_name) def __str__(self) -> str: """Creates string representation of object""" @@ -338,15 +319,14 @@ def __str__(self) -> str: 'Required', 'Description' ) - # List of ProgramParameter objects (str) params_str = '\n'.join([ '| {:10.10} | {:12.12} | {:12.12}| {:8.8} | {:>15} |'.format( - param.name, - str(getattr(self, param.name, 'None')), - param.type, - str(param.required), - param.description - ) for param in self.__program_params.values()]) + parameter_name, + str(getattr(self, parameter_name, "None")), + str(parameter_value.get("type", "None")), + str(parameter_name in self.metadata.get("required", [])), + str(parameter_value.get("description", "None")) + ) for parameter_name, parameter_value in self.__program_params.items()]) return "ParameterNamespace (Values):\n%s\n%s\n%s" \ % (header, '-' * len(header), params_str) diff --git a/releasenotes/notes/upgrade-metadata-json-schema-46f034ada7443cf9.yaml b/releasenotes/notes/upgrade-metadata-json-schema-46f034ada7443cf9.yaml new file mode 100644 index 000000000..08eb17d93 --- /dev/null +++ b/releasenotes/notes/upgrade-metadata-json-schema-46f034ada7443cf9.yaml @@ -0,0 +1,10 @@ +--- +upgrade: + - | + :meth:`qiskit_ibm.runtime.IBMRuntimeService.upload_program` now takes only two parameters, + ``data``, which is the program passed as a string or the path to the program file and the + ``metadata``, which is passed as a dictionary or path to the metadata JSON file. + In ``metadata`` the ``backend_requirements``, ``parameters``, ``return_values`` and + ``interim_results`` are now grouped under a specifications ``spec`` section. + ``parameters``, ``return_values`` and ``interim_results`` should now be specified as + JSON Schema. diff --git a/test/ibm/runtime/fake_runtime_client.py b/test/ibm/runtime/fake_runtime_client.py index 3a05e45f9..bd2b4852d 100644 --- a/test/ibm/runtime/fake_runtime_client.py +++ b/test/ibm/runtime/fake_runtime_client.py @@ -52,14 +52,15 @@ def to_dict(self, include_data=False): 'update_date': '2021-09-14T19:25:32Z'} if include_data: out['data'] = self._data + out['spec'] = {} if self._backend_requirements: - out['backendRequirements'] = json.dumps(self._backend_requirements) + out['spec']['backend_requirements'] = self._backend_requirements if self._parameters: - out['parameters'] = json.dumps({"doc": self._parameters}) + out['spec']['parameters'] = self._parameters if self._return_values: - out['returnValues'] = json.dumps(self._return_values) + out['spec']['return_values'] = self._return_values if self._interim_results: - out['interimResults'] = json.dumps(self._interim_results) + out['spec']['interim_results'] = self._interim_results return out @@ -231,19 +232,22 @@ def set_final_status(self, final_status): self._final_status = final_status def list_programs(self): - """List all progrmas.""" + """List all programs.""" programs = [] for prog in self._programs.values(): programs.append(prog.to_dict()) return {"programs": programs} def program_create(self, program_data, name, description, max_execution_time, - backend_requirements=None, parameters=None, return_values=None, - interim_results=None, is_public=False): + spec=None, is_public=False): """Create a program.""" program_id = name if program_id in self._programs: raise RequestsApiError("Program already exists.", status_code=409) + backend_requirements = spec.get('backend_requirements', None) + parameters = spec.get('parameters', None) + return_values = spec.get('return_values', None) + interim_results = spec.get('interim_results', None) self._programs[program_id] = BaseFakeProgram( program_id=program_id, name=name, data=program_data, cost=max_execution_time, description=description, backend_requirements=backend_requirements, diff --git a/test/ibm/runtime/test_runtime.py b/test/ibm/runtime/test_runtime.py index acedbea2f..b5eb61e96 100644 --- a/test/ibm/runtime/test_runtime.py +++ b/test/ibm/runtime/test_runtime.py @@ -12,6 +12,7 @@ """Tests for runtime service.""" +import copy import json import os from io import StringIO @@ -55,7 +56,7 @@ from qiskit_ibm.runtime import IBMRuntimeService, RuntimeJob from qiskit_ibm.runtime.constants import API_TO_JOB_ERROR_MESSAGE from qiskit_ibm.runtime.exceptions import RuntimeProgramNotFound, RuntimeJobFailureError -from qiskit_ibm.runtime.runtime_program import ParameterNamespace, ProgramParameter, ProgramResult +from qiskit_ibm.runtime.runtime_program import ParameterNamespace from ...ibm_test_case import IBMTestCase from .fake_runtime_client import (BaseFakeRuntimeClient, FailedRanTooLongRuntimeJob, @@ -70,16 +71,50 @@ class TestRuntime(IBMTestCase): "name": "qiskit-test", "description": "Test program.", "max_execution_time": 300, - "backend_requirements": {"min_num_qubits": 5}, - "parameters": [ - {'name': 'param1', 'description': 'Desc 1', 'type': 'str', 'required': True}, - {'name': 'param2', 'description': 'Desc 2', 'type': 'int', 'required': False}], - "return_values": [ - {"name": "ret_val", "description": "Some return value.", "type": "string"} - ], - "interim_results": [ - {"name": "int_res", "description": "Some interim result", "type": "string"}, - ] + "spec": { + "backend_requirements": { + "min_num_qubits": 5 + }, + "parameters": { + "properties": { + "param1": { + "description": "Desc 1", + "type": "string", + "enum": [ + "a", + "b", + "c" + ] + }, + "param2": { + "description": "Desc 2", + "type": "integer", + "min": 0 + } + }, + "required": [ + "param1" + ] + }, + "return_values": { + "type": "object", + "description": "Return values", + "properties": { + "ret_val": { + "description": "Some return value.", + "type": "string" + } + } + }, + "interim_results": { + "properties": { + "int_res": { + "description": "Some interim result", + "type": "string" + } + } + } + } } def setUp(self): @@ -601,26 +636,15 @@ def test_program_metadata(self): program.max_execution_time) self.assertTrue(program.creation_date) self.assertTrue(program.update_date) - self.assertEqual(self.DEFAULT_METADATA['backend_requirements'], + self.assertEqual(self.DEFAULT_METADATA['spec']['backend_requirements'], program.backend_requirements) - self.assertEqual([ProgramParameter(**param) for param in - self.DEFAULT_METADATA['parameters']], + self.assertEqual(self.DEFAULT_METADATA['spec']['parameters'], program.parameters().metadata) - self.assertEqual([ProgramResult(**ret) for ret in - self.DEFAULT_METADATA['return_values']], + self.assertEqual(self.DEFAULT_METADATA['spec']['return_values'], program.return_values) - self.assertEqual([ProgramResult(**ret) for ret in - self.DEFAULT_METADATA['interim_results']], + self.assertEqual(self.DEFAULT_METADATA['spec']['interim_results'], program.interim_results) - def test_metadata_combined(self): - """Test combining metadata""" - update_metadata = {"max_execution_time": 600} - program_id = self.runtime.upload_program( - data="def main() {}", metadata=self.DEFAULT_METADATA, **update_metadata) - program = self.runtime.program(program_id) - self.assertEqual(update_metadata['max_execution_time'], program.max_execution_time) - def test_different_providers(self): """Test retrieving job submitted with different provider.""" program_id = self._upload_program() @@ -636,12 +660,14 @@ def _upload_program(self, name=None, max_execution_time=300, """Upload a new program.""" name = name or uuid.uuid4().hex data = "def main() {}" + metadata = copy.deepcopy(self.DEFAULT_METADATA) + metadata.update(name=name) + metadata.update(is_public=is_public) + metadata.update(max_execution_time=max_execution_time) + metadata.update(description='A test program') program_id = self.runtime.upload_program( - name=name, data=data, - is_public=is_public, - max_execution_time=max_execution_time, - description="A test program") + metadata=metadata) return program_id def _run_program(self, program_id=None, inputs=None, job_classes=None, final_status=None, From ece86849fab8733c1196a38123c606a8a46bd8e0 Mon Sep 17 00:00:00 2001 From: Rathish Cholarajan Date: Mon, 25 Oct 2021 12:59:35 -0400 Subject: [PATCH 2/4] Update qiskit_ibm/runtime/ibm_runtime_service.py Co-authored-by: Jessie Yu --- qiskit_ibm/runtime/ibm_runtime_service.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit_ibm/runtime/ibm_runtime_service.py b/qiskit_ibm/runtime/ibm_runtime_service.py index 1e3cc75b5..b3dc1918d 100644 --- a/qiskit_ibm/runtime/ibm_runtime_service.py +++ b/qiskit_ibm/runtime/ibm_runtime_service.py @@ -303,6 +303,7 @@ def upload_program( * max_execution_time: Maximum execution time in seconds. Required. * description: Program description. Required. * is_public: Whether the runtime program should be visible to the public. + The default is ``False``. * spec: Specifications for backend characteristics and input parameters required to run the program, interim results and final result. From eb6a0ee3fc1e96934a5531189f2a767c5f72688a Mon Sep 17 00:00:00 2001 From: Rathish Cholarajan Date: Mon, 25 Oct 2021 13:05:07 -0400 Subject: [PATCH 3/4] Apply suggestions from code review --- qiskit_ibm/runtime/program/program_metadata_sample.json | 4 ++-- test/ibm/runtime/test_runtime.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/qiskit_ibm/runtime/program/program_metadata_sample.json b/qiskit_ibm/runtime/program/program_metadata_sample.json index 893387761..760737bc6 100644 --- a/qiskit_ibm/runtime/program/program_metadata_sample.json +++ b/qiskit_ibm/runtime/program/program_metadata_sample.json @@ -27,11 +27,11 @@ "properties": { "iteration": { "description": "Iteration number.", - "type": "int" + "type": "integer" }, "counts": { "description": "Histogram data of the circuit result.", - "type": "dict" + "type": "object" } } } diff --git a/test/ibm/runtime/test_runtime.py b/test/ibm/runtime/test_runtime.py index b5eb61e96..954755b43 100644 --- a/test/ibm/runtime/test_runtime.py +++ b/test/ibm/runtime/test_runtime.py @@ -664,7 +664,6 @@ def _upload_program(self, name=None, max_execution_time=300, metadata.update(name=name) metadata.update(is_public=is_public) metadata.update(max_execution_time=max_execution_time) - metadata.update(description='A test program') program_id = self.runtime.upload_program( data=data, metadata=metadata) From 549cab3a3c3cd9bd5710b760c7b71dc7643f4ee8 Mon Sep 17 00:00:00 2001 From: Rathish Cholarajan Date: Mon, 25 Oct 2021 19:12:27 -0400 Subject: [PATCH 4/4] Apply suggestions from code review Co-authored-by: Jessie Yu --- qiskit_ibm/runtime/ibm_runtime_service.py | 2 +- qiskit_ibm/runtime/program/program_metadata_sample.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit_ibm/runtime/ibm_runtime_service.py b/qiskit_ibm/runtime/ibm_runtime_service.py index b3dc1918d..a680afd49 100644 --- a/qiskit_ibm/runtime/ibm_runtime_service.py +++ b/qiskit_ibm/runtime/ibm_runtime_service.py @@ -296,7 +296,7 @@ def upload_program( metadata: Name of the program metadata file or metadata dictionary. A metadata file needs to be in the JSON format. The ``parameters``, ``return_values``, and ``interim_results`` should be defined as JSON Schema. - See :file:`program/program_metadata_sample.yaml` for an example. The + See :file:`program/program_metadata_sample.json` for an example. The fields in metadata are explained below. * name: Name of the program. Required. diff --git a/qiskit_ibm/runtime/program/program_metadata_sample.json b/qiskit_ibm/runtime/program/program_metadata_sample.json index 760737bc6..6449b474b 100644 --- a/qiskit_ibm/runtime/program/program_metadata_sample.json +++ b/qiskit_ibm/runtime/program/program_metadata_sample.json @@ -10,7 +10,7 @@ "type": "object", "properties": { "iterations": { - "description": "Number of iterations to run. Each iteration generates a runs a random circuit.", + "description": "Number of iterations to run. Each iteration generates and runs a random circuit.", "type": "integer" } },