From 4031f7c017dc1df57d6b2f8bd0639df39c4a4a4f Mon Sep 17 00:00:00 2001 From: kukushechkin Date: Mon, 29 Aug 2022 10:48:16 +0300 Subject: [PATCH 1/3] add requests timeout, fix pylint ci --- src/iqm_client/iqm_client.py | 13 +++++++++---- tests/conftest.py | 10 +++++++--- tests/test_iqm_client.py | 4 +++- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/iqm_client/iqm_client.py b/src/iqm_client/iqm_client.py index afa0152c3..4eccde8b5 100644 --- a/src/iqm_client/iqm_client.py +++ b/src/iqm_client/iqm_client.py @@ -119,6 +119,8 @@ import requests from pydantic import BaseModel, Field +REQUESTS_TIMEOUT = 10 + DEFAULT_TIMEOUT_SECONDS = 900 SECONDS_BETWEEN_CALLS = 1 REFRESH_MARGIN_SECONDS = 5 @@ -562,6 +564,7 @@ def submit_circuits( join(self._base_url, 'jobs'), json=data.dict(exclude_none=True), headers=headers, + timeout=REQUESTS_TIMEOUT ) if result.status_code == 401: @@ -586,7 +589,8 @@ def get_run(self, job_id: UUID) -> RunResult: bearer_token = self._get_bearer_token() result = requests.get( join(self._base_url, 'jobs/', str(job_id)), - headers=None if not bearer_token else {'Authorization': bearer_token} + headers=None if not bearer_token else {'Authorization': bearer_token}, + timeout=REQUESTS_TIMEOUT ) result.raise_for_status() result = RunResult.from_dict(result.json()) @@ -613,7 +617,8 @@ def get_run_status(self, job_id: UUID) -> RunStatus: bearer_token = self._get_bearer_token() result = requests.get( join(self._base_url, 'jobs/', str(job_id), 'status'), - headers=None if not bearer_token else {'Authorization': bearer_token} + headers=None if not bearer_token else {'Authorization': bearer_token}, + timeout=REQUESTS_TIMEOUT ) result.raise_for_status() result = RunStatus.from_dict(result.json()) @@ -667,7 +672,7 @@ def close_auth_session(self) -> bool: url = f'{self._credentials.auth_server_url}/realms/{AUTH_REALM}/protocol/openid-connect/logout' data = AuthRequest(client_id=AUTH_CLIENT_ID, refresh_token=self._credentials.refresh_token) - result = requests.post(url, data=data.dict(exclude_none=True)) + result = requests.post(url, data=data.dict(exclude_none=True), timeout=REQUESTS_TIMEOUT) if result.status_code not in [200, 204]: raise ClientAuthenticationError(f'Logout failed, {result.text}') self._credentials.access_token = None @@ -728,7 +733,7 @@ def _update_tokens(self): ) url = f'{self._credentials.auth_server_url}/realms/{AUTH_REALM}/protocol/openid-connect/token' - result = requests.post(url, data=data.dict(exclude_none=True)) + result = requests.post(url, data=data.dict(exclude_none=True), timeout=REQUESTS_TIMEOUT) if result.status_code != 200: raise ClientAuthenticationError(f'Failed to update tokens, {result.text}') tokens = result.json() diff --git a/tests/conftest.py b/tests/conftest.py index 5199dd26b..774003a06 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -30,6 +30,8 @@ from iqm_client import AUTH_CLIENT_ID, AUTH_REALM, AuthRequest, GrantType +REQUESTS_TIMEOUT = 10 + existing_run = UUID('3c3fcda3-e860-46bf-92a4-bcc59fa76ce9') missing_run = UUID('059e4186-50a3-4e6c-ba1f-37fe6afbdfc2') @@ -213,7 +215,8 @@ def prepare_tokens( } when(requests).post( f'{credentials["auth_server_url"]}/realms/{AUTH_REALM}/protocol/openid-connect/token', - data=request_data.dict(exclude_none=True) + data=request_data.dict(exclude_none=True), + timeout=REQUESTS_TIMEOUT ).thenReturn(MockJsonResponse(status_code, tokens)) return tokens @@ -249,7 +252,7 @@ def expect_status_request(url: str, access_token: Optional[str], times: int = 1) """ job_id = uuid4() headers = None if access_token is None else {'Authorization': f'Bearer {access_token}'} - expect(requests, times=times).get(f'{url}/jobs/{job_id}', headers=headers).thenReturn( + expect(requests, times=times).get(f'{url}/jobs/{job_id}', headers=headers, timeout=REQUESTS_TIMEOUT).thenReturn( MockJsonResponse(200, {'status': 'pending', 'metadata': {'shots': 42, 'circuits': []}}) ) return job_id @@ -265,7 +268,8 @@ def expect_logout(auth_server_url: str, refresh_token: str): request_data = AuthRequest(client_id=AUTH_CLIENT_ID, refresh_token=refresh_token) expect(requests, times=1).post( f'{auth_server_url}/realms/{AUTH_REALM}/protocol/openid-connect/logout', - data=request_data.dict(exclude_none=True) + data=request_data.dict(exclude_none=True), + timeout=REQUESTS_TIMEOUT ).thenReturn( mock({'status_code': 204, 'text': '{}'}) ) diff --git a/tests/test_iqm_client.py b/tests/test_iqm_client.py index 570b23ca0..6e529b3c1 100644 --- a/tests/test_iqm_client.py +++ b/tests/test_iqm_client.py @@ -23,6 +23,8 @@ SingleQubitMapping, Status, serialize_qubit_mapping) from tests.conftest import MockJsonResponse, existing_run, missing_run +REQUESTS_TIMEOUT = 10 + def test_serialize_qubit_mapping(): qubit_mapping = {'Alice': 'QB1', 'Bob': 'qubit_3', 'Charlie': 'physical 0'} @@ -170,7 +172,7 @@ def test_waiting_for_results(mock_server, base_url, settings_dict): def test_user_warning_is_emitted_when_warnings_in_response(base_url, settings_dict, capsys): client = IQMClient(base_url) msg = 'This is a warning msg' - with when(requests).get(f'{base_url}/jobs/{existing_run}', headers=None).thenReturn( + with when(requests).get(f'{base_url}/jobs/{existing_run}', headers=None, timeout=REQUESTS_TIMEOUT).thenReturn( MockJsonResponse(200, {'status': 'ready', 'warnings': [msg], 'metadata': {'shots': 42, 'circuits': []}}) ): with pytest.warns(UserWarning, match=msg): From 5cfb62102a91ab80874b5f8cfddcf95b8f08d0ef Mon Sep 17 00:00:00 2001 From: kukushechkin Date: Mon, 29 Aug 2022 10:48:35 +0300 Subject: [PATCH 2/3] mypy support --- .mypy.ini | 2 ++ CHANGELOG.rst | 5 +++++ setup.cfg | 2 ++ src/iqm_client/iqm_client.py | 26 ++++++++++++++------------ src/iqm_client/py.typed | 0 tox.ini | 4 ++-- 6 files changed, 25 insertions(+), 14 deletions(-) create mode 100644 .mypy.ini create mode 100644 src/iqm_client/py.typed diff --git a/.mypy.ini b/.mypy.ini new file mode 100644 index 000000000..90261dcc4 --- /dev/null +++ b/.mypy.ini @@ -0,0 +1,2 @@ +[mypy-mockito.*] +ignore_missing_imports = True diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7751c9b53..5beebcac0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,11 @@ Changelog ========= +Version 6.2 +=========== + +* Add mypy support. `#41 `_ + Version 6.1 =========== diff --git a/setup.cfg b/setup.cfg index 6bbf06705..5b5745cb2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -67,6 +67,8 @@ testing = jsons==1.6.1 jsonschema==4.4.0 mockito==1.3.0 + types-requests == 2.28.9 + types-jsonschema == 4.14.0 cicd = twine >= 3.3.0, < 4.0 wheel >= 0.36.2, < 1.0 diff --git a/src/iqm_client/iqm_client.py b/src/iqm_client/iqm_client.py index 4eccde8b5..640deaa20 100644 --- a/src/iqm_client/iqm_client.py +++ b/src/iqm_client/iqm_client.py @@ -500,6 +500,7 @@ def __del__(self): except Exception: # pylint: disable=broad-except pass + # pylint: disable=too-many-locals def submit_circuits( self, circuits: list[Circuit], @@ -523,6 +524,7 @@ def submit_circuits( Returns: ID for the created task. This ID is needed to query the status and the execution results. """ + serialized_qubit_mapping: Optional[list[SingleQubitMapping]] = None if qubit_mapping is not None: # check if qubit mapping is injective target_qubits = set(qubit_mapping.values()) @@ -543,13 +545,13 @@ def submit_circuits( if diff: raise ValueError(f'The physical qubits {diff} in the qubit mapping are not defined in settings.') - qubit_mapping = serialize_qubit_mapping(qubit_mapping) + serialized_qubit_mapping = serialize_qubit_mapping(qubit_mapping) # ``bearer_token`` can be ``None`` if cocos we're connecting does not use authentication bearer_token = self._get_bearer_token() data = RunRequest( - qubit_mapping=qubit_mapping, + qubit_mapping=serialized_qubit_mapping, circuits=circuits, settings=settings, calibration_set_id=calibration_set_id, @@ -593,13 +595,13 @@ def get_run(self, job_id: UUID) -> RunResult: timeout=REQUESTS_TIMEOUT ) result.raise_for_status() - result = RunResult.from_dict(result.json()) - if result.warnings: - for warning in result.warnings: + run_result = RunResult.from_dict(result.json()) + if run_result.warnings: + for warning in run_result.warnings: warnings.warn(warning) - if result.status == Status.FAILED: - raise CircuitExecutionError(result.message) - return result + if run_result.status == Status.FAILED: + raise CircuitExecutionError(run_result.message) + return run_result def get_run_status(self, job_id: UUID) -> RunStatus: """Query the status of the running task. @@ -621,11 +623,11 @@ def get_run_status(self, job_id: UUID) -> RunStatus: timeout=REQUESTS_TIMEOUT ) result.raise_for_status() - result = RunStatus.from_dict(result.json()) - if result.warnings: - for warning in result.warnings: + run_result = RunStatus.from_dict(result.json()) + if run_result.warnings: + for warning in run_result.warnings: warnings.warn(warning) - return result + return run_result def wait_for_results(self, job_id: UUID, timeout_secs: float = DEFAULT_TIMEOUT_SECONDS) -> RunResult: """Poll results until run is ready, failed, or timed out. diff --git a/src/iqm_client/py.typed b/src/iqm_client/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/tox.ini b/tox.ini index 908e9444f..5079366b7 100644 --- a/tox.ini +++ b/tox.ini @@ -16,8 +16,8 @@ extras = testing commands = pytest tests --verbose --cov --cov-report term-missing --junitxml=test_report.xml --doctest-modules src - pytest --pylint src/ - pytest --pylint tests/ --pylint-rcfile=tests/.pylintrc + pytest --mypy --pylint src/ + pytest --mypy --pylint tests/ --pylint-rcfile=tests/.pylintrc pytest --isort tests/ src/ --verbose [testenv:docs] From a5d74c5400138c3a4d862202c2d4b16323e48404 Mon Sep 17 00:00:00 2001 From: Vladimir Kukushkin Date: Mon, 29 Aug 2022 13:56:56 +0300 Subject: [PATCH 3/3] Update CHANGELOG.rst Co-authored-by: Hayk Sargsyan <52532457+hay-k@users.noreply.github.com> --- CHANGELOG.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5beebcac0..1175b000f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,7 +5,8 @@ Changelog Version 6.2 =========== -* Add mypy support. `#41 `_ +* Enable mypy checks. `#41 `_ +* Update source code according to new checks in pylint v2.15.0. `#41 `_ Version 6.1 ===========