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

Allow filtering runtime jobs by provider #197

Merged
11 changes: 9 additions & 2 deletions qiskit_ibm/api/clients/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,10 @@ def jobs_get(
limit: int = None,
skip: int = None,
pending: bool = None,
program_id: str = None
program_id: str = None,
hub: str = None,
group: str = None,
project: str = None
) -> Dict:
"""Get job data for all jobs.

Expand All @@ -189,11 +192,15 @@ def jobs_get(
pending: Returns 'QUEUED' and 'RUNNING' jobs if True,
returns 'DONE', 'CANCELLED' and 'ERROR' jobs if False.
program_id: Filter by Program ID.
hub: Filter by hub - hub, group, and project must all be specified.
group: Filter by group - hub, group, and project must all be specified.
project: Filter by project - hub, group, and project must all be specified.

Returns:
JSON response.
"""
return self.api.jobs_get(limit=limit, skip=skip, pending=pending, program_id=program_id)
return self.api.jobs_get(limit=limit, skip=skip, pending=pending,
program_id=program_id, hub=hub, group=group, project=project)

def job_results(self, job_id: str) -> str:
"""Get the results of a program job.
Expand Down
10 changes: 9 additions & 1 deletion qiskit_ibm/api/rest/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,10 @@ def jobs_get(
limit: int = None,
skip: int = None,
pending: bool = None,
program_id: str = None
program_id: str = None,
hub: str = None,
group: str = None,
project: str = None
) -> Dict:
"""Get a list of job data.

