Skip to content
Closed
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
07460d7
query programs by name
kt474 Nov 17, 2021
85be645
refactor cache logic
kt474 Nov 17, 2021
7413496
Merge branch 'main' into query-program-name
rathishcholarajan Nov 23, 2021
3c282e1
Merge branch 'main' into query-program-name
rathishcholarajan Nov 23, 2021
d2cc513
Merge branch 'main' into query-program-name
kt474 Nov 29, 2021
f733ee0
fix lint
kt474 Nov 29, 2021
b26f544
update tests
kt474 Nov 29, 2021
17d1f47
Merge branch 'main' into query-program-name
rathishcholarajan Nov 29, 2021
610e8a0
refactor programs cache
kt474 Nov 30, 2021
ad53714
fix lint
kt474 Nov 30, 2021
7be7a77
Merge branch 'main' into query-program-name
kt474 Nov 30, 2021
bf87cae
unrelated lint fix
kt474 Nov 30, 2021
5a1d7c2
Merge branch 'query-program-name' of https://github.com/Qiskit/qiskit…
kt474 Nov 30, 2021
b9933c9
Merge branch 'main' into query-program-name
rathishcholarajan Dec 1, 2021
830ddfc
Merge branch 'main' into query-program-name
rathishcholarajan Dec 1, 2021
ebacbee
update cache logic
kt474 Dec 2, 2021
d48e892
Merge branch 'main' into query-program-name
rathishcholarajan Dec 2, 2021
0ab06e6
update cache logic
kt474 Dec 5, 2021
c188a14
Merge branch 'main' into query-program-name
kt474 Dec 5, 2021
8d287d9
Update qiskit_ibm_runtime/ibm_runtime_service.py
kt474 Dec 7, 2021
861cf00
update retrieve programs method
kt474 Dec 7, 2021
af309c7
refactor logic
kt474 Dec 8, 2021
ea04aa8
Merge branch 'main' into query-program-name
kt474 Dec 16, 2021
fca5a98
Merge branch 'main' into query-program-name
kt474 Jan 4, 2022
0bc0bca
Merge branch 'main' into query-program-name
kt474 Jan 5, 2022
6671b7d
add tests back in
kt474 Jan 5, 2022
851caa2
Merge branch 'main' into query-program-name
kt474 Jan 6, 2022
d33595e
Merge branch 'main' into query-program-name
kt474 Jan 7, 2022
5e827fe
fix list programs test
kt474 Jan 7, 2022
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
8 changes: 5 additions & 3 deletions qiskit_ibm_runtime/api/clients/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,19 @@ def __init__(
)
self.api = Runtime(self._session)

def list_programs(self, limit: int = None, skip: int = None) -> Dict[str, Any]:
def list_programs(
self, name: str = "", limit: int = None, skip: int = None
) -> Dict[str, Any]:
"""Return a list of runtime programs.

Args:
name: Name of the program.
limit: The number of programs to return.
skip: The number of programs to skip.

Returns:
A list of runtime programs.
"""
return self.api.list_programs(limit, skip)
return self.api.list_programs(name, limit, skip)

