Skip to content
This repository has been archived by the owner on Jul 24, 2024. It is now read-only.

Query programs by name #194

Closed
wants to merge 58 commits into from
Closed
Changes from 1 commit
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
464192f
Remove version field from runtime program (#152)
rathishcholarajan Oct 18, 2021
abb98d0
Rename isPublic to is_public when creating or reading runtime program…
rathishcholarajan Oct 18, 2021
31fdd4b
Update programId to program_id when running program (#139)
renier Oct 18, 2021
0d5ebf8
Add support to view program update date (#160)
rathishcholarajan Oct 18, 2021
767afeb
Upload runtime program using 'data' field (#157)
rathishcholarajan Oct 19, 2021
24dfa95
Read programs from "programs" array in response (#161)
rathishcholarajan Oct 19, 2021
79cd9bd
Remove version field from runtime program (#152)
rathishcholarajan Oct 18, 2021
45ab0b2
Rename isPublic to is_public when creating or reading runtime program…
rathishcholarajan Oct 18, 2021
faff4e2
Update programId to program_id when running program (#139)
renier Oct 18, 2021
bcc27fb
Add support to view program update date (#160)
rathishcholarajan Oct 18, 2021
866d045
Upload runtime program using 'data' field (#157)
rathishcholarajan Oct 19, 2021
eda2a51
Read programs from "programs" array in response (#161)
rathishcholarajan Oct 19, 2021
ab33b52
Pass program as base64 string to update (#168)
rathishcholarajan Oct 21, 2021
a8fe605
Accept JSON schema as program metadata (#158)
rathishcholarajan Oct 25, 2021
1b26e0b
Merge branch 'runtime-release-q4' of https://github.com/Qiskit-Partne…
kt474 Oct 26, 2021
df4eb81
Pass program params as object (#171)
rathishcholarajan Oct 27, 2021
e3e4315
Fix integration tests
rathishcholarajan Oct 27, 2021
f5712bc
Merge branch 'runtime-release-q4' of https://github.com/Qiskit-Partne…
kt474 Oct 28, 2021
0f95825
Use count to reduce one last extra call to API (#172)
rathishcholarajan Oct 28, 2021
b532b14
Allow updating runtime metadata in place (#188)
jyu00 Oct 29, 2021
c1065f0
Merge branch 'runtime-release-q4' of https://github.com/Qiskit-Partne…
kt474 Nov 1, 2021
f5b4e54
Remove version field from runtime program (#152)
rathishcholarajan Oct 18, 2021
93a2b85
Rename isPublic to is_public when creating or reading runtime program…
rathishcholarajan Oct 18, 2021
966d42a
Update programId to program_id when running program (#139)
renier Oct 18, 2021
031fc25
Add support to view program update date (#160)
rathishcholarajan Oct 18, 2021
1561a9b
Upload runtime program using 'data' field (#157)
rathishcholarajan Oct 19, 2021
b656ece
Read programs from "programs" array in response (#161)
rathishcholarajan Oct 19, 2021
8fb3669
Pass program as base64 string to update (#168)
rathishcholarajan Oct 21, 2021
74472c5
Accept JSON schema as program metadata (#158)
rathishcholarajan Oct 25, 2021
ee76158
Pass program params as object (#171)
rathishcholarajan Oct 27, 2021
da52271
Fix integration tests
rathishcholarajan Oct 27, 2021
f3033b8
Use count to reduce one last extra call to API (#172)
rathishcholarajan Oct 28, 2021
aea236b
Allow updating runtime metadata in place (#188)
jyu00 Oct 29, 2021
e71b402
Merge branch 'runtime-release-q4' of https://github.com/Qiskit-Partne…
kt474 Nov 2, 2021
f72a108
wip program name query
kt474 Nov 2, 2021
4102480
Allow filtering runtime jobs by program ID (#193)
rathishcholarajan Nov 4, 2021
8e8bcb3
Allow runtime program authors to retrieve program data (#174)
kt474 Nov 4, 2021
9dbbeb6
Merge branch 'runtime-release-q4' into program-name-query
kt474 Nov 4, 2021
90484cc
add test case, refactor logic
kt474 Nov 4, 2021
07f0d53
Update cache after updating program (#196)
rathishcholarajan Nov 5, 2021
304387c
Merge branch 'runtime-release-q4' into program-name-query
rathishcholarajan Nov 5, 2021
91e4ba8
add integration test
kt474 Nov 5, 2021
b458efc
update doc strings
kt474 Nov 5, 2021
4b48ec0
Update releasenotes/notes/query-program-name-823e8e7cfef44f50.yaml
kt474 Nov 5, 2021
87a5429
Update test/ibm/runtime/test_runtime.py
kt474 Nov 5, 2021
1d6e8ff
Update test/ibm/runtime/test_runtime.py
kt474 Nov 5, 2021
005a5d4
Update test/ibm/runtime/test_runtime_integration.py
kt474 Nov 5, 2021
15655ec
Update test/ibm/runtime/test_runtime_integration.py
kt474 Nov 5, 2021
35a9879
Update test/ibm/runtime/test_runtime_integration.py
kt474 Nov 5, 2021
8b01221
Update test/ibm/runtime/test_runtime.py
kt474 Nov 5, 2021
8133076
Allow filtering runtime jobs by provider (#197)
kt474 Nov 8, 2021
bbcf7b5
Merge branch 'runtime-release-q4' into program-name-query
rathishcholarajan Nov 8, 2021
7f474e9
Update qiskit_ibm/api/rest/runtime.py
rathishcholarajan Nov 8, 2021
4522d9b
Support pagination for retrieving runtime programs (#170)
kt474 Nov 8, 2021
b40df0f
Merge branch 'runtime-release-q4' into program-name-query
kt474 Nov 8, 2021
d35b56f
update program name
kt474 Nov 8, 2021
a960d8e
Merge branch 'main' into program-name-query
kt474 Nov 10, 2021
caa749a
Merge branch 'main' into program-name-query
kt474 Nov 12, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Allow updating runtime metadata in place (#188)
* update runtime metadata

* return if no data

* fix mypy

* Update releasenotes/notes/update-runtime-metadata-d2ddbcfc0d034530.yaml

Co-authored-by: Rathish Cholarajan <rathishc24@gmail.com>
jyu00 and rathishcholarajan authored Oct 29, 2021
commit b532b1476046eb7faa31f6027ae2f789675a804b
33 changes: 20 additions & 13 deletions qiskit_ibm/api/clients/runtime.py
Original file line number Diff line number Diff line change
@@ -87,17 +87,6 @@ def program_get(self, program_id: str) -> Dict:
"""
return self.api.program(program_id).get()

def program_get_data(self, program_id: str) -> Dict:
"""Return a specific program and its data.

Args:
program_id: Program ID.

Returns:
Program information, including data.
"""
return self.api.program(program_id).get_data()

def set_program_visibility(self, program_id: str, public: bool) -> None:
"""Sets a program's visibility.

@@ -145,14 +134,32 @@ def program_delete(self, program_id: str) -> None:
"""
self.api.program(program_id).delete()

def program_update(self, program_id: str, program_data: str) -> None:
def program_update(
self,
program_id: str,
program_data: str = None,
name: str = None,
description: str = None,
max_execution_time: int = None,
spec: Optional[Dict] = None
) -> None:
"""Update a program.

Args:
program_id: Program ID.
program_data: Program data (base64 encoded).
name: Name of the program.
description: Program description.
max_execution_time: Maximum execution time.
spec: Backend requirements, parameters, interim results, return values, etc.
"""
self.api.program(program_id).update(program_data)
if program_data:
self.api.program(program_id).update_data(program_data)

if any([name, description, max_execution_time, spec]):
self.api.program(program_id).update_metadata(
name=name, description=description,
max_execution_time=max_execution_time, spec=spec)

def job_get(self, job_id: str) -> Dict:
"""Get job data.
41 changes: 30 additions & 11 deletions qiskit_ibm/api/rest/runtime.py
Original file line number Diff line number Diff line change
@@ -194,15 +194,6 @@ def get(self) -> Dict[str, Any]:
url = self.get_url('self')
return self.session.get(url).json()

def get_data(self) -> Dict[str, Any]:
"""Return program information, including data.

Returns:
JSON response.
"""
url = self.get_url('data')
return self.session.get(url).json()

def make_public(self) -> None:
"""Sets a runtime program's visibility to public."""
url = self.get_url('public')
@@ -222,8 +213,8 @@ def delete(self) -> None:
url = self.get_url('self')
self.session.delete(url)

def update(self, program_data: str) -> None:
"""Update a program.
def update_data(self, program_data: str) -> None:
"""Update program data.

Args:
program_data: Program data (base64 encoded).
@@ -232,6 +223,34 @@ def update(self, program_data: str) -> None:
self.session.put(url, data=program_data,
headers={'Content-Type': 'application/octet-stream'})

def update_metadata(
self,
name: str = None,
description: str = None,
max_execution_time: int = None,
spec: Optional[Dict] = None
) -> None:
"""Update program metadata.

Args:
name: Name of the program.
description: Program description.
max_execution_time: Maximum execution time.
spec: Backend requirements, parameters, interim results, return values, etc.
"""
url = self.get_url("self")
payload: Dict = {}
if name:
payload["name"] = name
if description:
payload["description"] = description
if max_execution_time:
payload["cost"] = max_execution_time
if spec:
payload["spec"] = spec

self.session.patch(url, json=payload)


class ProgramJob(RestAdapterBase):
"""Rest adapter for program job related endpoints."""
68 changes: 61 additions & 7 deletions qiskit_ibm/runtime/ibm_runtime_service.py
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@
from typing import Dict, Callable, Optional, Union, List, Any, Type
import json
import re
import warnings

from qiskit.providers.exceptions import QiskitBackendNotFoundError
from qiskit_ibm import ibm_provider # pylint: disable=unused-import
@@ -373,30 +374,83 @@ def _read_metadata(
def update_program(
self,
program_id: str,
data: str,
data: str = None,
metadata: Optional[Union[Dict, str]] = None,
name: str = None,
description: str = None,
max_execution_time: int = None,
spec: Optional[Dict] = None
) -> None:
"""Update a runtime program.

Program metadata can be specified using the `metadata` parameter or
individual parameters, such as `name` and `description`. If the
same metadata field is specified in both places, the individual parameter
takes precedence.

Args:
program_id: Program ID.
data: Program data or path of the file containing program data to upload.
metadata: Name of the program metadata file or metadata dictionary.
name: New program name.
description: New program description.
max_execution_time: New maximum execution time.
spec: New specifications for backend characteristics, input parameters,
interim results and final result.

Raises:
RuntimeProgramNotFound: If the program doesn't exist.
QiskitRuntimeError: If the request failed.
"""
if "def main(" not in data:
# This is the program file
with open(data, "r") as file:
data = file.read()
if not any([data, metadata, name, description, max_execution_time, spec]):
warnings.warn("None of the 'data', 'metadata', 'name', 'description', "
"'max_execution_time', or 'spec' parameters is specified. "
"No update is made.")
return

if data:
if "def main(" not in data:
# This is the program file
with open(data, "r") as file:
data = file.read()
data = to_base64_string(data)

if metadata:
metadata = self._read_metadata(metadata=metadata)
combined_metadata = self._merge_metadata(
metadata=metadata, name=name, description=description,
max_execution_time=max_execution_time, spec=spec)

try:
program_data = to_base64_string(data)
self._api_client.program_update(program_id, program_data)
self._api_client.program_update(
program_id, program_data=data, **combined_metadata)
except RequestsApiError as ex:
if ex.status_code == 404:
raise RuntimeProgramNotFound(f"Program not found: {ex.message}") from None
raise QiskitRuntimeError(f"Failed to update program: {ex}") from None

def _merge_metadata(
self,
metadata: Optional[Dict] = None,
**kwargs: Any
) -> Dict:
"""Merge multiple copies of metadata.
Args:
metadata: Program metadata.
**kwargs: Additional metadata fields to overwrite.
Returns:
Merged metadata.
"""
merged = {}
metadata = metadata or {}
metadata_keys = ['name', 'max_execution_time', 'description', 'spec']
for key in metadata_keys:
if kwargs.get(key, None) is not None:
merged[key] = kwargs[key]
elif key in metadata.keys():
merged[key] = metadata[key]
return merged

def delete_program(self, program_id: str) -> None:
"""Delete a runtime program.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
features:
- |
You can now use the :meth:`qiskit_ibm.runtime.IBMRuntimeService.update_program`
method to update the metadata for a Qiskit Runtime program.
Program metadata can be specified using the ``metadata`` parameter or
individual parameters, such as ``name`` and ``description``. If the
same metadata field is specified in both places, the individual parameter
takes precedence.
35 changes: 28 additions & 7 deletions test/ibm/runtime/fake_runtime_client.py
Original file line number Diff line number Diff line change
@@ -15,7 +15,8 @@
import time
import uuid
import json
from typing import Optional
import base64
from typing import Optional, Dict
from concurrent.futures import ThreadPoolExecutor

from qiskit_ibm.credentials import Credentials
@@ -51,7 +52,7 @@ def to_dict(self, include_data=False):
'creation_date': '2021-09-13T17:27:42Z',
'update_date': '2021-09-14T19:25:32Z'}
if include_data:
out['data'] = self._data
out['data'] = base64.standard_b64decode(self._data).decode()
out['spec'] = {}
if self._backend_requirements:
out['spec']['backend_requirements'] = self._backend_requirements
@@ -255,15 +256,35 @@ def program_create(self, program_data, name, description, max_execution_time,
is_public=is_public)
return {'id': program_id}

def program_update(
self,
program_id: str,
program_data: str = None,
name: str = None,
description: str = None,
max_execution_time: int = None,
spec: Optional[Dict] = None
) -> None:
"""Update a program."""
if program_id not in self._programs:
raise RequestsApiError("Program not found", status_code=404)
program = self._programs[program_id]
program._data = program_data or program._data
program._name = name or program._name
program._description = description or program._description
program._cost = max_execution_time or program._cost
if spec:
program._backend_requirements = \
spec.get("backend_requirements") or program._backend_requirements
program._parameters = spec.get("parameters") or program._parameters
program._return_values = spec.get("return_values") or program._return_values
program._interim_results = spec.get("interim_results") or program._interim_results

def program_get(self, program_id: str):
"""Return a specific program."""
if program_id not in self._programs:
raise RequestsApiError("Program not found", status_code=404)
return self._programs[program_id].to_dict()

def program_get_data(self, program_id: str):
"""Return a specific program and its data."""
return self._programs[program_id].to_dict(iclude_data=True)
return self._programs[program_id].to_dict(include_data=True)

def program_run(
self,
47 changes: 47 additions & 0 deletions test/ibm/runtime/test_runtime.py
Original file line number Diff line number Diff line change
@@ -340,6 +340,53 @@ def test_upload_program(self):
self.assertEqual(max_execution_time, program.max_execution_time)
self.assertEqual(program.is_public, is_public)

def test_update_program(self):
"""Test updating program."""
new_data = "def main() {foo=bar}"
new_metadata = copy.deepcopy(self.DEFAULT_METADATA)
new_metadata["name"] = "test_update_program"
new_name = "name2"
new_description = "some other description"
new_cost = self.DEFAULT_METADATA["max_execution_time"] + 100
new_spec = copy.deepcopy(self.DEFAULT_METADATA["spec"])
new_spec["backend_requirements"] = {"input_allowed": "runtime"}

sub_tests = [
{"data": new_data},
{"metadata": new_metadata},
{"data": new_data, "metadata": new_metadata},
{"metadata": new_metadata, "name": new_name},
{"data": new_data, "metadata": new_metadata, "description": new_description},
{"max_execution_time": new_cost, "spec": new_spec}
]

for new_vals in sub_tests:
with self.subTest(new_vals=new_vals.keys()):
program_id = self._upload_program()
self.runtime.update_program(program_id=program_id, **new_vals)
updated = self.runtime.program(program_id, refresh=True)
if "data" in new_vals:
raw_program = self.runtime._api_client.program_get(program_id)
self.assertEqual(new_data, raw_program["data"])
if "metadata" in new_vals and "name" not in new_vals:
self.assertEqual(new_metadata["name"], updated.name)
if "name" in new_vals:
self.assertEqual(new_name, updated.name)
if "description" in new_vals:
self.assertEqual(new_description, updated.description)
if "max_execution_time" in new_vals:
self.assertEqual(new_cost, updated.max_execution_time)
if "spec" in new_vals:
raw_program = self.runtime._api_client.program_get(program_id)
self.assertEqual(new_spec, raw_program["spec"])

def test_update_program_no_new_fields(self):
"""Test updating a program without any new data."""
program_id = self._upload_program()
with warnings.catch_warnings(record=True) as warn_cm:
self.runtime.update_program(program_id=program_id)
self.assertEqual(len(warn_cm), 1)

def test_delete_program(self):
"""Test deleting program."""
program_id = self._upload_program()
27 changes: 25 additions & 2 deletions test/ibm/runtime/test_runtime_integration.py
Original file line number Diff line number Diff line change
@@ -216,8 +216,8 @@ def test_double_delete_program(self):
with self.assertRaises(RuntimeProgramNotFound):
self.provider.runtime.delete_program(program_id)

def test_update_program(self):
"""Test updating a program."""
def test_update_program_data(self):
"""Test updating program data."""
program_v1 = """
def main(backend, user_messenger, **kwargs):
return "version 1"
@@ -226,13 +226,36 @@ def main(backend, user_messenger, **kwargs):
def main(backend, user_messenger, **kwargs):
return "version 2"
"""
# TODO retrieve program data instead of run program when #66 is merged
program_id = self._upload_program(data=program_v1)
job = self._run_program(program_id=program_id)
self.assertEqual("version 1", job.result())
self.provider.runtime.update_program(program_id=program_id, data=program_v2)
job = self._run_program(program_id=program_id)
self.assertEqual("version 2", job.result())

def test_update_program_metadata(self):
"""Test updating program metadata."""
program_id = self._upload_program()
original = self.provider.runtime.program(program_id)
new_metadata = {
"name": self._get_program_name(),
"description": "test_update_program_metadata",
"max_execution_time": original.max_execution_time + 100,
"spec": {
"return_values": {
"type": "object",
"description": "Some return value"
}
}
}
self.provider.runtime.update_program(program_id=program_id, metadata=new_metadata)
updated = self.provider.runtime.program(program_id, refresh=True)
self.assertEqual(new_metadata["name"], updated.name)
self.assertEqual(new_metadata["description"], updated.description)
self.assertEqual(new_metadata["max_execution_time"], updated.max_execution_time)
self.assertEqual(new_metadata["spec"]["return_values"], updated.return_values)

def test_run_program(self):
"""Test running a program."""
job = self._run_program(final_result="foo")