Expand All @@ -149,6 +152,9 @@ def jobs_get(
pending: Returns 'QUEUED' and 'RUNNING' jobs if True,
returns 'DONE', 'CANCELLED' and 'ERROR' jobs if False.
program_id: Filter by Program ID.
hub: Filter by hub - hub, group, and project must all be specified.
group: Filter by group - hub, group, and project must all be specified.
project: Filter by project - hub, group, and project must all be specified.

Returns:
JSON response.
Expand All @@ -163,6 +169,8 @@ def jobs_get(
payload['pending'] = 'true' if pending else 'false'
if program_id:
payload['program'] = program_id
if all([hub, group, project]):
payload['provider'] = f"{hub}/{group}/{project}"
return self.session.get(url, params=payload).json()

def logout(self) -> None:
Expand Down
22 changes: 20 additions & 2 deletions qiskit_ibm/runtime/ibm_runtime_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,10 @@ def jobs(
limit: Optional[int] = 10,
skip: int = 0,
pending: bool = None,
program_id: str = None
program_id: str = None,
hub: str = None,
group: str = None,
project: str = None
) -> List[RuntimeJob]:
"""Retrieve all runtime jobs, subject to optional filtering.

Expand All @@ -537,10 +540,22 @@ def jobs(
jobs are included. If ``False``, 'DONE', 'CANCELLED' and 'ERROR' jobs
are included.
program_id: Filter by Program ID.
hub: Filter by hub - hub, group, and project must all be specified.
group: Filter by group - hub, group, and project must all be specified.
project: Filter by project - hub, group, and project must all be specified.

Returns:
A list of runtime jobs.

Raises:
IBMInputValueError: If any but not all of the parameters ``hub``, ``group``
and ``project`` are given.
"""
if any([hub, group, project]) and not all([hub, group, project]):
raise IBMInputValueError('Hub, group and project '
'parameters must all be specified. '
'hub = "{}", group = "{}", project = "{}"'
.format(hub, group, project))
job_responses = [] # type: List[Dict[str, Any]]
current_page_limit = limit or 20
offset = skip
Expand All @@ -550,7 +565,10 @@ def jobs(
limit=current_page_limit,
skip=offset,
pending=pending,
program_id=program_id)
program_id=program_id,
hub=hub,
group=group,
project=project)
job_page = jobs_response["jobs"]
# count is the total number of jobs that would be returned if
# there was no limit or skip
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
features:
- |
You can now pass ``hub``, ``group``, and ``project`` parameters to
:meth:`qiskit_ibm.runtime.IBMRuntimeService.jobs` to filter jobs.
26 changes: 22 additions & 4 deletions test/ibm/runtime/fake_runtime_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,13 +214,23 @@ def _auto_progress(self):
class BaseFakeRuntimeClient:
"""Base class for faking the runtime client."""

def __init__(self, job_classes=None, final_status=None, job_kwargs=None):
def __init__(self, job_classes=None, final_status=None, job_kwargs=None,
hub=None, group=None, project=None):
"""Initialize a fake runtime client."""
self._programs = {}
self._jobs = {}
self._job_classes = job_classes or []
self._final_status = final_status
self._job_kwargs = job_kwargs or {}
self._hub = hub
self._group = group
self._project = project

def set_hgp(self, hub, group, project):
"""Set hub, group and project"""
self._hub = hub
self._group = group
self._project = project

def set_job_classes(self, classes):
"""Set job classes to use."""
Expand Down Expand Up @@ -297,9 +307,12 @@ def program_run(
"""Run the specified program."""
job_id = uuid.uuid4().hex
job_cls = self._job_classes.pop(0) if len(self._job_classes) > 0 else BaseFakeRuntimeJob
hub = self._hub or credentials.hub
group = self._group or credentials.group
project = self._project or credentials.project
job = job_cls(job_id=job_id, program_id=program_id,
hub=credentials.hub, group=credentials.group,
project=credentials.project, backend_name=backend_name,
hub=hub, group=group,
project=project, backend_name=backend_name,
params=params, final_status=self._final_status, image=image,
**self._job_kwargs)
self._jobs[job_id] = job
Expand All @@ -315,7 +328,8 @@ def job_get(self, job_id):
"""Get the specific job."""
return self._get_job(job_id).to_dict()

def jobs_get(self, limit=None, skip=None, pending=None, program_id=None):
def jobs_get(self, limit=None, skip=None, pending=None, program_id=None,
hub=None, group=None, project=None):
"""Get all jobs."""
pending_statuses = ['QUEUED', 'RUNNING']
returned_statuses = ['COMPLETED', 'FAILED', 'CANCELLED']
Expand All @@ -330,6 +344,10 @@ def jobs_get(self, limit=None, skip=None, pending=None, program_id=None):
if program_id:
jobs = [job for job in jobs if job._program_id == program_id]
count = len(jobs)
if all([hub, group, project]):
jobs = [job for job in jobs if
job._hub == hub and job._group == group and job._project == project]
count = len(jobs)
jobs = jobs[skip:limit+skip]
return {"jobs": [job.to_dict() for job in jobs],
"count": count}
Expand Down
20 changes: 19 additions & 1 deletion test/ibm/runtime/test_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,22 @@ def test_jobs_filter_by_program_id(self):
self.assertEqual(program_id, rjobs[0].program_id)
self.assertEqual(1, len(rjobs))

def test_jobs_filter_by_provider(self):
"""Test retrieving jobs by provider."""
program_id = self._upload_program()
job = self._run_program(program_id=program_id,
hub="defaultHub", group="defaultGroup", project="defaultProject")
job.wait_for_final_state()
rjobs = self.runtime.jobs(program_id=program_id,
hub="defaultHub", group="defaultGroup", project="defaultProject")
self.assertEqual(program_id, rjobs[0].program_id)
self.assertEqual(1, len(rjobs))
rjobs = self.runtime.jobs(program_id=program_id,
hub="test", group="test", project="test")
self.assertFalse(rjobs)
with self.assertRaises(IBMInputValueError):
self.runtime.jobs(hub="defaultHub")
rathishcholarajan marked this conversation as resolved.
Show resolved Hide resolved

def test_cancel_job(self):
"""Test canceling a job."""
job = self._run_program(job_classes=CancelableRuntimeJob)
Expand Down Expand Up @@ -725,13 +741,15 @@ def _upload_program(self, name=None, max_execution_time=300,
return program_id

def _run_program(self, program_id=None, inputs=None, job_classes=None, final_status=None,
decoder=None, image=""):
decoder=None, image="", hub=None, group=None, project=None):
"""Run a program."""
options = {'backend_name': "some_backend"}
if final_status is not None:
self.runtime._api_client.set_final_status(final_status)
elif job_classes:
self.runtime._api_client.set_job_classes(job_classes)
elif all([hub, group, project]):
self.runtime._api_client.set_hgp(hub, group, project)
if program_id is None:
program_id = self._upload_program()
job = self.runtime.run(program_id=program_id, inputs=inputs,
Expand Down
16 changes: 16 additions & 0 deletions test/ibm/runtime/test_runtime_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,22 @@ def test_retrieve_jobs_by_program_id(self):
self.assertEqual(program_id, rjobs[0].program_id)
self.assertEqual(1, len(rjobs))

def test_jobs_filter_by_provider(self):
"""Test retrieving jobs by provider."""
hub = self.provider.credentials.hub
group = self.provider.credentials.group
project = self.provider.credentials.project
program_id = self._upload_program()
job = self._run_program(program_id=program_id)
job.wait_for_final_state()
rjobs = self.provider.runtime.jobs(program_id=program_id,
hub=hub, group=group, project=project)
self.assertEqual(program_id, rjobs[0].program_id)
self.assertEqual(1, len(rjobs))
rjobs = self.provider.runtime.jobs(program_id=program_id,
hub="test", group="test", project="test")
self.assertFalse(rjobs)

def test_cancel_job_queued(self):
"""Test canceling a queued job."""
_ = self._run_program(iterations=10)
Expand Down