Skip to content

Commit 5eb138f

Browse files
authored
Fix jobs methods to use function filter correctly (#76)
* Update jobs method to use new filters, run black * Rename test folder to conform with Qiskit standards. Split unit tests into distict files to reflect file structure in the catalog. Split tests by tested functionality. * Add unit tests for jobs with function filter
1 parent e249814 commit 5eb138f

File tree

7 files changed

+157
-34
lines changed

7 files changed

+157
-34
lines changed

qiskit_ibm_catalog/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
QiskitServerless
2121
QiskitFunction
2222
"""
23+
2324
# pylint: disable=W0404
2425
from importlib_metadata import version as metadata_version, PackageNotFoundError
2526

@@ -28,7 +29,6 @@
2829
from .catalog import QiskitFunctionsCatalog
2930
from .serverless import QiskitServerless
3031

31-
3232
try:
3333
__version__ = metadata_version("qiskit_ibm_catalog")
3434
except PackageNotFoundError: # pragma: no cover

qiskit_ibm_catalog/catalog.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
1919
QiskitFunctionsCatalog
2020
"""
21+
2122
from __future__ import annotations
2223

2324
from typing import Optional, List
@@ -103,21 +104,23 @@ def list(self, **kwargs) -> List[QiskitFunction]:
103104
**{**kwargs, **{"filter": self.PRE_FILTER_KEYWORD}}
104105
)
105106

106-
def jobs(self, **kwargs) -> List[Job]:
107+
def jobs(self, function: Optional[QiskitFunction] = None, **kwargs) -> List[Job]:
107108
"""Returns list of jobs.
108109
109110
Args:
111+
function (QiskitFunction): The function that created the jobs we want to retrieve.
110112
limit (int, optional): Maximum number of jobs to return. Defaults to 10.
111113
offset (int, optional): Number of jobs to skip. Defaults to 0.
112114
status (str, optional): Filter by job status.
113115
created_after (str, optional): Filter jobs created after this timestamp.
114-
function_name (str, optional): Filter by function name.
115116
**kwargs: Additional query parameters.
116117
117118
Returns:
118119
List[Job]: jobs
119120
"""
120-
return self._client.jobs(**{**kwargs, **{"filter": self.PRE_FILTER_KEYWORD}})
121+
return self._client.jobs(
122+
function=function, **{**kwargs, **{"filter": self.PRE_FILTER_KEYWORD}}
123+
)
121124

122125
def provider_jobs(self, function: QiskitFunction, **kwargs) -> List[Job]:
123126
"""List of jobs created in this provider and function.

qiskit_ibm_catalog/serverless.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
1919
QiskitServerless
2020
"""
21+
2122
# pylint: disable=duplicate-code
2223
from __future__ import annotations
2324

@@ -118,13 +119,15 @@ def list(self, **kwargs) -> List[QiskitFunction]:
118119
**{**kwargs, **{"filter": self.PRE_FILTER_KEYWORD}}
119120
)
120121

121-
def jobs(self, **kwargs) -> List[Job]:
122+
def jobs(self, function: Optional[QiskitFunction] = None, **kwargs) -> List[Job]:
122123
"""Returns list of jobs.
123124
124125
Returns:
125126
List[Job]: jobs
126127
"""
127-
return self._client.jobs(**{**kwargs, **{"filter": self.PRE_FILTER_KEYWORD}})
128+
return self._client.jobs(
129+
function=function, **{**kwargs, **{"filter": self.PRE_FILTER_KEYWORD}}
130+
)
128131