def program_create(
self,
Expand Down
10 changes: 7 additions & 3 deletions qiskit_ibm_runtime/api/rest/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,22 @@ def program_job(self, job_id: str) -> "ProgramJob":
"""
return ProgramJob(self.session, job_id)

def list_programs(self, limit: int = None, skip: int = None) -> Dict[str, Any]:
def list_programs(
self, name: str = "", limit: int = None, skip: int = None
) -> Dict[str, Any]:
"""Return a list of runtime programs.

Args:
name: Name of the program.
limit: The number of programs to return.
skip: The number of programs to skip.

Returns:
A list of runtime programs.
"""
url = self.get_url("programs")
payload: Dict[str, int] = {}
payload: Dict[str, Union[int, str]] = {}
if name:
payload["name"] = name
if limit:
payload["limit"] = limit
if skip:
Expand Down
68 changes: 47 additions & 21 deletions qiskit_ibm_runtime/ibm_runtime_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ def __init__(
)
self._backends: Dict[str, "ibm_backend.IBMBackend"] = {}
self._api_client = None
self._programs_first_call = True
Comment thread
rathishcholarajan marked this conversation as resolved.
Outdated
Comment thread
kt474 marked this conversation as resolved.
Outdated
hgps = self._get_hgps()
for hgp in hgps:
for name, backend in hgp.backends.items():
Expand Down Expand Up @@ -811,6 +812,7 @@ def pprint_programs(
self,
refresh: bool = False,
detailed: bool = False,
name: Optional[str] = "",
limit: int = 20,
skip: int = 0,
) -> None:
Expand All @@ -820,11 +822,12 @@ def pprint_programs(
refresh: If ``True``, re-query the server for the programs. Otherwise
return the cached value.
detailed: If ``True`` print all details about available runtime programs.
name: Only retrieve programs with the exact program name given.
limit: The number of programs returned at a time. Default and maximum
value of 20.
skip: The number of programs to skip.
"""
programs = self.programs(refresh, limit, skip)
programs = self.programs(refresh, name, limit, skip)
for prog in programs:
print("=" * 50)
if detailed:
Expand All @@ -837,7 +840,11 @@ def pprint_programs(
print(f" Description: {prog.description}")

def programs(
self, refresh: bool = False, limit: int = 20, skip: int = 0
self,
refresh: bool = False,
name: Optional[str] = "",
limit: int = 20,
skip: int = 0,
) -> List[RuntimeProgram]:
"""Return available runtime programs.

Expand All @@ -846,35 +853,29 @@ def programs(
Args:
refresh: If ``True``, re-query the server for the programs. Otherwise
return the cached value.
name: Only retrieve programs with the exact program name given.
limit: The number of programs returned at a time. ``None`` means no limit.
skip: The number of programs to skip.

Returns:
A list of runtime programs.
"""
already_retrieved = False
if skip is None:
skip = 0
if not self._programs or refresh:
self._programs = {}
current_page_limit = 20
offset = 0
while True:
response = self._api_client.list_programs(
limit=current_page_limit, skip=offset
)
program_page = response.get("programs", [])
# count is the total number of programs that would be returned if
# there was no limit or skip
count = response.get("count", 0)
for prog_dict in program_page:
program = self._to_program(prog_dict)
self._programs[program.program_id] = program
if len(self._programs) == count:
# Stop if there are no more programs returned by the server.
break
offset += len(program_page)
if not self._programs or (refresh and not name):
self._retrieve_programs()
already_retrieved = True
if limit is None:
limit = len(self._programs)
if name:
if refresh and not already_retrieved:
self._retrieve_programs(name)
matched_programs = []
for program in list(self._programs.values()):
if program.name == name:
matched_programs.append(program)
return matched_programs[skip : limit + skip]
return list(self._programs.values())[skip : limit + skip]

def program(self, program_id: str, refresh: bool = False) -> RuntimeProgram:
Expand Down Expand Up @@ -908,6 +909,31 @@ def program(self, program_id: str, refresh: bool = False) -> RuntimeProgram:

return self._programs[program_id]

def _retrieve_programs(self, name: str = "") -> None:
"""Make an API call to fetch programs.

Args:
name: Name of program.
"""
self._programs = {}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should not modify self._programs right? If we call this function with name parameter then we will lose all programs already in cache that don’t match the name. This function should return programs and they should be stored in self._programs in the calling function above when called without name and stored in temp variable when called with name.

current_page_limit = 20
offset = 0
while True:
response = self._api_client.list_programs(
name=name, limit=current_page_limit, skip=offset
)
program_page = response.get("programs", [])
# count is the total number of programs that would be returned if
# there was no limit or skip
count = response.get("count", 0)
for prog_dict in program_page:
program = self._to_program(prog_dict)
self._programs[program.program_id] = program
if len(self._programs) == count:
# Stop if there are no more programs returned by the server.
break
offset += len(program_page)

def _to_program(self, response: Dict) -> RuntimeProgram:
"""Convert server response to ``RuntimeProgram`` instances.

Expand Down
8 changes: 8 additions & 0 deletions releasenotes/notes/query-program-name-22b97d6b4c5731d2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
upgrade:
- |
The ``name`` parameter has been added to
:meth:`qiskit_ibm_runtime.IBMRuntimeService.programs` and
:meth:`qiskit_ibm_runtime.IBMRuntimeService.pprint_programs`
which can be used to filter by a specific program name. The
``name`` given must be an exact match with an actual program name.
7 changes: 5 additions & 2 deletions test/ibm/runtime/fake_runtime_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,11 +265,14 @@ def set_final_status(self, final_status):
"""Set job status to passed in final status instantly."""
self._final_status = final_status

def list_programs(self, limit, skip):
def list_programs(self, name, limit, skip):
"""List all programs."""
programs = []
for prog in self._programs.values():
programs.append(prog.to_dict())
if not name:
programs.append(prog.to_dict())
if name == prog.to_dict()["name"]:
programs.append(prog.to_dict())
return {"programs": programs[skip : limit + skip], "count": len(self._programs)}

def program_create(
Expand Down
10 changes: 10 additions & 0 deletions test/ibm/runtime/test_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,16 @@ def test_list_programs(self):
all_ids = [prog.program_id for prog in programs]
self.assertIn(program_id, all_ids)

def test_filter_programs_with_program_name(self):
"""Test filter programs with the program name"""
program_id = self._upload_program(name="qiskit-test-sample")
programs = self.service.programs(name="qiskit-test-sample")
all_ids = [prog.program_id for prog in programs]
self.assertIn(program_id, all_ids)
programs = self.service.programs(name="qiskit-test")
all_ids = [prog.program_id for prog in programs]
self.assertNotIn(program_id, all_ids)

def test_list_programs_with_limit_skip(self):
"""Test listing programs with limit and skip."""
program_1 = self._upload_program()
Expand Down
10 changes: 10 additions & 0 deletions test/ibm/runtime/test_runtime_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,16 @@ def test_list_programs(self):
found = True
self.assertTrue(found, f"Program {self.program_id} not found!")

def test_filter_programs_with_program_name(self):
"""Test filter programs with program name."""
program_id = self._upload_program(name="qiskit-test-sample")
programs = self.service.programs(name="qiskit-test-sample")
all_ids = [prog.program_id for prog in programs]
self.assertIn(program_id, all_ids)
programs = self.service.programs(name="qiskit-test")
all_ids = [prog.program_id for prog in programs]
self.assertNotIn(program_id, all_ids)

def test_list_programs_with_limit_skip(self):
"""Test listing programs with limit and skip."""
self._upload_program()
Expand Down