diff --git a/Makefile b/Makefile index 52862acd3f..1d9099ef51 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ lint: tools/verify_headers.py qiskit_ibm_runtime test mypy: - mypy --module qiskit_ibm_runtime + mypy --module qiskit_ibm_runtime --package test style: black --check qiskit_ibm_runtime setup.py test docs/tutorials program_source diff --git a/setup.cfg b/setup.cfg index d35cda0f29..0ce3ac0b2b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,6 +2,7 @@ source = qiskit_ibm_runtime [mypy] +exclude = test/unit/test_jupyter.py python_version = 3.7 namespace_packages = True ignore_missing_imports = True @@ -15,3 +16,7 @@ strict_optional = False show_none_errors = False show_error_codes = True no_site_packages = True + +[mypy-test.*] +disallow_untyped_calls = False +disallow_untyped_defs = False \ No newline at end of file diff --git a/test/decorators.py b/test/decorators.py index 18536ee894..c467f10fe3 100644 --- a/test/decorators.py +++ b/test/decorators.py @@ -15,7 +15,7 @@ import os from dataclasses import dataclass from functools import wraps -from typing import Optional, List, Any +from typing import Callable, Optional, List, Any from unittest import SkipTest from qiskit_ibm_runtime import IBMRuntimeService @@ -71,7 +71,7 @@ def _wrapper(self, *args, **kwargs): def integration_test_setup( supported_auth: Optional[List[str]] = None, init_service: Optional[bool] = True, -): +) -> Callable: """Returns a decorator for integration test initialization. Args: diff --git a/test/e2e/test_tutorials.py b/test/e2e/test_tutorials.py index bde1293493..e84306e76e 100644 --- a/test/e2e/test_tutorials.py +++ b/test/e2e/test_tutorials.py @@ -35,7 +35,7 @@ ] -def _is_supported(auth: str, tutorial_filename: str): +def _is_supported(auth: str, tutorial_filename: str) -> bool: """Not all tutorials work for all auth types. Check if the given tutorial is supported by the targeted environment.""" allowlist = ( diff --git a/test/ibm_test_case.py b/test/ibm_test_case.py index d652e63899..398a86d984 100644 --- a/test/ibm_test_case.py +++ b/test/ibm_test_case.py @@ -19,9 +19,11 @@ import unittest from contextlib import suppress from collections import defaultdict +from typing import DefaultDict, Dict from qiskit_ibm_runtime import QISKIT_IBM_RUNTIME_LOGGER_NAME from qiskit_ibm_runtime.exceptions import IBMNotAuthorizedError +from qiskit_ibm_runtime import IBMRuntimeService from .utils import setup_test_logging from .decorators import IntegrationTestDependencies, integration_test_setup @@ -31,6 +33,11 @@ class IBMTestCase(unittest.TestCase): """Custom TestCase for use with qiskit-ibm-runtime.""" + log: logging.Logger + dependencies: IntegrationTestDependencies + service: IBMRuntimeService + program_ids: Dict[str, str] + @classmethod def setUpClass(cls): super().setUpClass() @@ -68,7 +75,7 @@ class IBMIntegrationTestCase(IBMTestCase): @classmethod @integration_test_setup() - def setUpClass(cls, dependencies: IntegrationTestDependencies): + def setUpClass(cls, dependencies: IntegrationTestDependencies) -> None: """Initial class level setup.""" # pylint: disable=arguments-differ super().setUpClass() @@ -78,8 +85,8 @@ def setUpClass(cls, dependencies: IntegrationTestDependencies): def setUp(self) -> None: """Test level setup.""" super().setUp() - self.to_delete = defaultdict(list) - self.to_cancel = defaultdict(list) + self.to_delete: DefaultDict = defaultdict(list) + self.to_cancel: DefaultDict = defaultdict(list) def tearDown(self) -> None: """Test level teardown.""" @@ -99,12 +106,12 @@ def tearDown(self) -> None: def _upload_program( self, - service, - name=None, - max_execution_time=300, - data=None, + service: IBMRuntimeService, + name: str = None, + max_execution_time: int = 300, + data: str = None, is_public: bool = False, - ): + ) -> str: """Upload a new program.""" name = name or PROGRAM_PREFIX data = data or RUNTIME_PROGRAM @@ -138,7 +145,9 @@ def tearDownClass(cls) -> None: service = cls.service service.delete_program(cls.program_ids[service.auth]) cls.log.debug( - "Deleted %s program %s", service.auth, cls.program_ids[service.auth] + "Deleted %s program %s", + service.auth, + cls.program_ids[service.auth], ) @classmethod diff --git a/test/integration/test_auth_client.py b/test/integration/test_auth_client.py index 12dc878260..6178f4c25c 100644 --- a/test/integration/test_auth_client.py +++ b/test/integration/test_auth_client.py @@ -25,41 +25,41 @@ class TestAuthClient(IBMTestCase): """Tests for the AuthClient.""" @integration_test_setup(supported_auth=["legacy"], init_service=False) - def test_valid_login(self, dependencies: IntegrationTestDependencies): + def test_valid_login(self, dependencies: IntegrationTestDependencies) -> None: """Test valid authentication.""" client = self._init_auth_client(dependencies.token, dependencies.url) self.assertTrue(client.access_token) @integration_test_setup(supported_auth=["legacy"], init_service=False) - def test_url_404(self, dependencies: IntegrationTestDependencies): + def test_url_404(self, dependencies: IntegrationTestDependencies) -> None: """Test login against a 404 URL""" url_404 = re.sub(r"/api.*$", "/api/TEST_404", dependencies.url) with self.assertRaises(ApiError): _ = self._init_auth_client(dependencies.token, url_404) @integration_test_setup(supported_auth=["legacy"], init_service=False) - def test_invalid_token(self, dependencies: IntegrationTestDependencies): + def test_invalid_token(self, dependencies: IntegrationTestDependencies) -> None: """Test login using invalid token.""" qe_token = "INVALID_TOKEN" with self.assertRaises(ApiError): _ = self._init_auth_client(qe_token, dependencies.url) @integration_test_setup(supported_auth=["legacy"], init_service=False) - def test_url_unreachable(self, dependencies: IntegrationTestDependencies): + def test_url_unreachable(self, dependencies: IntegrationTestDependencies) -> None: """Test login against an invalid (malformed) URL.""" qe_url = "INVALID_URL" with self.assertRaises(ApiError): _ = self._init_auth_client(dependencies.token, qe_url) @integration_test_setup(supported_auth=["legacy"], init_service=False) - def test_api_version(self, dependencies: IntegrationTestDependencies): + def test_api_version(self, dependencies: IntegrationTestDependencies) -> None: """Check the version of the QX API.""" client = self._init_auth_client(dependencies.token, dependencies.url) version = client.api_version() self.assertIsNotNone(version) @integration_test_setup(supported_auth=["legacy"], init_service=False) - def test_user_urls(self, dependencies: IntegrationTestDependencies): + def test_user_urls(self, dependencies: IntegrationTestDependencies) -> None: """Check the user urls of the QX API.""" client = self._init_auth_client(dependencies.token, dependencies.url) user_urls = client.user_urls() @@ -67,7 +67,7 @@ def test_user_urls(self, dependencies: IntegrationTestDependencies): self.assertTrue("http" in user_urls and "ws" in user_urls) @integration_test_setup(supported_auth=["legacy"], init_service=False) - def test_user_hubs(self, dependencies: IntegrationTestDependencies): + def test_user_hubs(self, dependencies: IntegrationTestDependencies) -> None: """Check the user hubs of the QX API.""" client = self._init_auth_client(dependencies.token, dependencies.url) user_hubs = client.user_hubs() diff --git a/test/integration/test_backend_serialization.py b/test/integration/test_backend_serialization.py index 3822b126e0..3efd6af58d 100644 --- a/test/integration/test_backend_serialization.py +++ b/test/integration/test_backend_serialization.py @@ -12,7 +12,7 @@ """Test deserializing server data.""" -from typing import Any, Dict, Optional +from typing import Any, Dict, Set, Optional import dateutil.parser @@ -88,7 +88,7 @@ def test_backend_properties(self, service): def _verify_data( self, data: Dict, good_keys: tuple, good_key_prefixes: Optional[tuple] = None - ): + ) -> None: """Verify that the input data does not contain serialized objects. Args: @@ -97,7 +97,7 @@ def _verify_data( good_key_prefixes: A list of known prefixes for keys that look like serialized objects. """ - suspect_keys = set() + suspect_keys: Set[Any] = set() _find_potential_encoded(data, "", suspect_keys) # Remove known good keys from suspect keys. for gkey in good_keys: diff --git a/test/integration/test_basic_server_paths.py b/test/integration/test_basic_server_paths.py index 50f62718a8..aa8f705a34 100644 --- a/test/integration/test_basic_server_paths.py +++ b/test/integration/test_basic_server_paths.py @@ -21,11 +21,11 @@ class TestBasicServerPaths(IBMTestCase): @classmethod @integration_test_setup(supported_auth=["legacy"]) - def setUpClass(cls, dependencies: IntegrationTestDependencies): + def setUpClass(cls, dependencies: IntegrationTestDependencies) -> None: # pylint: disable=arguments-differ super().setUpClass() - cls.service = dependencies.service - cls.hgps = list(dependencies.service._hgps.keys()) + cls.service = dependencies.service # type: ignore + cls.hgps = list(dependencies.service._hgps.keys()) # type: ignore def _require_2_hgps(self): if len(self.hgps) < 2: diff --git a/test/integration/test_proxies.py b/test/integration/test_proxies.py index 52bd121b36..1021a3c491 100644 --- a/test/integration/test_proxies.py +++ b/test/integration/test_proxies.py @@ -58,7 +58,7 @@ def tearDown(self): @integration_test_setup(supported_auth=["cloud"]) def test_proxies_cloud_runtime_client( self, dependencies: IntegrationTestDependencies - ): + ) -> None: """Should reach the proxy using RuntimeClient.""" # pylint: disable=unused-argument params = dependencies.service._client_params @@ -73,7 +73,7 @@ def test_proxies_cloud_runtime_client( @integration_test_setup(supported_auth=["legacy"], init_service=False) def test_proxies_legacy_runtime_client( self, dependencies: IntegrationTestDependencies - ): + ) -> None: """Should reach the proxy using RuntimeClient.""" service = IBMRuntimeService( auth="legacy", @@ -95,7 +95,9 @@ def test_proxies_legacy_runtime_client( self.assertIn(api_line, proxy_output) @integration_test_setup(supported_auth=["legacy"], init_service=False) - def test_proxies_account_client(self, dependencies: IntegrationTestDependencies): + def test_proxies_account_client( + self, dependencies: IntegrationTestDependencies + ) -> None: """Should reach the proxy using AccountClient.""" service = IBMRuntimeService( auth="legacy", @@ -117,7 +119,9 @@ def test_proxies_account_client(self, dependencies: IntegrationTestDependencies) self.assertIn(api_line, proxy_output) @integration_test_setup(supported_auth=["legacy"], init_service=False) - def test_proxies_authclient(self, dependencies: IntegrationTestDependencies): + def test_proxies_authclient( + self, dependencies: IntegrationTestDependencies + ) -> None: """Should reach the proxy using AuthClient.""" pproxy_desired_access_log_line_ = pproxy_desired_access_log_line( dependencies.url @@ -138,7 +142,9 @@ def test_proxies_authclient(self, dependencies: IntegrationTestDependencies): ) @integration_test_setup(supported_auth=["legacy"], init_service=False) - def test_proxies_versionclient(self, dependencies: IntegrationTestDependencies): + def test_proxies_versionclient( + self, dependencies: IntegrationTestDependencies + ) -> None: """Should reach the proxy using IBMVersionFinder.""" pproxy_desired_access_log_line_ = pproxy_desired_access_log_line( dependencies.url @@ -156,7 +162,7 @@ def test_proxies_versionclient(self, dependencies: IntegrationTestDependencies): @integration_test_setup(supported_auth=["legacy"], init_service=False) def test_invalid_proxy_port_runtime_client( self, dependencies: IntegrationTestDependencies - ): + ) -> None: """Should raise RequestApiError with ProxyError using RuntimeClient.""" params = ClientParameters( auth_type="legacy", @@ -172,7 +178,7 @@ def test_invalid_proxy_port_runtime_client( @integration_test_setup(supported_auth=["legacy"], init_service=False) def test_invalid_proxy_port_authclient( self, dependencies: IntegrationTestDependencies - ): + ) -> None: """Should raise RequestApiError with ProxyError using AuthClient.""" params = ClientParameters( auth_type="legacy", @@ -188,7 +194,7 @@ def test_invalid_proxy_port_authclient( @integration_test_setup(supported_auth=["legacy"], init_service=False) def test_invalid_proxy_port_versionclient( self, dependencies: IntegrationTestDependencies - ): + ) -> None: """Should raise RequestApiError with ProxyError using VersionClient.""" with self.assertRaises(RequestsApiError) as context_manager: version_finder = VersionClient( @@ -201,7 +207,7 @@ def test_invalid_proxy_port_versionclient( @integration_test_setup(supported_auth=["legacy"], init_service=False) def test_invalid_proxy_address_runtime_client( self, dependencies: IntegrationTestDependencies - ): + ) -> None: """Should raise RequestApiError with ProxyError using RuntimeClient.""" params = ClientParameters( auth_type="legacy", @@ -218,7 +224,7 @@ def test_invalid_proxy_address_runtime_client( @integration_test_setup(supported_auth=["legacy"], init_service=False) def test_invalid_proxy_address_authclient( self, dependencies: IntegrationTestDependencies - ): + ) -> None: """Should raise RequestApiError with ProxyError using AuthClient.""" params = ClientParameters( auth_type="legacy", @@ -234,7 +240,7 @@ def test_invalid_proxy_address_authclient( @integration_test_setup(supported_auth=["legacy"], init_service=False) def test_invalid_proxy_address_versionclient( self, dependencies: IntegrationTestDependencies - ): + ) -> None: """Should raise RequestApiError with ProxyError using VersionClient.""" with self.assertRaises(RequestsApiError) as context_manager: version_finder = VersionClient( @@ -245,7 +251,7 @@ def test_invalid_proxy_address_versionclient( self.assertIsInstance(context_manager.exception.__cause__, ProxyError) @integration_test_setup(supported_auth=["legacy"], init_service=False) - def test_proxy_urls(self, dependencies: IntegrationTestDependencies): + def test_proxy_urls(self, dependencies: IntegrationTestDependencies) -> None: """Test different forms of the proxy urls.""" test_urls = [ "http://{}:{}".format(ADDRESS, PORT), diff --git a/test/program.py b/test/program.py index 4d2078fd01..2a3a07b739 100644 --- a/test/program.py +++ b/test/program.py @@ -14,6 +14,7 @@ import uuid import copy +from qiskit_ibm_runtime import IBMRuntimeService DEFAULT_DATA = "def main() {}" @@ -50,7 +51,12 @@ } -def upload_program(service, name=None, max_execution_time=300, is_public: bool = False): +def upload_program( + service: IBMRuntimeService, + name: str = None, + max_execution_time: int = 300, + is_public: bool = False, +) -> str: """Upload a new program.""" name = name or uuid.uuid4().hex data = DEFAULT_DATA diff --git a/test/unit/mock/fake_account_client.py b/test/unit/mock/fake_account_client.py index df3b9d4173..872831e7b3 100644 --- a/test/unit/mock/fake_account_client.py +++ b/test/unit/mock/fake_account_client.py @@ -71,7 +71,7 @@ def __init__( config["backend_name"] = f"backend{idx}" self._backends.append(FakeApiBackend(config, status)) - def list_backends(self, *args, **kwargs) -> List[Dict[str, Any]]: + def list_backends(self) -> List[Dict[str, Any]]: """Return backends available for this provider.""" # pylint: disable=unused-argument return [back.configuration.copy() for back in self._backends] diff --git a/test/unit/mock/fake_legacy_auth_client.py b/test/unit/mock/fake_legacy_auth_client.py index 417636053a..05865857a7 100644 --- a/test/unit/mock/fake_legacy_auth_client.py +++ b/test/unit/mock/fake_legacy_auth_client.py @@ -22,7 +22,7 @@ def __init__(self, *args, **kwargs): """Initialize a auth runtime client.""" pass - def user_urls(self) -> Dict[str, str]: + def user_urls(self) -> Dict[str, Union[str, Dict]]: """Retrieve the API URLs from the authentication service. Returns: diff --git a/test/unit/mock/fake_runtime_client.py b/test/unit/mock/fake_runtime_client.py index d74c6d736f..c8abf5c448 100644 --- a/test/unit/mock/fake_runtime_client.py +++ b/test/unit/mock/fake_runtime_client.py @@ -18,7 +18,7 @@ import uuid from concurrent.futures import ThreadPoolExecutor from functools import wraps -from typing import Optional, Dict +from typing import Optional, Dict, Any from qiskit_ibm_runtime.api.exceptions import RequestsApiError from qiskit_ibm_runtime.utils import RuntimeEncoder @@ -337,7 +337,7 @@ def program_update( spec.get("interim_results") or program._interim_results ) - def program_get(self, program_id: str): + def program_get(self, program_id: str) -> Dict[str, Any]: """Return a specific program.""" if program_id not in self._programs: raise RequestsApiError("Program not found", status_code=404) @@ -351,7 +351,7 @@ def program_run( image: str, hgp: Optional[str], log_level: Optional[str], - ): + ) -> Dict[str, Any]: """Run the specified program.""" _ = self._get_program(program_id) job_id = uuid.uuid4().hex @@ -481,7 +481,7 @@ def list_backends(self): return self._backend_client.backend_names @cloud_only - def backend_configuration(self, backend_name: str): + def backend_configuration(self, backend_name: str) -> Dict[str, Any]: """Return the configuration of the IBM Cloud backend.""" configs = self._backend_client.list_backends() for conf in configs: @@ -490,19 +490,21 @@ def backend_configuration(self, backend_name: str): raise ValueError(f"Backend {backend_name} not found.") @cloud_only - def backend_status(self, backend_name: str): + def backend_status(self, backend_name: str) -> Dict[str, Any]: """Return the status of the IBM Cloud backend.""" return self._backend_client.backend_status(backend_name) @cloud_only - def backend_properties(self, backend_name: str, datetime=None): + def backend_properties( + self, backend_name: str, datetime: Any = None + ) -> Dict[str, Any]: """Return the properties of the IBM Cloud backend.""" if datetime: raise NotImplementedError("'datetime' is not supported with cloud runtime.") return self._backend_client.backend_properties(backend_name) @cloud_only - def backend_pulse_defaults(self, backend_name: str): + def backend_pulse_defaults(self, backend_name: str) -> Dict[str, Any]: """Return the pulse defaults of the IBM Cloud backend.""" return self._backend_client.backend_pulse_defaults(backend_name) diff --git a/test/unit/mock/fake_runtime_service.py b/test/unit/mock/fake_runtime_service.py index 6fb1ade545..a766fd5ec5 100644 --- a/test/unit/mock/fake_runtime_service.py +++ b/test/unit/mock/fake_runtime_service.py @@ -13,7 +13,7 @@ """Context managers for using with IBM Provider unit tests.""" from collections import OrderedDict -from typing import Dict +from typing import Dict, Any from unittest import mock from qiskit_ibm_runtime.accounts import Account @@ -52,11 +52,13 @@ def __init__(self, *args, **kwargs): ): super().__init__(*args, **kwargs) - def _authenticate_legacy_account(self, client_params: ClientParameters): + def _authenticate_legacy_account( + self, client_params: ClientParameters + ) -> "FakeAuthClient": """Mock authentication.""" return FakeAuthClient() - def _resolve_crn(self, account: Account): + def _resolve_crn(self, account: Account) -> None: pass def _initialize_hgps( @@ -117,9 +119,13 @@ def _discover_cloud_backends(self): return super()._discover_cloud_backends() -class FakeAuthClient: +class FakeAuthClient(AuthClient): """Fake auth client.""" + def __init__(self): # pylint: disable=super-init-not-called + # Avoid calling parent __init__ method. It has side-effects that are not supported in unit tests. + pass + def current_service_urls(self): """Return service urls.""" return {"http": "legacy_api_url", "services": {"runtime": "legacy_runtime_url"}} diff --git a/test/unit/mock/http_server.py b/test/unit/mock/http_server.py index b11857cb34..b3160d2c3a 100644 --- a/test/unit/mock/http_server.py +++ b/test/unit/mock/http_server.py @@ -15,14 +15,14 @@ import json import threading from http.server import BaseHTTPRequestHandler, HTTPServer -from typing import Dict +from typing import Dict, Any class BaseHandler(BaseHTTPRequestHandler): """Base request handler for testing.""" - good_response = {} - error_response = {} + good_response: Dict[str, Any] = {} + error_response: Dict[str, Any] = {} def _get_code(self): """Get the status code to be returned.""" @@ -65,7 +65,7 @@ def do_PUT(self): class ServerErrorOnceHandler(BaseHandler): """Request handler that returns a server error once then a good response.""" - bad_status_given = {} + bad_status_given: Dict[str, Any] = {} def _get_code(self): """Return 200 if the path was seen before, otherwise 504.""" @@ -90,7 +90,7 @@ class SimpleServer: PORT = 8123 URL = "http://{}:{}".format(IP_ADDRESS, PORT) - def __init__(self, handler_class: BaseHandler): + def __init__(self, handler_class): """SimpleServer constructor. Args: @@ -109,10 +109,10 @@ def stop(self): self.server.join(3) self.httpd.server_close() - def set_error_response(self, error_response: Dict): + def set_error_response(self, error_response: Dict) -> None: """Set the error response.""" setattr(self.httpd.RequestHandlerClass, "error_response", error_response) - def set_good_response(self, response: Dict): + def set_good_response(self, response: Dict) -> None: """Set good response.""" setattr(self.httpd.RequestHandlerClass, "good_response", response) diff --git a/test/utils.py b/test/utils.py index 6ff8431637..9564aa513e 100644 --- a/test/utils.py +++ b/test/utils.py @@ -28,7 +28,7 @@ from qiskit_ibm_runtime.exceptions import RuntimeInvalidStateError -def setup_test_logging(logger: logging.Logger, filename: str): +def setup_test_logging(logger: logging.Logger, filename: str) -> None: """Set logging to file and stdout for a logger. Args: @@ -93,7 +93,7 @@ def get_hgp(qe_token: str, qe_url: str, default: bool = True) -> HubGroupProject hgp_to_return = open_hgp if not default: # Get a non default hgp (i.e. not the default open access hgp). - hgps = service._get_hgps() + hgps = service._get_hgps() # type: ignore for hgp in hgps: if hgp != open_hgp: hgp_to_return = hgp