From 38f2a202191027b415fb6f6423854a5e43052d34 Mon Sep 17 00:00:00 2001 From: Milan Viricel <44581410+Mildophin@users.noreply.github.com> Date: Wed, 11 Sep 2024 16:05:07 +0200 Subject: [PATCH] [FEAT] Switch to V2 endpoints for some endpoints (#1281) (#135) --- CHANGELOG.md | 19 ++++- pasqal_cloud/_version.py | 2 +- pasqal_cloud/client.py | 12 +-- .../cancel/_.PATCH.json | 59 ++++++++++++++ .../cancel/jobs/_.PATCH.json | 38 +++++++++ .../complete/_.PATCH.json | 39 ++++++++++ .../jobs/_.POST.json | 54 +++++++++++++ .../jobs/_.POST.json | 54 +++++++++++++ .../_.POST.json | 29 +++++++ .../cancel/_.PATCH.json | 18 +++++ tests/test_batch.py | 78 +++++++++++-------- tests/test_client.py | 22 +++--- tests/test_job.py | 35 +++++---- tests/test_workload.py | 22 +++--- 14 files changed, 402 insertions(+), 79 deletions(-) create mode 100644 tests/fixtures/api/v2/batches/00000000-0000-0000-0000-000000000001/cancel/_.PATCH.json create mode 100644 tests/fixtures/api/v2/batches/00000000-0000-0000-0000-000000000001/cancel/jobs/_.PATCH.json create mode 100644 tests/fixtures/api/v2/batches/00000000-0000-0000-0000-000000000001/complete/_.PATCH.json create mode 100644 tests/fixtures/api/v2/batches/00000000-0000-0000-0000-000000000001/jobs/_.POST.json create mode 100644 tests/fixtures/api/v2/batches/00000000-0000-0000-0000-000000000002/jobs/_.POST.json create mode 100644 tests/fixtures/api/v2/jobs/00000000-0000-0000-0000-000000022010/_.POST.json create mode 100644 tests/fixtures/api/v2/jobs/00000000-0000-0000-0000-000000022010/cancel/_.PATCH.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 19d6990c..e0e40d17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,21 @@ All notable changes to this project will be documented in this file. +## [0.12.2] - 2024-09-11 + +### Changed + +- Now these methods are using V2 endpoints: + - Cancel a batch + - Cancel a job + - Cancel a group of jobs + - Add jobs to a batch + - Close a batch + ## [0.12.1] - 2024-09-11 +### Added + - Introduce EMU_FRESNEL device type ## [0.12.0] - 2024-09-03 @@ -11,20 +24,20 @@ All notable changes to this project will be documented in this file. ### Breaking change - 'from pasqal_cloud' import has completely replaced the deprecated import 'from sdk' -- 'group' field is now removed, use 'project' field instead +- 'group_id' field is now removed, use 'project_id' field instead ## [0.11.4] - 2024-08-28 ### Changed -Batch that do not accept new jobs are now called "closed" instead of "complete". As a result: +A Batch that does not accept new jobs is now called "closed" instead of "complete". As a result: - You should create an "open" batch using the "open" argument of the `create_batch` method instead of the `complete` argument. - Close an open batch using the `close_batch` method of the SDK or `close` method of the `Batch` class. They are functionally equivalent to the now deprecated `complete_batch` and `declare_complete` functions. - Batch dataclass parameter `complete` has been replaced by `open`. -- Using the deprecated method and arguments will now raise a warning . +- Using the deprecated method and arguments will now raise a warning. ## [0.11.3] - 2024-08-05 diff --git a/pasqal_cloud/_version.py b/pasqal_cloud/_version.py index 1447bfdb..28eaf004 100644 --- a/pasqal_cloud/_version.py +++ b/pasqal_cloud/_version.py @@ -13,4 +13,4 @@ # limitations under the License. -__version__ = "0.12.1" +__version__ = "0.12.2" diff --git a/pasqal_cloud/client.py b/pasqal_cloud/client.py index 5ba2f8b8..09366fd3 100644 --- a/pasqal_cloud/client.py +++ b/pasqal_cloud/client.py @@ -241,13 +241,13 @@ def get_job_results(self, job_id: str) -> Optional[JobResult]: def close_batch(self, batch_id: str) -> Dict[str, Any]: response: Dict[str, Any] = self._authenticated_request( - "PUT", f"{self.endpoints.core}/api/v1/batches/{batch_id}/complete" + "PATCH", f"{self.endpoints.core}/api/v2/batches/{batch_id}/complete" )["data"] return response def cancel_batch(self, batch_id: str) -> Dict[str, Any]: response: Dict[str, Any] = self._authenticated_request( - "PUT", f"{self.endpoints.core}/api/v1/batches/{batch_id}/cancel" + "PATCH", f"{self.endpoints.core}/api/v2/batches/{batch_id}/cancel" )["data"] return response @@ -277,7 +277,7 @@ def add_jobs( self, batch_id: str, jobs_data: Sequence[Mapping[str, Any]] ) -> Dict[str, Any]: response: Dict[str, Any] = self._authenticated_request( - "POST", f"{self.endpoints.core}/api/v1/batches/{batch_id}/jobs", jobs_data + "POST", f"{self.endpoints.core}/api/v2/batches/{batch_id}/jobs", jobs_data )["data"] return response @@ -289,7 +289,7 @@ def get_job(self, job_id: str) -> Dict[str, Any]: def cancel_job(self, job_id: str) -> Dict[str, Any]: response: Dict[str, Any] = self._authenticated_request( - "PUT", f"{self.endpoints.core}/api/v1/jobs/{job_id}/cancel" + "PATCH", f"{self.endpoints.core}/api/v2/jobs/{job_id}/cancel" )["data"] return response @@ -297,8 +297,8 @@ def cancel_jobs( self, batch_id: Union[UUID, str], filters: CancelJobFilters ) -> Dict[str, Any]: response: Dict[str, Any] = self._authenticated_request( - "PUT", - f"{self.endpoints.core}/api/v1/batches/{batch_id}/cancel/jobs", + "PATCH", + f"{self.endpoints.core}/api/v2/batches/{batch_id}/cancel/jobs", params=filters.model_dump(exclude_unset=True), )["data"] return response diff --git a/tests/fixtures/api/v2/batches/00000000-0000-0000-0000-000000000001/cancel/_.PATCH.json b/tests/fixtures/api/v2/batches/00000000-0000-0000-0000-000000000001/cancel/_.PATCH.json new file mode 100644 index 00000000..e6b15e90 --- /dev/null +++ b/tests/fixtures/api/v2/batches/00000000-0000-0000-0000-000000000001/cancel/_.PATCH.json @@ -0,0 +1,59 @@ +{ + "status": "success", + "message": "OK.", + "code": "200", + "data": { + "id": "00000000-0000-0000-0000-000000000001", + "created_at": "2023-03-23T10:41:36.635820+00:00", + "updated_at": "2023-03-23T10:42:27.610076+00:00", + "start_datetime": null, + "end_datetime": "2023-03-23T10:42:27.611384+00:00", + "priority": 20, + "sequence_builder": "{\"_build\": true, \"__module__\": \"pulser.sequence.sequence\", \"__name__\": \"Sequence\", \"__args__\": [{\"_build\": true, \"__module__\": \"pulser.register.register\", \"__name__\": \"Register\", \"__args__\": [{\"q0\": [-10.0, 0.0], \"q1\": [-5.0, -8.660254], \"q2\": [-5.0, 8.660254], \"q3\": [5.0, -8.660254], \"q4\": [5.0, 8.660254], \"q5\": [10.0, 0.0]}], \"__kwargs__\": {\"layout\": {\"_build\": true, \"__module__\": \"pulser.register.register_layout\", \"__name__\": \"RegisterLayout\", \"__args__\": [{\"_build\": true, \"__module__\": \"numpy\", \"__name__\": \"array\", \"__args__\": [[[0.0, 0.0], [2.5, 4.330127018922194], [2.5, -4.330127018922194], [-5.0, 0.0], [-5.0, 8.660254037844387], [5.0, 8.660254037844387], [7.5, 4.330127018922194], [10.0, 0.0], [7.5, -4.330127018922194], [5.0, -8.660254037844387], [-5.0, -8.660254037844387], [-10.0, 0.0], [-12.5, 4.330127018922194], [-10.0, 8.660254037844387], [10.0, 8.660254037844387], [15.0, 0.0], [-2.5, -12.99038105676658], [-7.5, -12.99038105676658], [-10.0, -8.660254037844387], [-10.0, 17.320508075688775], [5.0, 17.320508075688775], [10.0, 17.320508075688775], [12.5, 12.99038105676658], [17.5, 4.330127018922192], [20.0, 0.0], [12.5, -12.99038105676658], [-15.0, -8.660254037844387], [-20.0, 0.0], [-22.5, 4.330127018922194], [-12.5, 21.650635094610966], [17.5, 12.990381056766578], [25.0, 0.0], [12.5, -21.650635094610966], [-2.5, -21.650635094610966], [-12.5, -21.650635094610966], [-20.0, -8.660254037844384], [-22.5, 12.99038105676658], [5.0, 25.98076211353316], [17.5, 21.65063509461097], [20.0, 17.320508075688775], [22.5, 12.99038105676658], [25.0, 8.660254037844389], [27.5, 4.330127018922196], [25.0, -8.660254037844387], [-22.5, -12.99038105676658], [-30.0, 0.0], [-32.5, 4.330127018922194], [2.5, 30.310889132455355], [17.5, 30.310889132455355], [20.0, 25.98076211353316], [22.5, 21.650635094610966], [30.0, -8.660254037844387], [22.5, -21.650635094610966], [17.5, -30.310889132455355], [-2.5, -30.310889132455355], [-17.5, -30.310889132455355], [-22.5, -21.650635094610966], [-32.5, -4.330127018922192], [-37.5, 4.330127018922194], [-27.5, 21.650635094610966], [22.5, 30.310889132455355], [30.0, 17.320508075688775], [32.5, 12.99038105676658], [37.5, -4.330127018922194], [5.0, -34.64101615137755], [-22.5, -30.310889132455355], [-25.0, -25.98076211353316], [-32.5, -12.99038105676658]]], \"__kwargs__\": {}}], \"__kwargs__\": {}}, \"trap_ids\": [20, 24, 26, 35, 36, 41]}}, {\"_build\": false, \"__module__\": \"pulser.devices\", \"__name__\": \"IroiseMVP\"}], \"__kwargs__\": {}, \"__version__\": \"0.5.1\", \"calls\": [[\"declare_channel\", [\"ch_global\", \"rydberg_global\"], {\"initial_target\": null}], [\"add\", [{\"_build\": true, \"__module__\": \"pulser.pulse\", \"__name__\": \"Pulse\", \"__args__\": [{\"_build\": true, \"__module__\": \"pulser.waveforms\", \"__name__\": \"ConstantWaveform\", \"__args__\": [124, 12.566370614359172], \"__kwargs__\": {}}, {\"_build\": true, \"__module__\": \"pulser.waveforms\", \"__name__\": \"ConstantWaveform\", \"__args__\": [124, 25.132741228718345], \"__kwargs__\": {}}, 0.0], \"__kwargs__\": {\"post_phase_shift\": 0.0}}, \"ch_global\"], {}], [\"add\", [{\"_build\": true, \"__module__\": \"pulser.pulse\", \"__name__\": \"Pulse\", \"__args__\": [{\"_build\": true, \"__module__\": \"pulser.waveforms\", \"__name__\": \"ConstantWaveform\", \"__args__\": [400, 0.0], \"__kwargs__\": {}}, {\"_build\": true, \"__module__\": \"pulser.waveforms\", \"__name__\": \"ConstantWaveform\", \"__args__\": [400, -25.132741228718345], \"__kwargs__\": {}}, 0.0], \"__kwargs__\": {\"post_phase_shift\": 0.0}}, \"ch_global\"], {}], [\"add\", [{\"_build\": true, \"__module__\": \"pulser.pulse\", \"__name__\": \"Pulse\", \"__args__\": [{\"_build\": true, \"__module__\": \"pulser.waveforms\", \"__name__\": \"ConstantWaveform\", \"__args__\": [100, 12.566370614359172], \"__kwargs__\": {}}, {\"_build\": true, \"__module__\": \"pulser.waveforms\", \"__name__\": \"ConstantWaveform\", \"__args__\": [100, 25.132741228718345], \"__kwargs__\": {}}, 0.0], \"__kwargs__\": {\"post_phase_shift\": 0.0}}, \"ch_global\"], {}], [\"add\", [{\"_build\": true, \"__module__\": \"pulser.pulse\", \"__name__\": \"Pulse\", \"__args__\": [{\"_build\": true, \"__module__\": \"pulser.waveforms\", \"__name__\": \"ConstantWaveform\", \"__args__\": [400, 0.0], \"__kwargs__\": {}}, {\"_build\": true, \"__module__\": \"pulser.waveforms\", \"__name__\": \"ConstantWaveform\", \"__args__\": [400, -25.132741228718345], \"__kwargs__\": {}}, 0.0], \"__kwargs__\": {\"post_phase_shift\": 0.0}}, \"ch_global\"], {}], [\"add\", [{\"_build\": true, \"__module__\": \"pulser.pulse\", \"__name__\": \"Pulse\", \"__args__\": [{\"_build\": true, \"__module__\": \"pulser.waveforms\", \"__name__\": \"ConstantWaveform\", \"__args__\": [100, 12.566370614359172], \"__kwargs__\": {}}, {\"_build\": true, \"__module__\": \"pulser.waveforms\", \"__name__\": \"ConstantWaveform\", \"__args__\": [100, 25.132741228718345], \"__kwargs__\": {}}, 0.0], \"__kwargs__\": {\"post_phase_shift\": 0.0}}, \"ch_global\"], {}], [\"measure\", [], {}]], \"vars\": {}, \"to_build_calls\": []}", + "device_type": "EMU_FREE", + "status": "CANCELED", + "complete": true, + "open": false, + "project_id": "00000000-0000-0000-0000-000000000001", + "webhook": null, + "user_id": "EQZj1ZQE", + "configuration": null, + "device_status": null, + "jobs_count": 2, + "jobs_count_per_status": { + "PENDING": 0, + "RUNNING": 0, + "DONE": 0, + "ERROR": 0, + "CANCELED": 2 + }, + "jobs": [ + { + "id": "30c58103-8a6d-4e15-bc40-23285e73bf2b", + "created_at": "2023-03-23T10:41:36.635820+00:00", + "updated_at": "2023-03-23T10:42:27.610076+00:00", + "start_timestamp": null, + "end_timestamp": "2023-03-23T10:42:27.611419+00:00", + "runs": 200, + "status": "CANCELED", + "errors": null, + "variables": {}, + "batch_id": "5af686ee-d7dc-459e-a73e-12279ae04b91", + "project_id": "00000000-0000-0000-0000-000000000007" + }, + { + "id": "ed9ae427-a480-4bf6-9bd9-0ac82d0a1d50", + "created_at": "2023-03-23T10:41:36.635820+00:00", + "updated_at": "2023-03-23T10:42:27.610076+00:00", + "start_timestamp": null, + "end_timestamp": "2023-03-23T10:42:27.611425+00:00", + "runs": 100, + "status": "CANCELED", + "errors": null, + "variables": {}, + "batch_id": "5af686ee-d7dc-459e-a73e-12279ae04b91", + "project_id": "00000000-0000-0000-0000-000000000007" + } + ] + } +} diff --git a/tests/fixtures/api/v2/batches/00000000-0000-0000-0000-000000000001/cancel/jobs/_.PATCH.json b/tests/fixtures/api/v2/batches/00000000-0000-0000-0000-000000000001/cancel/jobs/_.PATCH.json new file mode 100644 index 00000000..ca0a5268 --- /dev/null +++ b/tests/fixtures/api/v2/batches/00000000-0000-0000-0000-000000000001/cancel/jobs/_.PATCH.json @@ -0,0 +1,38 @@ +{ + "status": "success", + "message": "OK.", + "code": "200", + "data": { + "jobs": [ + { + "id": "00000000-0000-0000-0000-000000000001", + "created_at": "2023-03-23T10:41:36.635820+00:00", + "updated_at": "2023-03-23T10:42:27.610076+00:00", + "start_timestamp": null, + "end_timestamp": "2023-03-23T10:42:27.611419+00:00", + "runs": 200, + "status": "CANCELED", + "errors": null, + "variables": {}, + "batch_id": "5af686ee-d7dc-459e-a73e-12279ae04b91", + "project_id": "00000000-0000-0000-0000-000000000007" + }, + { + "id": "00000000-0000-0000-0000-000000000002", + "created_at": "2023-03-23T10:41:36.635820+00:00", + "updated_at": "2023-03-23T10:42:27.610076+00:00", + "start_timestamp": null, + "end_timestamp": "2023-03-23T10:42:27.611419+00:00", + "runs": 200, + "status": "CANCELED", + "errors": null, + "variables": {}, + "batch_id": "5af686ee-d7dc-459e-a73e-12279ae04b91", + "project_id": "00000000-0000-0000-0000-000000000007" + } + ], + "errors": { + "00000000-0000-0000-0000-000000000003": "Error could not be cancelled" + } + } +} diff --git a/tests/fixtures/api/v2/batches/00000000-0000-0000-0000-000000000001/complete/_.PATCH.json b/tests/fixtures/api/v2/batches/00000000-0000-0000-0000-000000000001/complete/_.PATCH.json new file mode 100644 index 00000000..3ad39cc4 --- /dev/null +++ b/tests/fixtures/api/v2/batches/00000000-0000-0000-0000-000000000001/complete/_.PATCH.json @@ -0,0 +1,39 @@ +{ + "code": 200, + "data": { + "complete": true, + "open": false, + "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", + "jobs": [ + { + "batch_id": "00000000-0000-0000-0000-000000000001", + "id": "00000000-0000-0000-0000-000000022010", + "project_id": "00000000-0000-0000-0000-000000022111", + "runs": 50, + "status": "RUNNING", + "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/fixtures/api/v2/batches/00000000-0000-0000-0000-000000000001/jobs/_.POST.json b/tests/fixtures/api/v2/batches/00000000-0000-0000-0000-000000000001/jobs/_.POST.json new file mode 100644 index 00000000..8f62ba0a --- /dev/null +++ b/tests/fixtures/api/v2/batches/00000000-0000-0000-0000-000000000001/jobs/_.POST.json @@ -0,0 +1,54 @@ +{ + "code": 200, + "data": { + "complete": true, + "open": false, + "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] + } + }, + { + "batch_id": "00000000-0000-0000-0000-000000000001", + "id": "00000000-0000-0000-0000-000000022011", + "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/fixtures/api/v2/batches/00000000-0000-0000-0000-000000000002/jobs/_.POST.json b/tests/fixtures/api/v2/batches/00000000-0000-0000-0000-000000000002/jobs/_.POST.json new file mode 100644 index 00000000..1423bb4f --- /dev/null +++ b/tests/fixtures/api/v2/batches/00000000-0000-0000-0000-000000000002/jobs/_.POST.json @@ -0,0 +1,54 @@ +{ + "code": 200, + "data": { + "complete": true, + "open": false, + "created_at": "2021-11-10T15:24:38.155824", + "device_type": "MOCK_DEVICE", + "project_id": "00000000-0000-0000-0000-000000000001", + "id": "00000000-0000-0000-0000-000000000002", + "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-000000000002", + "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] + } + }, + { + "batch_id": "00000000-0000-0000-0000-000000000002", + "id": "00000000-0000-0000-0000-000000022011", + "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/fixtures/api/v2/jobs/00000000-0000-0000-0000-000000022010/_.POST.json b/tests/fixtures/api/v2/jobs/00000000-0000-0000-0000-000000022010/_.POST.json new file mode 100644 index 00000000..0da6dc34 --- /dev/null +++ b/tests/fixtures/api/v2/jobs/00000000-0000-0000-0000-000000022010/_.POST.json @@ -0,0 +1,29 @@ +{ + "code": 200, + "data": { + "batch_id": "00000000-0000-0000-0000-000000000001", + "project_id": "00000000-0000-0000-0000-000000000002", + "created_at": "2021-11-10T15:24:38.172109", + "errors": [ + "Error 1", + "Error 2: Cannot connect to QPU", + "Error 3: Matrix glitch" + ], + "id": "00000000-0000-0000-0000-000000022010", + "runs": 50, + "status": "PENDING", + "updated_at": "2021-11-10T15:27:06.698066", + "start_timestamp": "1636558026.698066", + "end_timestamp": "1636558026.698066", + "variables": { + "Omega_max": 14.4, + "last_target": "q1", + "ts": [ + 200, + 500 + ] + } + }, + "message": "OK.", + "status": "success" +} diff --git a/tests/fixtures/api/v2/jobs/00000000-0000-0000-0000-000000022010/cancel/_.PATCH.json b/tests/fixtures/api/v2/jobs/00000000-0000-0000-0000-000000022010/cancel/_.PATCH.json new file mode 100644 index 00000000..6949d8f1 --- /dev/null +++ b/tests/fixtures/api/v2/jobs/00000000-0000-0000-0000-000000022010/cancel/_.PATCH.json @@ -0,0 +1,18 @@ +{ + "status": "success", + "message": "OK.", + "code": "200", + "data": { + "id": "00000000-0000-0000-0000-000000022010", + "created_at": "2023-03-24T09:05:32.420887+00:00", + "updated_at": "2023-03-24T09:06:52.105568+00:00", + "start_timestamp": null, + "end_timestamp": "2023-03-24T09:06:52.107659+00:00", + "runs": 200, + "status": "CANCELED", + "errors": null, + "variables": {}, + "batch_id": "00000000-0000-0000-0000-000000000001", + "project_id": "00000000-0000-0000-0000-000000000002" + } +} diff --git a/tests/test_batch.py b/tests/test_batch.py index 93b33f82..24828704 100644 --- a/tests/test_batch.py +++ b/tests/test_batch.py @@ -5,6 +5,7 @@ from uuid import UUID, uuid4 import pytest +import requests_mock from pasqal_cloud import Batch, Job, RebatchFilters, SDK from pasqal_cloud.batch import Batch as BatchModel @@ -71,7 +72,7 @@ def _init_sdk(self): @pytest.mark.parametrize("emulator", [None] + [e.value for e in EmulatorType]) def test_create_batch( - self, emulator: Optional[str], mock_request: Generator[Any, Any, None] + self, emulator: Optional[str], mock_request: requests_mock.mocker.Mocker ): """ When successfully creating a batch, we should be able to assert @@ -91,7 +92,7 @@ def test_create_batch( @pytest.mark.parametrize("emulator", [None] + [e.value for e in EmulatorType]) def test_create_batch_with_complete_raises_warning( - self, emulator: Optional[str], mock_request: Generator[Any, Any, None] + self, emulator: Optional[str], mock_request: requests_mock.mocker.Mocker ): """ Test that using complete at batch definition is still accepted but will @@ -125,7 +126,7 @@ def test_create_batch_open_and_complete_raises_error(self, emulator: Optional[st ) def test_batch_create_exception( - self, mock_request_exception: Generator[Any, Any, None] + self, mock_request_exception: requests_mock.mocker.Mocker ): """ Assert the correct exception is raised when failing to create a batch @@ -148,7 +149,7 @@ def test_batch_create_exception( ) @pytest.mark.parametrize(("wait", "fetch_results"), [(True, False), (False, True)]) def test_create_batch_and_wait( - self, mock_request: Generator[Any, Any, None], wait: bool, fetch_results: bool + self, mock_request: requests_mock.mocker.Mocker, wait: bool, fetch_results: bool ): batch = self.sdk.create_batch( serialized_sequence=self.pulser_sequence, @@ -187,7 +188,7 @@ def test_get_batch(self, batch: Batch): batch_requested = self.sdk.get_batch(batch.id) assert isinstance(batch_requested, BatchModel) - def test_batch_add_jobs(self, mock_request: Generator[Any, Any, None]): + def test_batch_add_jobs(self, mock_request: requests_mock.mocker.Mocker): """ Test that after successfully creating, and adding jobs, the total number of jobs associated with a batch is correct, and the batch id is in the URL that was most @@ -201,7 +202,7 @@ def test_batch_add_jobs(self, mock_request: Generator[Any, Any, None]): assert len(batch.ordered_jobs) == 2 def test_batch_add_jobs_failure( - self, batch, mock_request_exception: Generator[Any, Any, None] + self, batch, mock_request_exception: requests_mock.mocker.Mocker ): """ When trying to add jobs to a batch, we test that we catch @@ -215,13 +216,13 @@ def test_batch_add_jobs_failure( assert mock_request_exception.last_request.method == "POST" assert ( mock_request_exception.last_request.url - == f"{self.sdk._client.endpoints.core}/api/v1/batches/{batch.id}/jobs" + == f"{self.sdk._client.endpoints.core}/api/v2/batches/{batch.id}/jobs" ) def test_batch_add_jobs_and_wait_for_results( self, batch: Batch, - mock_request: Generator[Any, Any, None], + mock_request: requests_mock.mocker.Mocker, load_mock_batch_json_response: Dict[str, Any], ): mock_request.reset_mock() @@ -298,7 +299,7 @@ def test_batch_close(self, batch: Batch): assert not batch.open def test_batch_close_failure( - self, batch: Batch, mock_request_exception: Generator[Any, Any, None] + self, batch: Batch, mock_request_exception: requests_mock.mocker.Mocker ): with pytest.raises(BatchClosingError): _ = batch.close(wait=False) @@ -307,7 +308,7 @@ def test_batch_close_failure( mock_request_exception.stop() def test_batch_close_and_wait_for_results( - self, batch: Batch, mock_request: Generator[Any, Any, None] + self, batch: Batch, mock_request: requests_mock.mocker.Mocker ): """TODO""" batch.close(wait=True) @@ -327,35 +328,35 @@ def test_batch_close_and_wait_for_results( def test_cancel_batch_self(self, mock_request, batch): batch.cancel() assert batch.status == "CANCELED" - assert mock_request.last_request.method == "PUT" + assert mock_request.last_request.method == "PATCH" assert ( mock_request.last_request.url == f"{self.sdk._client.endpoints.core}" - f"/api/v1/batches/{self.batch_id}/cancel" + f"/api/v2/batches/{self.batch_id}/cancel" ) def test_cancel_batch_self_error(self, mock_request_exception, batch): with pytest.raises(BatchCancellingError): batch.cancel() assert batch.status == "PENDING" - assert mock_request_exception.last_request.method == "PUT" + assert mock_request_exception.last_request.method == "PATCH" assert ( mock_request_exception.last_request.url == f"{self.sdk._client.endpoints.core}" - f"/api/v1/batches/{self.batch_id}/cancel" + f"/api/v2/batches/{self.batch_id}/cancel" ) def test_cancel_batch_sdk(self, mock_request): client_rsp = self.sdk.cancel_batch(self.batch_id) assert type(client_rsp) == Batch assert client_rsp.status == "CANCELED" - assert mock_request.last_request.method == "PUT" + assert mock_request.last_request.method == "PATCH" assert ( mock_request.last_request.url == f"{self.sdk._client.endpoints.core}" - f"/api/v1/batches/{self.batch_id}/cancel" + f"/api/v2/batches/{self.batch_id}/cancel" ) def test_cancel_batch_sdk_error( - self, mock_request_exception: Generator[Any, Any, None] + self, mock_request_exception: requests_mock.mocker.Mocker ): """ Assert that a BatchCancellingError is raised appropriately for @@ -367,11 +368,11 @@ def test_cancel_batch_sdk_error( with pytest.raises(BatchCancellingError): _ = self.sdk.cancel_batch(self.batch_id) - assert mock_request_exception.last_request.method == "PUT" + assert mock_request_exception.last_request.method == "PATCH" assert ( mock_request_exception.last_request.url == f"{self.sdk._client.endpoints.core}" - f"/api/v1/batches/{self.batch_id}/cancel" + f"/api/v2/batches/{self.batch_id}/cancel" ) @pytest.mark.parametrize( @@ -482,7 +483,7 @@ def test_get_batch_fetch_results_deprecated( ) def test_rebatch_success( self, - mock_request: Generator[Any, Any, None], + mock_request: requests_mock.mocker.Mocker, filters: Union[RebatchFilters, None], ): """ @@ -508,7 +509,9 @@ 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: Generator[Any, Any, None]): + def test_rebatch_sdk_error( + self, mock_request_exception: requests_mock.mocker.Mocker + ): """ As a user using the SDK with proper credentials, if my request for rebatching returns a status code @@ -526,7 +529,7 @@ def test_rebatch_sdk_error(self, mock_request_exception: Generator[Any, Any, Non def test_retry( self, - mock_request: Generator[Any, Any, None], + mock_request: requests_mock.mocker.Mocker, ): """ As a user using the SDK with proper credentials, @@ -547,7 +550,7 @@ def test_retry( assert mock_request.last_request.method == "POST" assert ( mock_request.last_request.url - == f"{self.sdk._client.endpoints.core}/api/v1/batches/" + == f"{self.sdk._client.endpoints.core}/api/v2/batches/" + f"{self.batch_id}/jobs" ) @@ -555,7 +558,7 @@ def test_retry_sdk_error( self, batch: Batch, job: Job, - mock_request_exception: Generator[Any, Any, None], + mock_request_exception: requests_mock.mocker.Mocker, ): """ As a user using the SDK with proper credentials, @@ -571,13 +574,13 @@ def test_retry_sdk_error( assert mock_request_exception.last_request.method == "POST" assert ( mock_request_exception.last_request.url - == f"{self.sdk._client.endpoints.core}/api/v1/batches/" + == f"{self.sdk._client.endpoints.core}/api/v2/batches/" + f"{self.batch_id}/jobs" ) def test_add_jobs_calls_the_correct_features( self, - mock_request: Generator[Any, Any, None], + mock_request: requests_mock.mocker.Mocker, ): """ Assert than we calling add_jobs that the correct @@ -610,13 +613,13 @@ def inline_mock_batch_response(request, _): assert ( mock_request.last_request.url == f"{self.sdk._client.endpoints.core}" - f"/api/v1/batches/{self.batch_id}/jobs" + f"/api/v2/batches/{self.batch_id}/jobs" ) assert isinstance(b, Batch) def test_add_jobs_call_raises_job_creation_error( self, - mock_request_exception: Generator[Any, Any, None], + mock_request_exception: requests_mock.mocker.Mocker, load_mock_batch_json_response: Generator[Any, Any, None], ): """ @@ -640,17 +643,30 @@ def test_add_jobs_call_raises_job_creation_error( assert ( mock_request_exception.last_request.url == f"{self.sdk._client.endpoints.core}" - f"/api/v1/batches/{self.batch_id}/jobs" + f"/api/v2/batches/{self.batch_id}/jobs" ) - def test_close_batch_raises_batch_close_error(self): + def test_close_batch_raises_batch_close_error( + self, mock_request_exception: requests_mock.mocker.Mocker + ): """ Assert that when calling close_batch, we're capable of encapsulating the HTTPError as a BatchClosingError instead. """ + mock_request_exception.register_uri( + "PATCH", + f"/core-fast/api/v2/batches/{self.batch_id}/complete", + status_code=500, + ) with pytest.raises(BatchClosingError): _ = self.sdk.close_batch(self.batch_id) + assert ( + mock_request_exception.last_request.url + == f"{self.sdk._client.endpoints.core}" + f"/api/v2/batches/{self.batch_id}/complete" + ) + def test_rebatch_raises_value_error_on_invalid_filters(self): """ As a user using the SDK with proper credentials, @@ -659,4 +675,4 @@ def test_rebatch_raises_value_error_on_invalid_filters(self): with pytest.raises( ValueError, match="Filters needs to be a RebatchFilters instance" ): - _ = self.sdk.rebatch(UUID(int=0x1), {"min_runs": 10}) + _ = self.sdk.rebatch(id=UUID(int=0x1), filters={"min_runs": 10}) diff --git a/tests/test_client.py b/tests/test_client.py index f52e6ee8..9dd6c2ba 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,10 +1,10 @@ import contextlib -from typing import Any, Generator from unittest.mock import patch from uuid import uuid4 import pytest import requests +import requests_mock from auth0.v3.exceptions import Auth0Error from pasqal_cloud import ( @@ -236,7 +236,7 @@ def _mock_sleep(self): yield def test_download_results_retry_on_exception( - self, mock_request: Generator[Any, Any, None] + self, mock_request: requests_mock.mocker.Mocker ): """ Test the retry logic for HTTP calls when an error status code is encountered. @@ -255,7 +255,7 @@ def test_download_results_retry_on_exception( assert len(mock_request.request_history) == 6 def test_download_results_retry_on_connection_error( - self, mock_request: Generator[Any, Any, None] + self, mock_request: requests_mock.mocker.Mocker ): """ Test the retry logic for HTTP calls when a network error is encountered. @@ -279,7 +279,7 @@ def raise_connection_error(*_): @pytest.mark.parametrize("status_code", [408, 425, 429, 500, 502, 503, 504]) def test_sdk_retry_on_exception( - self, mock_request: Generator[Any, Any, None], status_code: int + self, mock_request: requests_mock.mocker.Mocker, status_code: int ): """ If a HTTP status code matches any of the codes passed as parameters, @@ -295,7 +295,7 @@ def test_sdk_retry_on_exception( assert len(mock_request.request_history) == 6 def test_sdk_doesnt_retry_on_exceptions( - self, mock_request: Generator[Any, Any, None] + self, mock_request: requests_mock.mocker.Mocker ): """ If the HTTP status code is not one we consider valid for retires, we should not @@ -311,7 +311,7 @@ def test_sdk_doesnt_retry_on_exceptions( assert len(mock_request.request_history) == 1 def test_sdk_200_avoids_all_exception_handling( - self, mock_request: Generator[Any, Any, None] + self, mock_request: requests_mock.mocker.Mocker ): """ We have no need to retry requests on a successful HTTP request, so @@ -337,7 +337,9 @@ def _init_sdk(self): project_id=str(uuid4()), ) - def test_pagination_request_success(self, mock_request: Generator[Any, Any, None]): + def test_pagination_request_success( + self, mock_request: requests_mock.mocker.Mocker + ): """ Test requests with pagination with multiple pages. This test verifies that the pagination works correctly by simulating @@ -383,7 +385,7 @@ def test_pagination_request_success(self, mock_request: Generator[Any, Any, None assert len(mock_request.request_history) == 3 def test_pagination_request_changed_total_items_during_query_success( - self, mock_request: Generator[Any, Any, None] + self, mock_request: requests_mock.mocker.Mocker ): """ Test request with pagination where the total number of items @@ -430,7 +432,7 @@ def test_pagination_request_changed_total_items_during_query_success( assert len(mock_request.request_history) == 3 def test_request_pagination_without_pagination_success( - self, mock_request: Generator[Any, Any, None] + self, mock_request: requests_mock.mocker.Mocker ): """ Test request with pagination when there is only a single page of data. @@ -469,7 +471,7 @@ def _init_sdk(self): ) def test_user_agent_in_request_headers( - self, mock_request: Generator[Any, Any, None] + self, mock_request: requests_mock.mocker.Mocker ): """ Test that the `_authenticated_request` method of diff --git a/tests/test_job.py b/tests/test_job.py index 6e76e5a1..a7fe7cbf 100644 --- a/tests/test_job.py +++ b/tests/test_job.py @@ -1,9 +1,10 @@ from datetime import datetime -from typing import Any, Dict, Generator, List, Union +from typing import Any, Dict, List, Union from unittest.mock import patch from uuid import UUID, uuid4 import pytest +import requests_mock from pasqal_cloud import ( CancelJobFilters, @@ -62,7 +63,7 @@ def test_get_job(self, job: Job): assert job_requested.id == job.id def test_get_job_error( - self, job, mock_request_exception: Generator[Any, Any, None] + self, job, mock_request_exception: requests_mock.mocker.Mocker ): """ When attempting to execute get_job, we receive an exception @@ -78,21 +79,21 @@ def test_get_job_error( f"/api/v2/jobs/{job.id}" ) - def test_cancel_job_self(self, mock_request: Generator[Any, Any, None], job: Job): + def test_cancel_job_self(self, mock_request: requests_mock.mocker.Mocker, job: Job): """ After cancelling a job, we can assert that the status is CANCELED as expected and that the correct methods and URLS were used. """ job.cancel() assert job.status == "CANCELED" - assert mock_request.last_request.method == "PUT" + assert mock_request.last_request.method == "PATCH" assert ( mock_request.last_request.url - == f"{self.sdk._client.endpoints.core}/api/v1/jobs/{self.job_id}/cancel" + == f"{self.sdk._client.endpoints.core}/api/v2/jobs/{self.job_id}/cancel" ) def test_cancel_job_self_error( - self, mock_request_exception: Generator[Any, Any, None], job: Job + self, mock_request_exception: requests_mock.mocker.Mocker, job: Job ): """ When trying to cancel a job, we assert that an exception is raised @@ -102,13 +103,13 @@ def test_cancel_job_self_error( with pytest.raises(JobCancellingError): job.cancel() assert job.status == "PENDING" - assert mock_request_exception.last_request.method == "PUT" + assert mock_request_exception.last_request.method == "PATCH" assert ( mock_request_exception.last_request.url - == f"{self.sdk._client.endpoints.core}/api/v1/jobs/{self.job_id}/cancel" + == f"{self.sdk._client.endpoints.core}/api/v2/jobs/{self.job_id}/cancel" ) - def test_cancel_job_sdk(self, mock_request: Generator[Any, Any, None]): + def test_cancel_job_sdk(self, mock_request: requests_mock.mocker.Mocker): """ After successfully executing the .cancel_job method, we further validate the response object is as anticipated, @@ -117,14 +118,14 @@ def test_cancel_job_sdk(self, mock_request: Generator[Any, Any, None]): client_rsp = self.sdk.cancel_job(self.job_id) assert type(client_rsp) == Job assert client_rsp.status == "CANCELED" - assert mock_request.last_request.method == "PUT" + assert mock_request.last_request.method == "PATCH" assert ( mock_request.last_request.url - == f"{self.sdk._client.endpoints.core}/api/v1/jobs/{self.job_id}/cancel" + == f"{self.sdk._client.endpoints.core}/api/v2/jobs/{self.job_id}/cancel" ) def test_cancel_job_sdk_error( - self, mock_request_exception: Generator[Any, Any, None] + self, mock_request_exception: requests_mock.mocker.Mocker ): """ When trying to cancel a job, we assert that an exception is raised @@ -132,10 +133,10 @@ def test_cancel_job_sdk_error( """ with pytest.raises(JobCancellingError): _ = self.sdk.cancel_job(self.job_id) - assert mock_request_exception.last_request.method == "PUT" + assert mock_request_exception.last_request.method == "PATCH" assert ( mock_request_exception.last_request.url - == f"{self.sdk._client.endpoints.core}/api/v1/jobs/{self.job_id}/cancel" + == f"{self.sdk._client.endpoints.core}/api/v2/jobs/{self.job_id}/cancel" ) def test_job_instantiation_with_extra_field(self, job): @@ -290,7 +291,7 @@ def test_get_jobs_with_pagination_success( ) def test_get_jobs_sdk_error( - self, mock_request_exception: Generator[Any, Any, None] + self, mock_request_exception: requests_mock.mocker.Mocker ): """ As a user using the SDK with proper credentials, @@ -406,7 +407,7 @@ def test_cancel_jobs_success( assert isinstance(response.errors, Dict) - assert mock_request.last_request.method == "PUT" + assert mock_request.last_request.method == "PATCH" # Convert filters to the appropriate format for query parameters query_params = build_query_params( @@ -415,7 +416,7 @@ def test_cancel_jobs_success( # Check that the correct url was requested with query params assert ( mock_request.last_request.url - == f"{self.sdk._client.endpoints.core}/api/v1/batches/{UUID(int=0x1)}" + == f"{self.sdk._client.endpoints.core}/api/v2/batches/{UUID(int=0x1)}" f"/cancel/jobs{query_params}" ) diff --git a/tests/test_workload.py b/tests/test_workload.py index d427da51..bfed4771 100644 --- a/tests/test_workload.py +++ b/tests/test_workload.py @@ -1,10 +1,10 @@ import json -from typing import Any, Generator from unittest.mock import patch from uuid import UUID, uuid4 import pytest import requests +import requests_mock from pasqal_cloud import SDK, Workload from pasqal_cloud.errors import ( @@ -52,7 +52,7 @@ def _mock_sleep(self): with patch("time.sleep"): yield - def test_create_workload(self, mock_request: Generator[Any, Any, None]): + def test_create_workload(self, mock_request: requests_mock.mocker.Mocker): workload = self.sdk.create_workload( backend=self.backend, workload_type=self.workload_type, @@ -69,7 +69,7 @@ def test_create_workload(self, mock_request: Generator[Any, Any, None]): assert mock_request.last_request.method == "POST" def test_create_workload_error( - self, mock_request_exception: Generator[Any, Any, None] + self, mock_request_exception: requests_mock.mocker.Mocker ): with pytest.raises(WorkloadCreationError): _ = self.sdk.create_workload( @@ -84,7 +84,7 @@ def test_create_workload_error( ) assert mock_request_exception.last_request.method == "POST" - def test_create_workload_and_wait(self, mock_request: Generator[Any, Any, None]): + def test_create_workload_and_wait(self, mock_request: requests_mock.mocker.Mocker): workload = self.sdk.create_workload( backend=self.backend, workload_type=self.workload_type, @@ -99,7 +99,7 @@ def test_create_workload_and_wait(self, mock_request: Generator[Any, Any, None]) assert mock_request.last_request.method == "GET" def test_get_workload( - self, mock_request: Generator[Any, Any, None], workload: Workload + self, mock_request: requests_mock.mocker.Mocker, workload: Workload ): workload_requested = self.sdk.get_workload(workload.id) assert workload_requested.id == self.workload_id @@ -110,7 +110,7 @@ def test_get_workload( def test_get_workload_with_link( self, - mock_request: Generator[Any, Any, None], + mock_request: requests_mock.mocker.Mocker, workload_with_link_id: Workload, result_link_endpoint: str, ): @@ -122,7 +122,7 @@ def test_get_workload_with_link( def test_get_workload_with_invalid_link( self, workload_with_invalid_link_id: Workload, - mock_request: Generator[Any, Any, None], + mock_request: requests_mock.mocker.Mocker, ): with pytest.raises(WorkloadResultsDecodeError): self.sdk.get_workload(workload_with_invalid_link_id) @@ -132,7 +132,7 @@ def test_get_workload_with_invalid_link( ) def test_get_workload_error( - self, mock_request_exception: Generator[Any, Any, None], workload: Workload + self, mock_request_exception: requests_mock.mocker.Mocker, workload: Workload ): with pytest.raises(WorkloadFetchingError): _ = self.sdk.get_workload(workload.id) @@ -144,7 +144,7 @@ def test_get_workload_error( assert mock_request_exception.last_request.method == "GET" def test_cancel_workload_self( - self, mock_request: Generator[Any, Any, None], workload: Workload + self, mock_request: requests_mock.mocker.Mocker, workload: Workload ): workload.cancel() assert workload.status == "CANCELED" @@ -165,7 +165,7 @@ def test_cancel_workload_self_error(self, mock_request_exception, workload): f"/api/v1/workloads/{self.workload_id}/cancel" ) - def test_cancel_workload_sdk(self, mock_request: Generator[Any, Any, None]): + def test_cancel_workload_sdk(self, mock_request: requests_mock.mocker.Mocker): client_rsp = self.sdk.cancel_workload(self.workload_id) assert type(client_rsp) == Workload assert client_rsp.status == "CANCELED" @@ -176,7 +176,7 @@ def test_cancel_workload_sdk(self, mock_request: Generator[Any, Any, None]): ) def test_cancel_workload_sdk_error( - self, mock_request_exception: Generator[Any, Any, None], workload: Workload + self, mock_request_exception: requests_mock.mocker.Mocker, workload: Workload ): with pytest.raises(WorkloadCancellingError): _ = self.sdk.cancel_workload(self.workload_id)