129132
def provider_jobs(self, function: QiskitFunction, **kwargs) -> List[Job]:
130133
"""List of jobs created in this provider and function.
File renamed without changes.
File renamed without changes.

test/test_catalog.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# This code is part of Qiskit.
2+
#
3+
# (C) Copyright IBM 2024.
4+
#
5+
# This code is licensed under the Apache License, Version 2.0. You may
6+
# obtain a copy of this license in the LICENSE.txt file in the root directory
7+
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8+
#
9+
# Any modifications or derivative works of this code must retain this
10+
# copyright notice, and modified files need to carry a notice indicating
11+
# that they have been altered from the originals.
12+
13+
"""Tests for QiskitFunctionsCatalog class."""
14+
15+
from unittest import TestCase, mock
16+
17+
from qiskit_serverless import IBMServerlessClient
18+
from qiskit_serverless.core import Job, QiskitFunction
19+
from qiskit_ibm_catalog import QiskitFunctionsCatalog
20+
21+
22+
class TestCatalog(TestCase):
23+
"""TestCatalog."""
24+
25+
@mock.patch(
26+
"qiskit_serverless.core.clients.serverless_client.ServerlessClient._verify_credentials"
27+
)
28+
def test_authentication(self, _verify_mock):
29+
"""Tests authentication of serverless client."""
30+
catalog = QiskitFunctionsCatalog(token="token", instance="instance")
31+
32+
# pylint: disable=protected-access
33+
self.assertEqual(catalog._client.token, "token")
34+
self.assertEqual(catalog._client.instance, "instance")
35+
# pylint: enable=protected-access
36+
37+
@mock.patch.object(
38+
IBMServerlessClient,
39+
"functions",
40+
return_value=[QiskitFunction("the-ultimate-answer")],
41+
)
42+
@mock.patch.object(
43+
IBMServerlessClient, "jobs", return_value=[Job("42", mock.MagicMock())]
44+
)
45+
@mock.patch(
46+
"qiskit_serverless.core.clients.serverless_client.ServerlessClient._verify_credentials"
47+
)
48+
def test_basic_functions(self, _verify_mock, jobs_mock, functions_list_mock):
49+
"""Tests basic function of catalog."""
50+
catalog = QiskitFunctionsCatalog(token="token", instance="instance")
51+
52+
jobs = catalog.jobs(limit=10)
53+
functions = catalog.list()
54+
55+
jobs_mock.assert_called()
56+
called_kwargs = jobs_mock.call_args.kwargs
57+
assert called_kwargs["filter"] == "catalog"
58+
assert called_kwargs["limit"] == 10
59+
functions_list_mock.assert_called_with(**{"filter": "catalog"})
60+
61+
self.assertEqual(len(jobs), 1)
62+
self.assertEqual(len(functions), 1)
63+
64+
@mock.patch.object(
65+
IBMServerlessClient, "jobs", return_value=[Job("42", mock.MagicMock())]
66+
)
67+
@mock.patch(
68+
"qiskit_serverless.core.clients.serverless_client.ServerlessClient._verify_credentials"
69+
)
70+
def test_jobs_with_function_filter(self, _verify_mock, jobs_mock):
71+
"""Tests that 'function' is forwarded and 'serverless' filter is enforced."""
72+
catalog = QiskitFunctionsCatalog(token="token", instance="instance")
73+
74+
my_function = QiskitFunction("my-func")
75+
76+
# Call jobs with both function and an additional kwarg
77+
jobs = catalog.jobs(function=my_function, limit=7)
78+
79+
jobs_mock.assert_called()
80+
called_args = jobs_mock.call_args.args
81+
called_kwargs = jobs_mock.call_args.kwargs
82+
83+
# Positional args should be empty because we pass by keyword
84+
assert called_args == ()
85+
86+
# Ensure 'function' got forwarded and 'filter' remained enforced
87+
assert called_kwargs["function"] is my_function
88+
assert called_kwargs["filter"] == "catalog"
89+
assert called_kwargs["limit"] == 7
90+
91+
self.assertEqual(len(jobs), 1)
92+
self.assertIsInstance(jobs[0], Job)
93+
self.assertEqual(jobs[0].job_id, "42")
Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,44 +10,32 @@
1010
# copyright notice, and modified files need to carry a notice indicating
1111
# that they have been altered from the originals.
1212

13-
"""Tests for wrappers."""
13+
"""Tests for QiskitServerless class."""
1414

1515
from unittest import TestCase, mock
1616

1717
from qiskit_serverless import IBMServerlessClient
1818
from qiskit_serverless.core import Job, QiskitFunction
19-
from qiskit_ibm_catalog import QiskitServerless, QiskitFunctionsCatalog
19+
from qiskit_ibm_catalog import QiskitServerless
2020

2121

22-
class TestCatalog(TestCase):
23-
"""TestCatalog."""
22+
class TestServerless(TestCase):
23+
"""TestServerless."""
2424

25-
@mock.patch.object(
26-
IBMServerlessClient,
27-
"functions",
28-
return_value=[QiskitFunction("the-ultimate-answer")],
29-
)
30-
@mock.patch.object(
31-
IBMServerlessClient, "jobs", return_value=[Job("42", mock.MagicMock())]
32-
)
3325
@mock.patch(
3426
"qiskit_serverless.core.clients.serverless_client.ServerlessClient._verify_credentials"
3527
)
36-
def test_basic_functions(self, _token_mock, jobs_mock, functions_list_mock):
37-
"""Tests basic function of catalog."""
38-
catalog = QiskitFunctionsCatalog(token="token", instance="instance")
39-
jobs = catalog.jobs(limit=10)
40-
functions = catalog.list()
41-
42-
jobs_mock.assert_called_with(**{"filter": "catalog", "limit": 10})
43-
functions_list_mock.assert_called_with(**{"filter": "catalog"})
44-
45-
self.assertEqual(len(jobs), 1)
46-
self.assertEqual(len(functions), 1)
47-
28+
def test_authentication(self, _verify_mock):
29+
"""Tests authentication of serverless client."""
30+
serverless = QiskitServerless(
31+
token="token", instance="instance", host="http://host"
32+
)
4833

49-
class TestServerless(TestCase):
50-
"""TestServerless."""
34+
# pylint: disable=protected-access
35+
self.assertEqual(serverless._client.token, "token")
36+
self.assertEqual(serverless._client.instance, "instance")
37+
self.assertEqual(serverless._client.host, "http://host")
38+
# pylint: enable=protected-access
5139

5240
@mock.patch.object(
5341
IBMServerlessClient,
@@ -60,7 +48,7 @@ class TestServerless(TestCase):
6048
@mock.patch(
6149
"qiskit_serverless.core.clients.serverless_client.ServerlessClient._verify_credentials"
6250
)
63-
def test_basic_functions(self, _token_mock, jobs_mock, functions_list_mock):
51+
def test_basic_functions(self, _verify_mock, jobs_mock, functions_list_mock):
6452
"""Tests basic function of serverless client."""
6553
serverless = QiskitServerless(
6654
token="token", instance="instance", host="http://host"
@@ -75,8 +63,44 @@ def test_basic_functions(self, _token_mock, jobs_mock, functions_list_mock):
7563
jobs = serverless.jobs(limit=10)
7664
functions = serverless.list()
7765

78-
jobs_mock.assert_called_with(**{"filter": "serverless", "limit": 10})
66+
jobs_mock.assert_called()
67+
called_kwargs = jobs_mock.call_args.kwargs
68+
assert called_kwargs["filter"] == "serverless"
69+
assert called_kwargs["limit"] == 10
7970
functions_list_mock.assert_called_with(**{"filter": "serverless"})
8071

8172
self.assertEqual(len(jobs), 1)
8273
self.assertEqual(len(functions), 1)
74+
75+
@mock.patch.object(
76+
IBMServerlessClient, "jobs", return_value=[Job("42", mock.MagicMock())]
77+
)
78+
@mock.patch(
79+
"qiskit_serverless.core.clients.serverless_client.ServerlessClient._verify_credentials"
80+
)
81+
def test_jobs_with_function_filter(self, _verify_mock, jobs_mock):
82+
"""Tests that 'function' is forwarded and 'serverless' filter is enforced."""
83+
serverless = QiskitServerless(
84+
token="token", instance="instance", host="http://host"
85+
)
86+
87+
my_function = QiskitFunction("my-func")
88+
89+
# Call jobs with both function and an additional kwarg
90+
jobs = serverless.jobs(function=my_function, limit=7)
91+
92+
jobs_mock.assert_called()
93+
called_args = jobs_mock.call_args.args
94+
called_kwargs = jobs_mock.call_args.kwargs
95+
96+
# Positional args should be empty because we pass by keyword
97+
assert called_args == ()
98+
99+
# Ensure 'function' got forwarded and 'filter' remained enforced
100+
assert called_kwargs["function"] is my_function
101+
assert called_kwargs["filter"] == "serverless"
102+
assert called_kwargs["limit"] == 7
103+
104+
self.assertEqual(len(jobs), 1)
105+
self.assertIsInstance(jobs[0], Job)
106+
self.assertEqual(jobs[0].job_id, "42")

0 commit comments

Comments
 (0)