openquantum-sdk 0.3.7__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openquantum_sdk-0.3.7/openquantum_sdk.egg-info → openquantum_sdk-0.4.0}/PKG-INFO +18 -8
- openquantum_sdk-0.3.7/PKG-INFO → openquantum_sdk-0.4.0/README.md +15 -20
- openquantum_sdk-0.4.0/openquantum_sdk/__init__.py +21 -0
- {openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/openquantum_sdk/__main__.py +17 -2
- {openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/openquantum_sdk/backends.py +18 -0
- {openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/openquantum_sdk/clients.py +62 -3
- {openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/openquantum_sdk/models.py +93 -2
- openquantum_sdk-0.3.7/README.md → openquantum_sdk-0.4.0/openquantum_sdk.egg-info/PKG-INFO +30 -5
- openquantum_sdk-0.4.0/openquantum_sdk.egg-info/requires.txt +7 -0
- {openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/pyproject.toml +3 -3
- {openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/tests/test_backends.py +17 -0
- {openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/tests/test_clients.py +103 -0
- openquantum_sdk-0.3.7/openquantum_sdk/__init__.py +0 -5
- openquantum_sdk-0.3.7/openquantum_sdk.egg-info/requires.txt +0 -7
- {openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/MANIFEST.in +0 -0
- {openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/openquantum_sdk/auth.py +0 -0
- {openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/openquantum_sdk/enums.py +0 -0
- {openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/openquantum_sdk/qiskit/__init__.py +0 -0
- {openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/openquantum_sdk/utils.py +0 -0
- {openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/openquantum_sdk.egg-info/SOURCES.txt +0 -0
- {openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/openquantum_sdk.egg-info/dependency_links.txt +0 -0
- {openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/openquantum_sdk.egg-info/entry_points.txt +0 -0
- {openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/openquantum_sdk.egg-info/top_level.txt +0 -0
- {openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/requirements-dev.txt +0 -0
- {openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/requirements.txt +0 -0
- {openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/setup.cfg +0 -0
- {openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/tests/__init__.py +0 -0
- {openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/tests/test_clients_plan_priority.py +0 -0
- {openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/tests/test_qiskit_wrappers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openquantum-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Python SDK for Open Quantum
|
|
5
5
|
Classifier: Programming Language :: Python :: 3
|
|
6
6
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
@@ -9,9 +9,9 @@ Requires-Python: >=3.8
|
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
10
|
Requires-Dist: requests>=2.25.0
|
|
11
11
|
Provides-Extra: qiskit
|
|
12
|
-
Requires-Dist: openquantum-sdk-qiskit>=0.
|
|
12
|
+
Requires-Dist: openquantum-sdk-qiskit>=0.3.0; extra == "qiskit"
|
|
13
13
|
Provides-Extra: pennylane
|
|
14
|
-
Requires-Dist: openquantum-sdk-pennylane>=0.
|
|
14
|
+
Requires-Dist: openquantum-sdk-pennylane>=0.2.0; extra == "pennylane"
|
|
15
15
|
|
|
16
16
|
# Open Quantum Python SDK
|
|
17
17
|
|
|
@@ -21,9 +21,11 @@ A lightweight, modern Python SDK for the **Open Quantum Platform**.
|
|
|
21
21
|
|
|
22
22
|
* **Management API** — list organizations, backend classes, and providers
|
|
23
23
|
* **Scheduler API** — job categories, upload, prepare, submit, poll, and **one-call `submit_job()`**
|
|
24
|
+
* **Input formats** — OpenQASM (`qasm`) by default; other formats via `input_format` when the backend supports them
|
|
24
25
|
* **Smart defaults** — auto-select execution plan & queue priority from quote
|
|
25
26
|
* **Thread-safe auth** — Keycloak client-credentials with auto-refresh
|
|
26
27
|
* **Automatic output download** — `download_job_output()` returns parsed JSON
|
|
28
|
+
* **Calibration download** — `download_job_calibration()` when the job has `calibration_data_url`
|
|
27
29
|
|
|
28
30
|
## Installation
|
|
29
31
|
|
|
@@ -39,7 +41,7 @@ Python 3.9+ required.
|
|
|
39
41
|
|
|
40
42
|
### Option 1: SDK Key File (Recommended)
|
|
41
43
|
|
|
42
|
-
Download from [Open Quantum SDK Keys](https://openquantum.com/keys):
|
|
44
|
+
Download from [Open Quantum SDK Keys](https://www.openquantum.com/keys):
|
|
43
45
|
|
|
44
46
|
```json
|
|
45
47
|
{
|
|
@@ -130,6 +132,11 @@ try:
|
|
|
130
132
|
else:
|
|
131
133
|
print("No output available.")
|
|
132
134
|
|
|
135
|
+
if job.calibration_data_url:
|
|
136
|
+
cal_json = scheduler.download_job_calibration(job)
|
|
137
|
+
print("\n--- CALIBRATION (JSON) ---")
|
|
138
|
+
print(json.dumps(cal_json, indent=2))
|
|
139
|
+
|
|
133
140
|
except Exception as e:
|
|
134
141
|
print(f"\n--- JOB FAILED ---")
|
|
135
142
|
print(f"Error: {e}")
|
|
@@ -273,6 +280,8 @@ finally:
|
|
|
273
280
|
| `queue_priority` | `QueuePriorityType \| "auto"` | `"auto"` | |
|
|
274
281
|
| `auto_approve_quote` | `bool` | `True` | Set `False` in notebooks |
|
|
275
282
|
| `job_timeout_seconds` | `int` | `86400` | 1 day |
|
|
283
|
+
| `input_format` | `str` | `"qasm"` | OpenQASM by default; other values only if listed in backend `supported_formats` |
|
|
284
|
+
| `submitted_with` | `str` | `"sdk"` | Client surface for analytics |
|
|
276
285
|
|
|
277
286
|
## API Reference
|
|
278
287
|
|
|
@@ -291,6 +300,7 @@ finally:
|
|
|
291
300
|
| `cancel_job(job_id)` | `None` | Cancel running job |
|
|
292
301
|
| `list_jobs(organization_id, limit=20, cursor=..., status=...)` | `PaginatedJobs` | List your jobs |
|
|
293
302
|
| **`download_job_output(job)`** | `Any` | **Downloads + parses JSON output** |
|
|
303
|
+
| **`download_job_calibration(job)`** | `Any` | **Downloads + parses calibration JSON** (when tagged) |
|
|
294
304
|
| `submit_job(config, file_path=... or file_content=...)` | `JobRead` | **One-call submission + polling** |
|
|
295
305
|
|
|
296
306
|
### `ManagementClient`
|
|
@@ -308,15 +318,15 @@ finally:
|
|
|
308
318
|
| Model | Key Fields |
|
|
309
319
|
|-------|------------|
|
|
310
320
|
| `BackendClassRead` | `id`, `name`, `description`, `type`, `provider_id`, `short_code`, `queue_depth`, `accepting_jobs`, `status` |
|
|
311
|
-
| `ErrorResponse` | `status_code`, `message: List[str]`, `error_code` |
|
|
321
|
+
| `ErrorResponse` | `status_code`, `message: List[str]`, `type`, `error_code` (request id) |
|
|
312
322
|
| `JobCategoryRead` | `id`, `name`, `short_code` |
|
|
313
323
|
| `JobCreate` | `organization_id`, `job_preparation_id`, `execution_plan_id`, `queue_priority_id` |
|
|
314
|
-
| `JobList` | `id`, `backend_class_id`, `name`, `status`, `backend_name`, `output_data_url`, `credits_used`, `submitted_at` |
|
|
315
|
-
| `JobPreparationCreate` | `organization_id`, `backend_class_id`, `name`, `upload_endpoint_id`, `job_subcategory_id`, `shots`, `configuration_data` |
|
|
324
|
+
| `JobList` | `id`, `backend_class_id`, `name`, `status`, `backend_name`, `output_data_url`, `calibration_data_url`, `credits_used`, `submitted_at` |
|
|
325
|
+
| `JobPreparationCreate` | `organization_id`, `backend_class_id`, `name`, `upload_endpoint_id`, `job_subcategory_id`, `shots`, `configuration_data`, `input_format`, `submitted_with` |
|
|
316
326
|
| `JobPreparationResponse` | `id` |
|
|
317
327
|
| `JobPreparationResultResponse` | `organization_id`, `backend_class_id`, `status`, `name`, `input_data_url`, `job_category_id`, `job_subcategory_id`, `shots`, `quote: List[QuotePlan]`, `message`, `configuration_data` |
|
|
318
328
|
| `JobPreparationUploadResponse` | `id`, `url` |
|
|
319
|
-
| `JobRead` | `id`, `status`, `input_data_url`, `job_preparation_id`, `execution_plan_id`, `queue_priority_id`, `message`, `output_data_url`, `transaction_id`, `submitted_at` |
|
|
329
|
+
| `JobRead` | `id`, `status`, `input_data_url`, `job_preparation_id`, `execution_plan_id`, `queue_priority_id`, `message`, `output_data_url`, `calibration_data_url`, `transaction_id`, `submitted_at` |
|
|
320
330
|
| `OrganizationRead` | `id`, `name` |
|
|
321
331
|
| `PaginatedBackendClasses` | `backend_classes: List[BackendClassRead]`, `pagination: PaginationInfo` |
|
|
322
332
|
| `PaginatedJobCategories` | `categories: List[JobCategoryRead]`, `pagination: PaginationInfo` |
|
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: openquantum-sdk
|
|
3
|
-
Version: 0.3.7
|
|
4
|
-
Summary: Python SDK for Open Quantum
|
|
5
|
-
Classifier: Programming Language :: Python :: 3
|
|
6
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
7
|
-
Classifier: Operating System :: OS Independent
|
|
8
|
-
Requires-Python: >=3.8
|
|
9
|
-
Description-Content-Type: text/markdown
|
|
10
|
-
Requires-Dist: requests>=2.25.0
|
|
11
|
-
Provides-Extra: qiskit
|
|
12
|
-
Requires-Dist: openquantum-sdk-qiskit>=0.2.4; extra == "qiskit"
|
|
13
|
-
Provides-Extra: pennylane
|
|
14
|
-
Requires-Dist: openquantum-sdk-pennylane>=0.1.0; extra == "pennylane"
|
|
15
|
-
|
|
16
1
|
# Open Quantum Python SDK
|
|
17
2
|
|
|
18
3
|
A lightweight, modern Python SDK for the **Open Quantum Platform**.
|
|
@@ -21,9 +6,11 @@ A lightweight, modern Python SDK for the **Open Quantum Platform**.
|
|
|
21
6
|
|
|
22
7
|
* **Management API** — list organizations, backend classes, and providers
|
|
23
8
|
* **Scheduler API** — job categories, upload, prepare, submit, poll, and **one-call `submit_job()`**
|
|
9
|
+
* **Input formats** — OpenQASM (`qasm`) by default; other formats via `input_format` when the backend supports them
|
|
24
10
|
* **Smart defaults** — auto-select execution plan & queue priority from quote
|
|
25
11
|
* **Thread-safe auth** — Keycloak client-credentials with auto-refresh
|
|
26
12
|
* **Automatic output download** — `download_job_output()` returns parsed JSON
|
|
13
|
+
* **Calibration download** — `download_job_calibration()` when the job has `calibration_data_url`
|
|
27
14
|
|
|
28
15
|
## Installation
|
|
29
16
|
|
|
@@ -39,7 +26,7 @@ Python 3.9+ required.
|
|
|
39
26
|
|
|
40
27
|
### Option 1: SDK Key File (Recommended)
|
|
41
28
|
|
|
42
|
-
Download from [Open Quantum SDK Keys](https://openquantum.com/keys):
|
|
29
|
+
Download from [Open Quantum SDK Keys](https://www.openquantum.com/keys):
|
|
43
30
|
|
|
44
31
|
```json
|
|
45
32
|
{
|
|
@@ -130,6 +117,11 @@ try:
|
|
|
130
117
|
else:
|
|
131
118
|
print("No output available.")
|
|
132
119
|
|
|
120
|
+
if job.calibration_data_url:
|
|
121
|
+
cal_json = scheduler.download_job_calibration(job)
|
|
122
|
+
print("\n--- CALIBRATION (JSON) ---")
|
|
123
|
+
print(json.dumps(cal_json, indent=2))
|
|
124
|
+
|
|
133
125
|
except Exception as e:
|
|
134
126
|
print(f"\n--- JOB FAILED ---")
|
|
135
127
|
print(f"Error: {e}")
|
|
@@ -273,6 +265,8 @@ finally:
|
|
|
273
265
|
| `queue_priority` | `QueuePriorityType \| "auto"` | `"auto"` | |
|
|
274
266
|
| `auto_approve_quote` | `bool` | `True` | Set `False` in notebooks |
|
|
275
267
|
| `job_timeout_seconds` | `int` | `86400` | 1 day |
|
|
268
|
+
| `input_format` | `str` | `"qasm"` | OpenQASM by default; other values only if listed in backend `supported_formats` |
|
|
269
|
+
| `submitted_with` | `str` | `"sdk"` | Client surface for analytics |
|
|
276
270
|
|
|
277
271
|
## API Reference
|
|
278
272
|
|
|
@@ -291,6 +285,7 @@ finally:
|
|
|
291
285
|
| `cancel_job(job_id)` | `None` | Cancel running job |
|
|
292
286
|
| `list_jobs(organization_id, limit=20, cursor=..., status=...)` | `PaginatedJobs` | List your jobs |
|
|
293
287
|
| **`download_job_output(job)`** | `Any` | **Downloads + parses JSON output** |
|
|
288
|
+
| **`download_job_calibration(job)`** | `Any` | **Downloads + parses calibration JSON** (when tagged) |
|
|
294
289
|
| `submit_job(config, file_path=... or file_content=...)` | `JobRead` | **One-call submission + polling** |
|
|
295
290
|
|
|
296
291
|
### `ManagementClient`
|
|
@@ -308,15 +303,15 @@ finally:
|
|
|
308
303
|
| Model | Key Fields |
|
|
309
304
|
|-------|------------|
|
|
310
305
|
| `BackendClassRead` | `id`, `name`, `description`, `type`, `provider_id`, `short_code`, `queue_depth`, `accepting_jobs`, `status` |
|
|
311
|
-
| `ErrorResponse` | `status_code`, `message: List[str]`, `error_code` |
|
|
306
|
+
| `ErrorResponse` | `status_code`, `message: List[str]`, `type`, `error_code` (request id) |
|
|
312
307
|
| `JobCategoryRead` | `id`, `name`, `short_code` |
|
|
313
308
|
| `JobCreate` | `organization_id`, `job_preparation_id`, `execution_plan_id`, `queue_priority_id` |
|
|
314
|
-
| `JobList` | `id`, `backend_class_id`, `name`, `status`, `backend_name`, `output_data_url`, `credits_used`, `submitted_at` |
|
|
315
|
-
| `JobPreparationCreate` | `organization_id`, `backend_class_id`, `name`, `upload_endpoint_id`, `job_subcategory_id`, `shots`, `configuration_data` |
|
|
309
|
+
| `JobList` | `id`, `backend_class_id`, `name`, `status`, `backend_name`, `output_data_url`, `calibration_data_url`, `credits_used`, `submitted_at` |
|
|
310
|
+
| `JobPreparationCreate` | `organization_id`, `backend_class_id`, `name`, `upload_endpoint_id`, `job_subcategory_id`, `shots`, `configuration_data`, `input_format`, `submitted_with` |
|
|
316
311
|
| `JobPreparationResponse` | `id` |
|
|
317
312
|
| `JobPreparationResultResponse` | `organization_id`, `backend_class_id`, `status`, `name`, `input_data_url`, `job_category_id`, `job_subcategory_id`, `shots`, `quote: List[QuotePlan]`, `message`, `configuration_data` |
|
|
318
313
|
| `JobPreparationUploadResponse` | `id`, `url` |
|
|
319
|
-
| `JobRead` | `id`, `status`, `input_data_url`, `job_preparation_id`, `execution_plan_id`, `queue_priority_id`, `message`, `output_data_url`, `transaction_id`, `submitted_at` |
|
|
314
|
+
| `JobRead` | `id`, `status`, `input_data_url`, `job_preparation_id`, `execution_plan_id`, `queue_priority_id`, `message`, `output_data_url`, `calibration_data_url`, `transaction_id`, `submitted_at` |
|
|
320
315
|
| `OrganizationRead` | `id`, `name` |
|
|
321
316
|
| `PaginatedBackendClasses` | `backend_classes: List[BackendClassRead]`, `pagination: PaginationInfo` |
|
|
322
317
|
| `PaginatedJobCategories` | `categories: List[JobCategoryRead]`, `pagination: PaginationInfo` |
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .clients import SchedulerClient, ManagementClient
|
|
2
|
+
from .models import (
|
|
3
|
+
APIError,
|
|
4
|
+
ApiErrorType,
|
|
5
|
+
TermsOfUseRequiredError,
|
|
6
|
+
TERMS_OF_USE_REQUIRED_MESSAGE,
|
|
7
|
+
is_not_found_error,
|
|
8
|
+
)
|
|
9
|
+
from .utils import poll_for_status
|
|
10
|
+
|
|
11
|
+
__version__ = "0.4.0"
|
|
12
|
+
__all__ = [
|
|
13
|
+
"SchedulerClient",
|
|
14
|
+
"ManagementClient",
|
|
15
|
+
"poll_for_status",
|
|
16
|
+
"APIError",
|
|
17
|
+
"ApiErrorType",
|
|
18
|
+
"TermsOfUseRequiredError",
|
|
19
|
+
"TERMS_OF_USE_REQUIRED_MESSAGE",
|
|
20
|
+
"is_not_found_error",
|
|
21
|
+
]
|
|
@@ -6,7 +6,8 @@ import json
|
|
|
6
6
|
import argparse
|
|
7
7
|
import requests
|
|
8
8
|
from .auth import ClientCredentials, ClientCredentialsAuth
|
|
9
|
-
from .clients import
|
|
9
|
+
from .clients import SchedulerClient, ManagementClient, JobSubmissionConfig
|
|
10
|
+
from .models import APIError, TermsOfUseRequiredError
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
def load_creds_from_file(path: str) -> ClientCredentials:
|
|
@@ -163,12 +164,13 @@ def main():
|
|
|
163
164
|
print(f"Final Status: {job.status}")
|
|
164
165
|
print(f"Submitted At: {job.submitted_at}")
|
|
165
166
|
print(f"Output URL: {job.output_data_url}")
|
|
167
|
+
print(f"Calibration URL: {job.calibration_data_url}")
|
|
166
168
|
print("---------------------")
|
|
167
169
|
|
|
168
170
|
if job.output_data_url:
|
|
169
171
|
print("\nFetching job output...")
|
|
170
172
|
try:
|
|
171
|
-
output_json = scheduler.download_job_output(job
|
|
173
|
+
output_json = scheduler.download_job_output(job)
|
|
172
174
|
print("\n--- JOB OUTPUT (JSON) ---")
|
|
173
175
|
print(json.dumps(output_json, indent=2))
|
|
174
176
|
print("-------------------------")
|
|
@@ -177,6 +179,19 @@ def main():
|
|
|
177
179
|
else:
|
|
178
180
|
print("\nNo output_data_url available (job may have failed or produced no output).")
|
|
179
181
|
|
|
182
|
+
if job.calibration_data_url:
|
|
183
|
+
print("\nFetching calibration data...")
|
|
184
|
+
try:
|
|
185
|
+
cal_json = scheduler.download_job_calibration(job)
|
|
186
|
+
print("\n--- CALIBRATION (JSON) ---")
|
|
187
|
+
print(json.dumps(cal_json, indent=2))
|
|
188
|
+
print("--------------------------")
|
|
189
|
+
except Exception as e:
|
|
190
|
+
print(f"Failed to download/parse calibration: {e}", file=sys.stderr)
|
|
191
|
+
|
|
192
|
+
except TermsOfUseRequiredError as e:
|
|
193
|
+
print(str(e), file=sys.stderr)
|
|
194
|
+
sys.exit(1)
|
|
180
195
|
except requests.exceptions.HTTPError as e:
|
|
181
196
|
print(f"API error: {e.response.status_code} - {e.response.text}", file=sys.stderr)
|
|
182
197
|
sys.exit(1)
|
|
@@ -67,6 +67,9 @@ class BackendCapabilities:
|
|
|
67
67
|
"verbatim_boxes", ...). See sdk/docs/device-features.md and
|
|
68
68
|
the DeviceFeatures enum. These drive precompilation,
|
|
69
69
|
validation, and SDK-side decisions before jobs reach QPUs.
|
|
70
|
+
supported_formats: Circuit input language families this backend
|
|
71
|
+
accepts (e.g. ``"qasm"``, ``"qblox_sequence"``). Defaults to
|
|
72
|
+
``["qasm"]``. OpenQASM 2 and 3 both map to ``"qasm"``.
|
|
70
73
|
"""
|
|
71
74
|
|
|
72
75
|
short_code: str
|
|
@@ -76,6 +79,7 @@ class BackendCapabilities:
|
|
|
76
79
|
topology: Topology = field(default_factory=Topology)
|
|
77
80
|
limits: Dict[str, Any] = field(default_factory=dict)
|
|
78
81
|
features: List[str] = field(default_factory=list)
|
|
82
|
+
supported_formats: List[str] = field(default_factory=lambda: ["qasm"])
|
|
79
83
|
|
|
80
84
|
@property
|
|
81
85
|
def supported_gate_names(self) -> set[str]:
|
|
@@ -136,6 +140,11 @@ class BackendCapabilities:
|
|
|
136
140
|
"""True if classical control flow (if/for/while on classical bits) is supported."""
|
|
137
141
|
return self.supports_feature("classical_control")
|
|
138
142
|
|
|
143
|
+
def supports_format(self, name: str) -> bool:
|
|
144
|
+
"""Return True if the backend accepts the given input format family."""
|
|
145
|
+
formats = self.supported_formats or ["qasm"]
|
|
146
|
+
return name in formats
|
|
147
|
+
|
|
139
148
|
def to_dict(self) -> Dict[str, Any]:
|
|
140
149
|
"""Convert to the dict format used by the Qiskit plugin's target builder."""
|
|
141
150
|
return {
|
|
@@ -148,6 +157,9 @@ class BackendCapabilities:
|
|
|
148
157
|
"noise": {},
|
|
149
158
|
"limits": self.limits,
|
|
150
159
|
"features": list(self.features) if self.features else ["measure"],
|
|
160
|
+
"supported_formats": list(self.supported_formats)
|
|
161
|
+
if self.supported_formats
|
|
162
|
+
else ["qasm"],
|
|
151
163
|
}
|
|
152
164
|
|
|
153
165
|
|
|
@@ -624,6 +636,11 @@ def capabilities_from_constraint_data(
|
|
|
624
636
|
directed_edges=topo_data.get("directed_edges", False),
|
|
625
637
|
coupling_map=topo_data.get("coupling_map", []),
|
|
626
638
|
)
|
|
639
|
+
raw_formats = constraint_data.get("supported_formats") or []
|
|
640
|
+
supported_formats = (
|
|
641
|
+
[str(f) for f in raw_formats] if raw_formats else ["qasm"]
|
|
642
|
+
)
|
|
643
|
+
|
|
627
644
|
return BackendCapabilities(
|
|
628
645
|
short_code=short_code,
|
|
629
646
|
n_qubits=constraint_data.get("n_qubits", 0),
|
|
@@ -632,6 +649,7 @@ def capabilities_from_constraint_data(
|
|
|
632
649
|
topology=topology,
|
|
633
650
|
limits=constraint_data.get("limits", {}),
|
|
634
651
|
features=constraint_data.get("features", []) or [],
|
|
652
|
+
supported_formats=supported_formats,
|
|
635
653
|
)
|
|
636
654
|
|
|
637
655
|
|
|
@@ -11,6 +11,9 @@ from .enums import AutoChoice, ExecutionPlanType, QueuePriorityType
|
|
|
11
11
|
from .utils import strip_gate_definitions
|
|
12
12
|
from .models import (
|
|
13
13
|
APIError,
|
|
14
|
+
TermsOfUseRequiredError,
|
|
15
|
+
error_from_response,
|
|
16
|
+
is_not_found_error,
|
|
14
17
|
BackendClassRead,
|
|
15
18
|
CreditBalanceRead,
|
|
16
19
|
ErrorResponse,
|
|
@@ -66,6 +69,12 @@ class JobSubmissionConfig:
|
|
|
66
69
|
submission. Useful in notebooks and CLI scripts.
|
|
67
70
|
job_timeout_seconds: Maximum seconds to wait for the job to complete.
|
|
68
71
|
Defaults to 86 400 (1 day).
|
|
72
|
+
submitted_with: Client/surface that submitted this preparation
|
|
73
|
+
(e.g. ``"sdk"``, ``"qiskit"``, ``"cli"``). Defaults to ``"sdk"``.
|
|
74
|
+
input_format: Circuit input language family (``"qasm"`` or
|
|
75
|
+
``"qblox_sequence"``). Defaults to ``"qasm"``. OpenQASM 2/3
|
|
76
|
+
both use ``"qasm"``; use ``"qblox_sequence"`` for Qblox
|
|
77
|
+
sequencer JSON (program + waveforms).
|
|
69
78
|
|
|
70
79
|
Example:
|
|
71
80
|
>>> config = JobSubmissionConfig(
|
|
@@ -93,6 +102,12 @@ class JobSubmissionConfig:
|
|
|
93
102
|
# USER-CONTROLLABLE: How long to wait for job to finish
|
|
94
103
|
job_timeout_seconds: int = 86_400 # 1 day
|
|
95
104
|
|
|
105
|
+
# Attribution for analytics / provenance
|
|
106
|
+
submitted_with: Optional[str] = "sdk"
|
|
107
|
+
|
|
108
|
+
# Circuit language family (qasm | qblox_sequence). None → API default "qasm".
|
|
109
|
+
input_format: Optional[str] = None
|
|
110
|
+
|
|
96
111
|
# INTERNAL: Fixed preparation timeout and polling
|
|
97
112
|
_preparation_timeout_seconds: int = 300 # 5 minutes
|
|
98
113
|
_preparation_poll_interval: float = 2.0
|
|
@@ -159,9 +174,12 @@ class BaseClient:
|
|
|
159
174
|
err = ErrorResponse(
|
|
160
175
|
status_code=payload.get("status_code", resp.status_code),
|
|
161
176
|
message=payload.get("message", []),
|
|
177
|
+
type=payload.get("type"),
|
|
162
178
|
error_code=payload.get("error_code"),
|
|
163
179
|
)
|
|
164
|
-
raise
|
|
180
|
+
raise error_from_response(err)
|
|
181
|
+
except (TermsOfUseRequiredError, APIError):
|
|
182
|
+
raise
|
|
165
183
|
except ValueError:
|
|
166
184
|
pass
|
|
167
185
|
resp.raise_for_status()
|
|
@@ -422,6 +440,39 @@ class SchedulerClient(BaseClient):
|
|
|
422
440
|
"Content may not be JSON or is malformed."
|
|
423
441
|
) from exc
|
|
424
442
|
|
|
443
|
+
def download_job_calibration(self, job: JobRead) -> Any:
|
|
444
|
+
"""Download and parse the calibration snapshot tagged on a job.
|
|
445
|
+
|
|
446
|
+
Calibration is optional: only present if reported.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
job: A ``JobRead`` from ``get_job`` / ``submit_job``. Must have a
|
|
450
|
+
non-``None`` ``calibration_data_url``.
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
Parsed JSON calibration payload.
|
|
454
|
+
|
|
455
|
+
Raises:
|
|
456
|
+
RuntimeError: If the job has no ``calibration_data_url``.
|
|
457
|
+
ValueError: If the body cannot be parsed as JSON.
|
|
458
|
+
"""
|
|
459
|
+
if not job.calibration_data_url:
|
|
460
|
+
raise RuntimeError(
|
|
461
|
+
f"Job {job.id} has no calibration_data_url. "
|
|
462
|
+
"No calibration was tagged on the completing execution."
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
resp = requests.get(job.calibration_data_url)
|
|
466
|
+
resp.raise_for_status()
|
|
467
|
+
|
|
468
|
+
try:
|
|
469
|
+
return resp.json()
|
|
470
|
+
except ValueError as exc:
|
|
471
|
+
raise ValueError(
|
|
472
|
+
f"Failed to parse calibration of job {job.id} as JSON. "
|
|
473
|
+
"Content may not be JSON or is malformed."
|
|
474
|
+
) from exc
|
|
475
|
+
|
|
425
476
|
def prepare_job(self, preparation: JobPreparationCreate) -> JobPreparationResponse:
|
|
426
477
|
"""Submit a job preparation request.
|
|
427
478
|
|
|
@@ -435,7 +486,12 @@ class SchedulerClient(BaseClient):
|
|
|
435
486
|
Returns:
|
|
436
487
|
A ``JobPreparationResponse`` containing the preparation ``id``.
|
|
437
488
|
"""
|
|
438
|
-
|
|
489
|
+
payload = {
|
|
490
|
+
key: value
|
|
491
|
+
for key, value in asdict(preparation).items()
|
|
492
|
+
if value is not None
|
|
493
|
+
}
|
|
494
|
+
data = self._request("POST", "/v1/jobs/prepare", json=payload)
|
|
439
495
|
return JobPreparationResponse.from_dict(data)
|
|
440
496
|
|
|
441
497
|
def get_preparation_result(self, preparation_id: str) -> JobPreparationResultResponse:
|
|
@@ -496,6 +552,7 @@ class SchedulerClient(BaseClient):
|
|
|
496
552
|
message=data.get("message"),
|
|
497
553
|
quote=quote,
|
|
498
554
|
configuration_data=data.get("configuration_data", {}),
|
|
555
|
+
input_format=data.get("input_format"),
|
|
499
556
|
)
|
|
500
557
|
|
|
501
558
|
def create_job(self, job: JobCreate) -> JobRead:
|
|
@@ -651,6 +708,8 @@ class SchedulerClient(BaseClient):
|
|
|
651
708
|
job_subcategory_id=config.job_subcategory_id,
|
|
652
709
|
shots=config.shots,
|
|
653
710
|
configuration_data=config.configuration_data or {},
|
|
711
|
+
submitted_with=config.submitted_with,
|
|
712
|
+
input_format=config.input_format,
|
|
654
713
|
)
|
|
655
714
|
if config.verbose:
|
|
656
715
|
print("Step 2/5: Preparing job and fetching quote...")
|
|
@@ -749,7 +808,7 @@ class SchedulerClient(BaseClient):
|
|
|
749
808
|
done = resp.status in ("Completed", "Failed")
|
|
750
809
|
return done, resp
|
|
751
810
|
except APIError as e:
|
|
752
|
-
if e
|
|
811
|
+
if is_not_found_error(e):
|
|
753
812
|
if verbose:
|
|
754
813
|
print(" > Preparation status: Not found (retrying...)")
|
|
755
814
|
return False, None
|
|
@@ -50,6 +50,24 @@ class PaginationInfo(FlexibleDataclass):
|
|
|
50
50
|
extra: Dict[str, Any] = field(default_factory=dict, repr=False)
|
|
51
51
|
|
|
52
52
|
|
|
53
|
+
class ApiErrorType:
|
|
54
|
+
UNAUTHORIZED = "UNAUTHORIZED"
|
|
55
|
+
FORBIDDEN = "FORBIDDEN"
|
|
56
|
+
INSUFFICIENT_PERMISSIONS = "INSUFFICIENT_PERMISSIONS"
|
|
57
|
+
ORG_MEMBERSHIP_REQUIRED = "ORG_MEMBERSHIP_REQUIRED"
|
|
58
|
+
TERMS_OF_USE_REQUIRED = "TERMS_OF_USE_REQUIRED"
|
|
59
|
+
NOT_FOUND = "NOT_FOUND"
|
|
60
|
+
CONFLICT = "CONFLICT"
|
|
61
|
+
VALIDATION_ERROR = "VALIDATION_ERROR"
|
|
62
|
+
BAD_REQUEST = "BAD_REQUEST"
|
|
63
|
+
INSUFFICIENT_CREDITS = "INSUFFICIENT_CREDITS"
|
|
64
|
+
JOB_LIMIT_REACHED = "JOB_LIMIT_REACHED"
|
|
65
|
+
JOB_NOT_CANCELABLE = "JOB_NOT_CANCELABLE"
|
|
66
|
+
RESOURCE_NOT_ACTIVE = "RESOURCE_NOT_ACTIVE"
|
|
67
|
+
NOT_READY = "NOT_READY"
|
|
68
|
+
INTERNAL_ERROR = "INTERNAL_ERROR"
|
|
69
|
+
|
|
70
|
+
|
|
53
71
|
@dataclass
|
|
54
72
|
class ErrorResponse:
|
|
55
73
|
"""Structured error payload returned by the API on HTTP 4xx/5xx responses.
|
|
@@ -57,11 +75,13 @@ class ErrorResponse:
|
|
|
57
75
|
Attributes:
|
|
58
76
|
status_code: The HTTP status code from the response.
|
|
59
77
|
message: List of human-readable error message strings.
|
|
60
|
-
|
|
78
|
+
type: Stable machine-readable error type (see ``ApiErrorType``).
|
|
79
|
+
error_code: Request identifier for support tracing.
|
|
61
80
|
"""
|
|
62
81
|
|
|
63
82
|
status_code: int
|
|
64
83
|
message: Union[List[str], str]
|
|
84
|
+
type: Optional[str] = None
|
|
65
85
|
error_code: Optional[str] = None
|
|
66
86
|
|
|
67
87
|
|
|
@@ -79,7 +99,59 @@ class APIError(Exception):
|
|
|
79
99
|
self.error_response = error_response
|
|
80
100
|
msgs = error_response.message if isinstance(error_response.message, list) else [error_response.message]
|
|
81
101
|
msg = "; ".join(msgs)
|
|
82
|
-
|
|
102
|
+
type_part = f" [{error_response.type}]" if error_response.type else ""
|
|
103
|
+
super().__init__(
|
|
104
|
+
f"API Error {error_response.status_code}{type_part}: {msg} "
|
|
105
|
+
f"(Request ID: {error_response.error_code})"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
TERMS_OF_USE_REQUIRED_MESSAGE = (
|
|
110
|
+
"Please visit https://www.openquantum.com to accept the Terms of Use before submitting jobs."
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class TermsOfUseRequiredError(APIError):
|
|
115
|
+
"""Raised when job prepare/create is blocked until Terms of Use are accepted.
|
|
116
|
+
|
|
117
|
+
Users must visit https://www.openquantum.com, sign in, and accept the latest
|
|
118
|
+
Terms of Use (and complete portal onboarding) before submitting jobs.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
def __init__(self, error_response: Optional[ErrorResponse] = None):
|
|
122
|
+
if error_response is None:
|
|
123
|
+
error_response = ErrorResponse(
|
|
124
|
+
status_code=403,
|
|
125
|
+
message=TERMS_OF_USE_REQUIRED_MESSAGE,
|
|
126
|
+
type=ApiErrorType.TERMS_OF_USE_REQUIRED,
|
|
127
|
+
)
|
|
128
|
+
# Keep error_response for callers that inspect status/request id, but
|
|
129
|
+
# present a clean, actionable message (not the generic API Error wrapper).
|
|
130
|
+
self.error_response = error_response
|
|
131
|
+
Exception.__init__(self, TERMS_OF_USE_REQUIRED_MESSAGE)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def error_from_response(error_response: ErrorResponse) -> APIError:
|
|
135
|
+
"""Map an API ``ErrorResponse`` to the most specific SDK exception.
|
|
136
|
+
|
|
137
|
+
Branching uses the stable ``type`` field only — never message text.
|
|
138
|
+
"""
|
|
139
|
+
if error_response.type == ApiErrorType.TERMS_OF_USE_REQUIRED:
|
|
140
|
+
return TermsOfUseRequiredError(error_response)
|
|
141
|
+
return APIError(error_response)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def is_not_found_error(error: APIError) -> bool:
|
|
145
|
+
"""Return True if the error indicates a missing resource.
|
|
146
|
+
|
|
147
|
+
Prefers ``ErrorResponse.type``; falls back to HTTP 404 when ``type``
|
|
148
|
+
is absent (e.g. older gateways).
|
|
149
|
+
"""
|
|
150
|
+
if error.error_response.type == ApiErrorType.NOT_FOUND:
|
|
151
|
+
return True
|
|
152
|
+
if error.error_response.type is None and error.error_response.status_code == 404:
|
|
153
|
+
return True
|
|
154
|
+
return False
|
|
83
155
|
|
|
84
156
|
|
|
85
157
|
# ---------------------------------------------------------------------------
|
|
@@ -253,6 +325,14 @@ class JobPreparationCreate:
|
|
|
253
325
|
job_subcategory_id: Job subcategory UUID or short code.
|
|
254
326
|
shots: Number of measurement shots to execute.
|
|
255
327
|
configuration_data: Optional backend-specific configuration.
|
|
328
|
+
submitted_with: Client/surface that submitted this preparation
|
|
329
|
+
(e.g. ``"sdk"``, ``"qiskit"``, ``"pennylane"``, ``"cli"``).
|
|
330
|
+
Optional; omit or ``None`` for unknown/legacy clients.
|
|
331
|
+
input_format: Circuit input language family (``"qasm"`` or
|
|
332
|
+
``"qblox_sequence"``). Defaults to ``"qasm"``. OpenQASM 2 and
|
|
333
|
+
3 both use ``"qasm"``; version is detected from the file.
|
|
334
|
+
``"qblox_sequence"`` is Qblox sequencer JSON. Must be listed
|
|
335
|
+
in the backend's ``constraint_data.supported_formats``.
|
|
256
336
|
"""
|
|
257
337
|
|
|
258
338
|
organization_id: str
|
|
@@ -262,6 +342,8 @@ class JobPreparationCreate:
|
|
|
262
342
|
job_subcategory_id: str
|
|
263
343
|
shots: int
|
|
264
344
|
configuration_data: Dict[str, Any] = field(default_factory=dict)
|
|
345
|
+
submitted_with: Optional[str] = None
|
|
346
|
+
input_format: Optional[str] = None
|
|
265
347
|
|
|
266
348
|
|
|
267
349
|
@dataclass
|
|
@@ -355,6 +437,8 @@ class JobPreparationResultResponse:
|
|
|
355
437
|
quote: List of available execution plans with pricing details.
|
|
356
438
|
message: Optional status or error message.
|
|
357
439
|
configuration_data: Backend-specific configuration echoed back.
|
|
440
|
+
input_format: Circuit input language family (``"qasm"`` or
|
|
441
|
+
``"qblox_sequence"``).
|
|
358
442
|
extra: Additional fields returned by the API not mapped to attributes.
|
|
359
443
|
"""
|
|
360
444
|
|
|
@@ -369,6 +453,7 @@ class JobPreparationResultResponse:
|
|
|
369
453
|
quote: List[QuotePlan]
|
|
370
454
|
message: Optional[str] = None
|
|
371
455
|
configuration_data: Dict[str, Any] = field(default_factory=dict)
|
|
456
|
+
input_format: Optional[str] = None
|
|
372
457
|
extra: Dict[str, Any] = field(default_factory=dict, repr=False)
|
|
373
458
|
|
|
374
459
|
|
|
@@ -406,6 +491,8 @@ class JobRead(FlexibleDataclass):
|
|
|
406
491
|
message: Optional status or error message from the backend.
|
|
407
492
|
output_data_url: URL to download results from, available once
|
|
408
493
|
the job completes.
|
|
494
|
+
calibration_data_url: Presigned URL for the calibration snapshot
|
|
495
|
+
tagged on the successful execution, if any.
|
|
409
496
|
transaction_id: Credit transaction ID, if a charge was applied.
|
|
410
497
|
submitted_at: ISO 8601 timestamp of when the job was submitted.
|
|
411
498
|
extra: Additional fields returned by the API not mapped to attributes.
|
|
@@ -419,6 +506,7 @@ class JobRead(FlexibleDataclass):
|
|
|
419
506
|
queue_priority_id: str
|
|
420
507
|
message: Optional[str] = None
|
|
421
508
|
output_data_url: Optional[str] = None
|
|
509
|
+
calibration_data_url: Optional[str] = None
|
|
422
510
|
transaction_id: Optional[str] = None
|
|
423
511
|
submitted_at: Optional[str] = None
|
|
424
512
|
extra: Dict[str, Any] = field(default_factory=dict, repr=False)
|
|
@@ -438,6 +526,8 @@ class JobList(FlexibleDataclass):
|
|
|
438
526
|
status: Current job status string.
|
|
439
527
|
backend_name: Display name of the backend, if available.
|
|
440
528
|
output_data_url: URL to download results, if the job completed.
|
|
529
|
+
calibration_data_url: Presigned URL for the calibration snapshot
|
|
530
|
+
tagged on the successful execution, if any.
|
|
441
531
|
credits_used: Number of credits charged for this job.
|
|
442
532
|
submitted_at: ISO 8601 timestamp of when the job was submitted.
|
|
443
533
|
extra: Additional fields returned by the API not mapped to attributes.
|
|
@@ -449,6 +539,7 @@ class JobList(FlexibleDataclass):
|
|
|
449
539
|
status: str
|
|
450
540
|
backend_name: Optional[str] = None
|
|
451
541
|
output_data_url: Optional[str] = None
|
|
542
|
+
calibration_data_url: Optional[str] = None
|
|
452
543
|
credits_used: Optional[float] = None
|
|
453
544
|
submitted_at: Optional[str] = None
|
|
454
545
|
extra: Dict[str, Any] = field(default_factory=dict, repr=False)
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openquantum-sdk
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Python SDK for Open Quantum
|
|
5
|
+
Classifier: Programming Language :: Python :: 3
|
|
6
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
7
|
+
Classifier: Operating System :: OS Independent
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: requests>=2.25.0
|
|
11
|
+
Provides-Extra: qiskit
|
|
12
|
+
Requires-Dist: openquantum-sdk-qiskit>=0.3.0; extra == "qiskit"
|
|
13
|
+
Provides-Extra: pennylane
|
|
14
|
+
Requires-Dist: openquantum-sdk-pennylane>=0.2.0; extra == "pennylane"
|
|
15
|
+
|
|
1
16
|
# Open Quantum Python SDK
|
|
2
17
|
|
|
3
18
|
A lightweight, modern Python SDK for the **Open Quantum Platform**.
|
|
@@ -6,9 +21,11 @@ A lightweight, modern Python SDK for the **Open Quantum Platform**.
|
|
|
6
21
|
|
|
7
22
|
* **Management API** — list organizations, backend classes, and providers
|
|
8
23
|
* **Scheduler API** — job categories, upload, prepare, submit, poll, and **one-call `submit_job()`**
|
|
24
|
+
* **Input formats** — OpenQASM (`qasm`) by default; other formats via `input_format` when the backend supports them
|
|
9
25
|
* **Smart defaults** — auto-select execution plan & queue priority from quote
|
|
10
26
|
* **Thread-safe auth** — Keycloak client-credentials with auto-refresh
|
|
11
27
|
* **Automatic output download** — `download_job_output()` returns parsed JSON
|
|
28
|
+
* **Calibration download** — `download_job_calibration()` when the job has `calibration_data_url`
|
|
12
29
|
|
|
13
30
|
## Installation
|
|
14
31
|
|
|
@@ -24,7 +41,7 @@ Python 3.9+ required.
|
|
|
24
41
|
|
|
25
42
|
### Option 1: SDK Key File (Recommended)
|
|
26
43
|
|
|
27
|
-
Download from [Open Quantum SDK Keys](https://openquantum.com/keys):
|
|
44
|
+
Download from [Open Quantum SDK Keys](https://www.openquantum.com/keys):
|
|
28
45
|
|
|
29
46
|
```json
|
|
30
47
|
{
|
|
@@ -115,6 +132,11 @@ try:
|
|
|
115
132
|
else:
|
|
116
133
|
print("No output available.")
|
|
117
134
|
|
|
135
|
+
if job.calibration_data_url:
|
|
136
|
+
cal_json = scheduler.download_job_calibration(job)
|
|
137
|
+
print("\n--- CALIBRATION (JSON) ---")
|
|
138
|
+
print(json.dumps(cal_json, indent=2))
|
|
139
|
+
|
|
118
140
|
except Exception as e:
|
|
119
141
|
print(f"\n--- JOB FAILED ---")
|
|
120
142
|
print(f"Error: {e}")
|
|
@@ -258,6 +280,8 @@ finally:
|
|
|
258
280
|
| `queue_priority` | `QueuePriorityType \| "auto"` | `"auto"` | |
|
|
259
281
|
| `auto_approve_quote` | `bool` | `True` | Set `False` in notebooks |
|
|
260
282
|
| `job_timeout_seconds` | `int` | `86400` | 1 day |
|
|
283
|
+
| `input_format` | `str` | `"qasm"` | OpenQASM by default; other values only if listed in backend `supported_formats` |
|
|
284
|
+
| `submitted_with` | `str` | `"sdk"` | Client surface for analytics |
|
|
261
285
|
|
|
262
286
|
## API Reference
|
|
263
287
|
|
|
@@ -276,6 +300,7 @@ finally:
|
|
|
276
300
|
| `cancel_job(job_id)` | `None` | Cancel running job |
|
|
277
301
|
| `list_jobs(organization_id, limit=20, cursor=..., status=...)` | `PaginatedJobs` | List your jobs |
|
|
278
302
|
| **`download_job_output(job)`** | `Any` | **Downloads + parses JSON output** |
|
|
303
|
+
| **`download_job_calibration(job)`** | `Any` | **Downloads + parses calibration JSON** (when tagged) |
|
|
279
304
|
| `submit_job(config, file_path=... or file_content=...)` | `JobRead` | **One-call submission + polling** |
|
|
280
305
|
|
|
281
306
|
### `ManagementClient`
|
|
@@ -293,15 +318,15 @@ finally:
|
|
|
293
318
|
| Model | Key Fields |
|
|
294
319
|
|-------|------------|
|
|
295
320
|
| `BackendClassRead` | `id`, `name`, `description`, `type`, `provider_id`, `short_code`, `queue_depth`, `accepting_jobs`, `status` |
|
|
296
|
-
| `ErrorResponse` | `status_code`, `message: List[str]`, `error_code` |
|
|
321
|
+
| `ErrorResponse` | `status_code`, `message: List[str]`, `type`, `error_code` (request id) |
|
|
297
322
|
| `JobCategoryRead` | `id`, `name`, `short_code` |
|
|
298
323
|
| `JobCreate` | `organization_id`, `job_preparation_id`, `execution_plan_id`, `queue_priority_id` |
|
|
299
|
-
| `JobList` | `id`, `backend_class_id`, `name`, `status`, `backend_name`, `output_data_url`, `credits_used`, `submitted_at` |
|
|
300
|
-
| `JobPreparationCreate` | `organization_id`, `backend_class_id`, `name`, `upload_endpoint_id`, `job_subcategory_id`, `shots`, `configuration_data` |
|
|
324
|
+
| `JobList` | `id`, `backend_class_id`, `name`, `status`, `backend_name`, `output_data_url`, `calibration_data_url`, `credits_used`, `submitted_at` |
|
|
325
|
+
| `JobPreparationCreate` | `organization_id`, `backend_class_id`, `name`, `upload_endpoint_id`, `job_subcategory_id`, `shots`, `configuration_data`, `input_format`, `submitted_with` |
|
|
301
326
|
| `JobPreparationResponse` | `id` |
|
|
302
327
|
| `JobPreparationResultResponse` | `organization_id`, `backend_class_id`, `status`, `name`, `input_data_url`, `job_category_id`, `job_subcategory_id`, `shots`, `quote: List[QuotePlan]`, `message`, `configuration_data` |
|
|
303
328
|
| `JobPreparationUploadResponse` | `id`, `url` |
|
|
304
|
-
| `JobRead` | `id`, `status`, `input_data_url`, `job_preparation_id`, `execution_plan_id`, `queue_priority_id`, `message`, `output_data_url`, `transaction_id`, `submitted_at` |
|
|
329
|
+
| `JobRead` | `id`, `status`, `input_data_url`, `job_preparation_id`, `execution_plan_id`, `queue_priority_id`, `message`, `output_data_url`, `calibration_data_url`, `transaction_id`, `submitted_at` |
|
|
305
330
|
| `OrganizationRead` | `id`, `name` |
|
|
306
331
|
| `PaginatedBackendClasses` | `backend_classes: List[BackendClassRead]`, `pagination: PaginationInfo` |
|
|
307
332
|
| `PaginatedJobCategories` | `categories: List[JobCategoryRead]`, `pagination: PaginationInfo` |
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "openquantum-sdk"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Python SDK for Open Quantum"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -19,8 +19,8 @@ classifiers = [
|
|
|
19
19
|
]
|
|
20
20
|
|
|
21
21
|
[project.optional-dependencies]
|
|
22
|
-
qiskit = ["openquantum-sdk-qiskit>=0.
|
|
23
|
-
pennylane = ["openquantum-sdk-pennylane>=0.
|
|
22
|
+
qiskit = ["openquantum-sdk-qiskit>=0.3.0"]
|
|
23
|
+
pennylane = ["openquantum-sdk-pennylane>=0.2.0"]
|
|
24
24
|
|
|
25
25
|
[project.scripts]
|
|
26
26
|
openquantum-sdk = "openquantum_sdk.__main__:main"
|
|
@@ -189,6 +189,23 @@ class TestBackendFeatures:
|
|
|
189
189
|
assert caps.allows_barriers is True
|
|
190
190
|
assert caps.allows_reset is False
|
|
191
191
|
assert caps.allows_dynamic_circuits is False
|
|
192
|
+
# Missing supported_formats defaults to qasm
|
|
193
|
+
assert caps.supported_formats == ["qasm"]
|
|
194
|
+
assert caps.supports_format("qasm") is True
|
|
195
|
+
assert caps.supports_format("qblox_sequence") is False
|
|
196
|
+
|
|
197
|
+
def test_capabilities_from_constraint_data_parses_supported_formats(self):
|
|
198
|
+
cd = {
|
|
199
|
+
"n_qubits": 8,
|
|
200
|
+
"native_ops": [],
|
|
201
|
+
"topology": {"directed_edges": False, "coupling_map": []},
|
|
202
|
+
"supported_formats": ["qblox_sequence"],
|
|
203
|
+
}
|
|
204
|
+
caps = capabilities_from_constraint_data("test:qblox", cd)
|
|
205
|
+
assert caps.supported_formats == ["qblox_sequence"]
|
|
206
|
+
assert caps.supports_format("qblox_sequence") is True
|
|
207
|
+
assert caps.supports_format("qasm") is False
|
|
208
|
+
assert caps.to_dict()["supported_formats"] == ["qblox_sequence"]
|
|
192
209
|
|
|
193
210
|
def test_features_helpers_on_live_style_data(self):
|
|
194
211
|
cd = {
|
|
@@ -13,10 +13,13 @@ from openquantum_sdk.clients import (
|
|
|
13
13
|
)
|
|
14
14
|
from openquantum_sdk.enums import ExecutionPlanType, QueuePriorityType
|
|
15
15
|
from openquantum_sdk.models import (
|
|
16
|
+
JobCreate,
|
|
16
17
|
QuotePlan,
|
|
17
18
|
QueuePriority,
|
|
18
19
|
JobPreparationCreate,
|
|
19
20
|
OrganizationRead,
|
|
21
|
+
TERMS_OF_USE_REQUIRED_MESSAGE,
|
|
22
|
+
TermsOfUseRequiredError,
|
|
20
23
|
)
|
|
21
24
|
|
|
22
25
|
|
|
@@ -54,6 +57,7 @@ def mock_scheduler():
|
|
|
54
57
|
"queue_priority_id": "prio-1",
|
|
55
58
|
"input_data_url": "https://input.example.com/job-123",
|
|
56
59
|
"output_data_url": "https://output.example.com/result-123",
|
|
60
|
+
"calibration_data_url": "https://calibration.example.com/cal-123",
|
|
57
61
|
"transaction_id": "tx-789",
|
|
58
62
|
"submitted_at": "2025-04-05T12:00:00Z",
|
|
59
63
|
},
|
|
@@ -425,6 +429,26 @@ def test_download_job_output_non_json(mock_scheduler):
|
|
|
425
429
|
mock_scheduler.download_job_output(job)
|
|
426
430
|
|
|
427
431
|
|
|
432
|
+
def test_download_job_calibration_success(mock_scheduler):
|
|
433
|
+
m = mock_scheduler.session.mocker
|
|
434
|
+
m.get(
|
|
435
|
+
"https://calibration.example.com/cal-123",
|
|
436
|
+
json={"classifiers": {}, "components": {}},
|
|
437
|
+
)
|
|
438
|
+
job = mock_scheduler.get_job("job-123")
|
|
439
|
+
# Inject calibration URL as API would after completion
|
|
440
|
+
job.calibration_data_url = "https://calibration.example.com/cal-123"
|
|
441
|
+
cal = mock_scheduler.download_job_calibration(job)
|
|
442
|
+
assert "classifiers" in cal
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def test_download_job_calibration_no_url(mock_scheduler):
|
|
446
|
+
job = mock_scheduler.get_job("job-123")
|
|
447
|
+
job.calibration_data_url = None
|
|
448
|
+
with pytest.raises(RuntimeError, match=r"no calibration_data_url"):
|
|
449
|
+
mock_scheduler.download_job_calibration(job)
|
|
450
|
+
|
|
451
|
+
|
|
428
452
|
def test_download_job_output_http_error(mock_scheduler):
|
|
429
453
|
job = mock_scheduler.get_job("job-123")
|
|
430
454
|
mock_scheduler.session.mocker.get(
|
|
@@ -436,6 +460,85 @@ def test_download_job_output_http_error(mock_scheduler):
|
|
|
436
460
|
mock_scheduler.download_job_output(job)
|
|
437
461
|
|
|
438
462
|
|
|
463
|
+
def test_prepare_job_terms_of_use_required(mock_scheduler):
|
|
464
|
+
mock_scheduler.session.mocker.post(
|
|
465
|
+
"https://scheduler.openquantum.com/v1/jobs/prepare",
|
|
466
|
+
status_code=403,
|
|
467
|
+
json={
|
|
468
|
+
"status_code": 403,
|
|
469
|
+
"message": [TERMS_OF_USE_REQUIRED_MESSAGE],
|
|
470
|
+
"type": "TERMS_OF_USE_REQUIRED",
|
|
471
|
+
"error_code": "req-terms",
|
|
472
|
+
},
|
|
473
|
+
)
|
|
474
|
+
prep = JobPreparationCreate(
|
|
475
|
+
organization_id="org-123",
|
|
476
|
+
backend_class_id="backend-1",
|
|
477
|
+
name="Test",
|
|
478
|
+
upload_endpoint_id="upload-1",
|
|
479
|
+
job_subcategory_id="sub-1",
|
|
480
|
+
shots=10,
|
|
481
|
+
configuration_data={},
|
|
482
|
+
)
|
|
483
|
+
with pytest.raises(TermsOfUseRequiredError, match="openquantum.com") as exc_info:
|
|
484
|
+
mock_scheduler.prepare_job(prep)
|
|
485
|
+
assert str(exc_info.value) == TERMS_OF_USE_REQUIRED_MESSAGE
|
|
486
|
+
assert exc_info.value.error_response.status_code == 403
|
|
487
|
+
assert exc_info.value.error_response.type == "TERMS_OF_USE_REQUIRED"
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def test_create_job_terms_of_use_required(mock_scheduler):
|
|
491
|
+
mock_scheduler.session.mocker.post(
|
|
492
|
+
"https://scheduler.openquantum.com/v1/jobs",
|
|
493
|
+
status_code=403,
|
|
494
|
+
json={
|
|
495
|
+
"status_code": 403,
|
|
496
|
+
"message": TERMS_OF_USE_REQUIRED_MESSAGE,
|
|
497
|
+
"type": "TERMS_OF_USE_REQUIRED",
|
|
498
|
+
"error_code": "req-terms",
|
|
499
|
+
},
|
|
500
|
+
)
|
|
501
|
+
job = JobCreate(
|
|
502
|
+
organization_id="org-123",
|
|
503
|
+
job_preparation_id="prep-1",
|
|
504
|
+
execution_plan_id="plan-1",
|
|
505
|
+
queue_priority_id="prio-1",
|
|
506
|
+
)
|
|
507
|
+
with pytest.raises(TermsOfUseRequiredError, match="Terms of Use"):
|
|
508
|
+
mock_scheduler.create_job(job)
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def test_prepare_job_org_membership_required(mock_scheduler):
|
|
512
|
+
from openquantum_sdk.models import APIError, ApiErrorType
|
|
513
|
+
|
|
514
|
+
mock_scheduler.session.mocker.post(
|
|
515
|
+
"https://scheduler.openquantum.com/v1/jobs/prepare",
|
|
516
|
+
status_code=403,
|
|
517
|
+
json={
|
|
518
|
+
"status_code": 403,
|
|
519
|
+
"message": ["User is not a member of the organization"],
|
|
520
|
+
"type": ApiErrorType.ORG_MEMBERSHIP_REQUIRED,
|
|
521
|
+
"error_code": "req-org",
|
|
522
|
+
},
|
|
523
|
+
)
|
|
524
|
+
prep = JobPreparationCreate(
|
|
525
|
+
organization_id="org-123",
|
|
526
|
+
backend_class_id="backend-1",
|
|
527
|
+
name="Test",
|
|
528
|
+
upload_endpoint_id="upload-1",
|
|
529
|
+
job_subcategory_id="sub-1",
|
|
530
|
+
shots=10,
|
|
531
|
+
configuration_data={},
|
|
532
|
+
)
|
|
533
|
+
with pytest.raises(APIError) as exc_info:
|
|
534
|
+
mock_scheduler.prepare_job(prep)
|
|
535
|
+
assert not isinstance(exc_info.value, TermsOfUseRequiredError)
|
|
536
|
+
assert (
|
|
537
|
+
exc_info.value.error_response.type
|
|
538
|
+
== ApiErrorType.ORG_MEMBERSHIP_REQUIRED
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
|
|
439
542
|
# ---------------------------------------------------------------------------
|
|
440
543
|
# Categories & Listing
|
|
441
544
|
# ---------------------------------------------------------------------------
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openquantum_sdk-0.3.7 → openquantum_sdk-0.4.0}/openquantum_sdk.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|