From 0798242859adb66cca6f2ff09ef42b2db835ac5d Mon Sep 17 00:00:00 2001 From: "oliver.gordon" Date: Fri, 12 Apr 2024 16:07:11 +0200 Subject: [PATCH] only retry for certain status codes --- pasqal_cloud/__init__.py | 3 ++- pasqal_cloud/batch.py | 4 ++-- pasqal_cloud/client.py | 32 +++++++++++++------------ tests/conftest.py | 8 +++---- tests/fixtures/test_payloads.py | 42 +++++++++++++++++++++++++++++++++ tests/test_batch.py | 15 +++++------- tests/test_client.py | 3 ++- 7 files changed, 75 insertions(+), 32 deletions(-) create mode 100644 tests/fixtures/test_payloads.py diff --git a/pasqal_cloud/__init__.py b/pasqal_cloud/__init__.py index 51e317be..5bb9aa97 100644 --- a/pasqal_cloud/__init__.py +++ b/pasqal_cloud/__init__.py @@ -137,7 +137,8 @@ def create_batch( stored in the serialized sequence configuration: A dictionary with extra configuration for the emulators that accept it. - wait: Whether to block on this statement until all the submitted jobs are terminated + wait: Whether to block on this statement until all the submitted jobs are + terminated fetch_results (deprecated): Whether to wait for the batch to be done and fetch results diff --git a/pasqal_cloud/batch.py b/pasqal_cloud/batch.py index fd61bc3b..11b2be0d 100644 --- a/pasqal_cloud/batch.py +++ b/pasqal_cloud/batch.py @@ -13,7 +13,6 @@ BatchSetCompleteError, BatchCancellingError, JobCreationError, - JobFetchingError, JobRetryError, ) from pasqal_cloud.job import CreateJob, Job @@ -170,7 +169,8 @@ def retry(self, job: Job, wait: bool = False) -> None: raise JobRetryError from e def declare_complete(self, wait: bool = False, fetch_results: bool = False) -> None: - """Declare to PCS that the batch is complete and returns an updated batch instance. + """Declare to PCS that the batch is complete and returns an updated + batch instance. Args: wait: Whether to wait for the batch to be done and fetch results. diff --git a/pasqal_cloud/client.py b/pasqal_cloud/client.py index a83799f2..27257dba 100644 --- a/pasqal_cloud/client.py +++ b/pasqal_cloud/client.py @@ -114,25 +114,27 @@ def _request( while not successful_request and iteration <= HTTP_RETRIES: # time = (interval seconds * exponent rule) ^ retries delay = (1 * 2) ** iteration + rsp = requests.request( + method, + url, + json=payload, + timeout=TIMEOUT, + headers={"content-type": "application/json"}, + auth=self.authenticator, + params=params, + ) try: - rsp = requests.request( - method, - url, - json=payload, - timeout=TIMEOUT, - headers={"content-type": "application/json"}, - auth=self.authenticator, - params=params, - ) - data: JSendPayload = rsp.json() - successful_request = True + rsp.raise_for_status() except Exception as e: - if iteration == HTTP_RETRIES: + if ( + rsp.status_code not in {408, 425, 429, 500, 502, 503, 504} + or iteration == HTTP_RETRIES + ): raise e - time.sleep(delay) - iteration += 1 - return data + time.sleep(delay) + iteration += 1 + return rsp.json() def _send_batch(self, batch_data: Dict[str, Any]) -> Dict[str, Any]: batch_data.update({"project_id": self.project_id}) diff --git a/tests/conftest.py b/tests/conftest.py index 9fc68352..3dfd3e58 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,7 +10,7 @@ from pasqal_cloud.endpoints import Endpoints from tests.test_doubles.authentication import FakeAuth0AuthenticationSuccess from requests import HTTPError -from typing import Generator, Any +from typing import Generator, Any, Optional TEST_API_FIXTURES_PATH = "tests/fixtures/api" RESULT_LINK_ENDPOINT = "http://result-link/" @@ -61,7 +61,7 @@ def mock_response(request, context) -> Dict[str, Any]: @pytest.fixture(scope="session") @requests_mock.Mocker(kw="mock") -def request_mock(mock=None): +def request_mock(mock=None) -> Optional[Any]: # Configure requests to use the local JSON files a response mock.register_uri( requests_mock.ANY, @@ -73,7 +73,7 @@ def request_mock(mock=None): @pytest.fixture(scope="session") @requests_mock.Mocker(kw="mock") -def request_mock_exception(mock=None): +def request_mock_exception(mock=None) -> Optional[Any]: mock.register_uri( requests_mock.ANY, requests_mock.ANY, @@ -106,7 +106,7 @@ def batch_creation_error_data() -> Dict[str, Any]: def request_mock_exception_batch_creation( batch_creation_error_data, mock=None, -): +) -> Optional[Any]: # Configure requests to use the local JSON files a response mock.register_uri( requests_mock.ANY, diff --git a/tests/fixtures/test_payloads.py b/tests/fixtures/test_payloads.py new file mode 100644 index 00000000..37fed92e --- /dev/null +++ b/tests/fixtures/test_payloads.py @@ -0,0 +1,42 @@ +from typing import Any, Dict + + +def successful_batch_payload_response() -> Dict[str, Any]: + return { + "code": 200, + "data": { + "complete": True, + "created_at": "2021-11-10T15:24:38.155824", + "device_type": "MOCK_DEVICE", + "project_id": "00000000-0000-0000-0000-000000000001", + "id": "00000000-0000-0000-0000-000000000001", + "priority": 10, + "sequence_builder": "pulser_test_sequence", + "status": "PENDING", + "updated_at": "2021-11-10T15:27:44.110274", + "user_id": "EQZj1ZQE", + "webhook": "10.0.1.5", + "configuration": {"dt": 10.0, "precision": "normal"}, + "start_datetime": "2023-03-23T10:42:27.611384+00:00", + "end_datetime": "2023-03-23T10:42:27.611384+00:00", + "jobs": [ + { + "batch_id": "00000000-0000-0000-0000-000000000001", + "id": "00000000-0000-0000-0000-000000022010", + "project_id": "00000000-0000-0000-0000-000000022111", + "runs": 50, + "status": "PENDING", + "created_at": "2021-11-10T15:27:06.698066", + "errors": [], + "updated_at": "2021-11-10T15:27:06.698066", + "variables": { + "Omega_max": 14.4, + "last_target": "q1", + "ts": [200, 500], + }, + } + ], + }, + "message": "OK.", + "status": "success", + } diff --git a/tests/test_batch.py b/tests/test_batch.py index 0a719e54..e6b70563 100644 --- a/tests/test_batch.py +++ b/tests/test_batch.py @@ -22,12 +22,9 @@ from tests.test_doubles.authentication import FakeAuth0AuthenticationSuccess -from requests import HTTPError -from tests.conftest import mock_core_response -import requests_mock +from tests.conftest import mock_core_response from unittest.mock import patch -from requests.models import Response class TestBatch: @@ -97,7 +94,7 @@ def test_batch_create_exception( self, mock_request_exception: Generator[Any, Any, None] ): """ - Assert the correct exception is raised when failign to create a batch + Assert the correct exception is raised when failing to create a batch and that the correct methods and URLs are used. """ with pytest.raises(BatchCreationError): @@ -173,7 +170,7 @@ def test_batch_add_jobs_failure( ): """ When trying to add jobs to a batch, we test that we catch - an exception JobCreationError while later validationg the HTTP method + an exception JobCreationError while later validating the HTTP method and URL executed. """ with pytest.raises(JobCreationError): @@ -192,6 +189,7 @@ def test_batch_add_jobs_and_wait_for_results( mock_request: Generator[Any, Any, None], load_mock_batch_json_response: Dict[str, Any], ): + mock_request.reset_mock() # Override the batch id so that we load the right API fixtures batch.id = "00000000-0000-0000-0000-000000000002" @@ -222,7 +220,6 @@ def custom_get_batch_mock(request, _): "GET", f"/core-fast/api/v1/batches/{batch.id}", json=custom_get_batch_mock ) - # Reset history so that we can count calls later mock_request.register_uri( "POST", f"/core-fast/api/v1/batches/{batch.id}/jobs", @@ -318,7 +315,7 @@ def test_cancel_batch_sdk_error( self, mock_request_exception: Generator[Any, Any, None] ): """ - Assert that a BatchCancellingError is raised appropriatrely for + Assert that a BatchCancellingError is raised appropriately for failling requests when calling sdk.cancel_batch(...) This test also assert the most recently used HTTP method and URL @@ -555,7 +552,7 @@ def test_rebatch_success( assert batch.parent_id == self.batch_id assert batch.ordered_jobs[0].parent_id - def test_rebatch_sdk_error(self, mock_request_exception): + def test_rebatch_sdk_error(self, mock_request_exception: Generator[Any, Any, None]): """ As a user using the SDK with proper credentials, if my request for rebatching returns a non 200 status code, diff --git a/tests/test_client.py b/tests/test_client.py index f307f1d0..8accb32a 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -9,6 +9,7 @@ FakeAuth0AuthenticationFailure, FakeAuth0AuthenticationSuccess, ) +from typing import Any, Generator class TestSDKCommonAttributes: @@ -165,7 +166,7 @@ class TestEnvSDK(TestSDKCommonAttributes): ("dev", "https://apis.dev.pasqal.cloud/core-fast"), ], ) - def test_select_env(self, env, core_endpoint_expected): + def test_select_env(self, env: str, core_endpoint_expected: str): sdk = SDK( project_id=self.project_id, username=self.username,