openquantum-sdk 0.3.4__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.4/openquantum_sdk.egg-info → openquantum_sdk-0.4.0}/PKG-INFO +28 -13
- openquantum_sdk-0.3.4/PKG-INFO → openquantum_sdk-0.4.0/README.md +23 -23
- openquantum_sdk-0.4.0/openquantum_sdk/__init__.py +21 -0
- {openquantum_sdk-0.3.4 → openquantum_sdk-0.4.0}/openquantum_sdk/__main__.py +20 -3
- {openquantum_sdk-0.3.4 → openquantum_sdk-0.4.0}/openquantum_sdk/auth.py +58 -5
- openquantum_sdk-0.4.0/openquantum_sdk/backends.py +706 -0
- {openquantum_sdk-0.3.4 → openquantum_sdk-0.4.0}/openquantum_sdk/clients.py +379 -29
- openquantum_sdk-0.4.0/openquantum_sdk/enums.py +40 -0
- openquantum_sdk-0.4.0/openquantum_sdk/models.py +560 -0
- {openquantum_sdk-0.3.4 → openquantum_sdk-0.4.0}/openquantum_sdk/qiskit/__init__.py +3 -7
- openquantum_sdk-0.4.0/openquantum_sdk/utils.py +71 -0
- openquantum_sdk-0.3.4/README.md → openquantum_sdk-0.4.0/openquantum_sdk.egg-info/PKG-INFO +38 -12
- {openquantum_sdk-0.3.4 → openquantum_sdk-0.4.0}/openquantum_sdk.egg-info/SOURCES.txt +3 -1
- openquantum_sdk-0.4.0/openquantum_sdk.egg-info/requires.txt +7 -0
- {openquantum_sdk-0.3.4 → openquantum_sdk-0.4.0}/pyproject.toml +5 -1
- openquantum_sdk-0.4.0/tests/test_backends.py +257 -0
- {openquantum_sdk-0.3.4 → openquantum_sdk-0.4.0}/tests/test_clients.py +105 -2
- openquantum_sdk-0.4.0/tests/test_clients_plan_priority.py +162 -0
- openquantum_sdk-0.3.4/openquantum_sdk/__init__.py +0 -5
- openquantum_sdk-0.3.4/openquantum_sdk/enums.py +0 -16
- openquantum_sdk-0.3.4/openquantum_sdk/models.py +0 -230
- openquantum_sdk-0.3.4/openquantum_sdk/utils.py +0 -24
- openquantum_sdk-0.3.4/openquantum_sdk.egg-info/requires.txt +0 -1
- openquantum_sdk-0.3.4/tests/conftest.py +0 -40
- {openquantum_sdk-0.3.4 → openquantum_sdk-0.4.0}/MANIFEST.in +0 -0
- {openquantum_sdk-0.3.4 → openquantum_sdk-0.4.0}/openquantum_sdk.egg-info/dependency_links.txt +0 -0
- {openquantum_sdk-0.3.4 → openquantum_sdk-0.4.0}/openquantum_sdk.egg-info/entry_points.txt +0 -0
- {openquantum_sdk-0.3.4 → openquantum_sdk-0.4.0}/openquantum_sdk.egg-info/top_level.txt +0 -0
- {openquantum_sdk-0.3.4 → openquantum_sdk-0.4.0}/requirements-dev.txt +0 -0
- {openquantum_sdk-0.3.4 → openquantum_sdk-0.4.0}/requirements.txt +0 -0
- {openquantum_sdk-0.3.4 → openquantum_sdk-0.4.0}/setup.cfg +0 -0
- {openquantum_sdk-0.3.4 → openquantum_sdk-0.4.0}/tests/__init__.py +0 -0
- {openquantum_sdk-0.3.4 → 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
|
|
@@ -8,6 +8,10 @@ Classifier: Operating System :: OS Independent
|
|
|
8
8
|
Requires-Python: >=3.8
|
|
9
9
|
Description-Content-Type: text/markdown
|
|
10
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"
|
|
11
15
|
|
|
12
16
|
# Open Quantum Python SDK
|
|
13
17
|
|
|
@@ -17,9 +21,11 @@ A lightweight, modern Python SDK for the **Open Quantum Platform**.
|
|
|
17
21
|
|
|
18
22
|
* **Management API** — list organizations, backend classes, and providers
|
|
19
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
|
|
20
25
|
* **Smart defaults** — auto-select execution plan & queue priority from quote
|
|
21
26
|
* **Thread-safe auth** — Keycloak client-credentials with auto-refresh
|
|
22
27
|
* **Automatic output download** — `download_job_output()` returns parsed JSON
|
|
28
|
+
* **Calibration download** — `download_job_calibration()` when the job has `calibration_data_url`
|
|
23
29
|
|
|
24
30
|
## Installation
|
|
25
31
|
|
|
@@ -35,7 +41,7 @@ Python 3.9+ required.
|
|
|
35
41
|
|
|
36
42
|
### Option 1: SDK Key File (Recommended)
|
|
37
43
|
|
|
38
|
-
Download from [Open Quantum SDK Keys](https://openquantum.com/keys):
|
|
44
|
+
Download from [Open Quantum SDK Keys](https://www.openquantum.com/keys):
|
|
39
45
|
|
|
40
46
|
```json
|
|
41
47
|
{
|
|
@@ -100,7 +106,7 @@ try:
|
|
|
100
106
|
# 3. Define config
|
|
101
107
|
config = JobSubmissionConfig(
|
|
102
108
|
organization_id=org_id,
|
|
103
|
-
backend_class_id="
|
|
109
|
+
backend_class_id="iqm:emerald",
|
|
104
110
|
name="Bell State SDK Quickstart",
|
|
105
111
|
job_subcategory_id="fin:port", # Finance Portfolio Optimization
|
|
106
112
|
shots=100,
|
|
@@ -126,6 +132,11 @@ try:
|
|
|
126
132
|
else:
|
|
127
133
|
print("No output available.")
|
|
128
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
|
+
|
|
129
140
|
except Exception as e:
|
|
130
141
|
print(f"\n--- JOB FAILED ---")
|
|
131
142
|
print(f"Error: {e}")
|
|
@@ -162,7 +173,7 @@ try:
|
|
|
162
173
|
print(f" - {bc.name} [{bc.type}] — {bc.short_code or bc.id}")
|
|
163
174
|
|
|
164
175
|
# 2. Choose IDs
|
|
165
|
-
MY_BACKEND = "
|
|
176
|
+
MY_BACKEND = "iqm:emerald"
|
|
166
177
|
MY_SUBCATEGORY = "fin:port"
|
|
167
178
|
|
|
168
179
|
# 3. Upload
|
|
@@ -269,6 +280,8 @@ finally:
|
|
|
269
280
|
| `queue_priority` | `QueuePriorityType \| "auto"` | `"auto"` | |
|
|
270
281
|
| `auto_approve_quote` | `bool` | `True` | Set `False` in notebooks |
|
|
271
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 |
|
|
272
285
|
|
|
273
286
|
## API Reference
|
|
274
287
|
|
|
@@ -287,6 +300,7 @@ finally:
|
|
|
287
300
|
| `cancel_job(job_id)` | `None` | Cancel running job |
|
|
288
301
|
| `list_jobs(organization_id, limit=20, cursor=..., status=...)` | `PaginatedJobs` | List your jobs |
|
|
289
302
|
| **`download_job_output(job)`** | `Any` | **Downloads + parses JSON output** |
|
|
303
|
+
| **`download_job_calibration(job)`** | `Any` | **Downloads + parses calibration JSON** (when tagged) |
|
|
290
304
|
| `submit_job(config, file_path=... or file_content=...)` | `JobRead` | **One-call submission + polling** |
|
|
291
305
|
|
|
292
306
|
### `ManagementClient`
|
|
@@ -304,15 +318,15 @@ finally:
|
|
|
304
318
|
| Model | Key Fields |
|
|
305
319
|
|-------|------------|
|
|
306
320
|
| `BackendClassRead` | `id`, `name`, `description`, `type`, `provider_id`, `short_code`, `queue_depth`, `accepting_jobs`, `status` |
|
|
307
|
-
| `ErrorResponse` | `status_code`, `message: List[str]`, `error_code` |
|
|
321
|
+
| `ErrorResponse` | `status_code`, `message: List[str]`, `type`, `error_code` (request id) |
|
|
308
322
|
| `JobCategoryRead` | `id`, `name`, `short_code` |
|
|
309
323
|
| `JobCreate` | `organization_id`, `job_preparation_id`, `execution_plan_id`, `queue_priority_id` |
|
|
310
|
-
| `JobList` | `id`, `backend_class_id`, `name`, `status`, `backend_name`, `output_data_url`, `credits_used`, `submitted_at` |
|
|
311
|
-
| `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` |
|
|
312
326
|
| `JobPreparationResponse` | `id` |
|
|
313
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` |
|
|
314
328
|
| `JobPreparationUploadResponse` | `id`, `url` |
|
|
315
|
-
| `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` |
|
|
316
330
|
| `OrganizationRead` | `id`, `name` |
|
|
317
331
|
| `PaginatedBackendClasses` | `backend_classes: List[BackendClassRead]`, `pagination: PaginationInfo` |
|
|
318
332
|
| `PaginatedJobCategories` | `categories: List[JobCategoryRead]`, `pagination: PaginationInfo` |
|
|
@@ -353,7 +367,7 @@ from openquantum_sdk.enums import ExecutionPlanType, QueuePriorityType
|
|
|
353
367
|
```bash
|
|
354
368
|
python -m openquantum_sdk --sdk-key ./key.json \
|
|
355
369
|
--input bell.qasm \
|
|
356
|
-
--backend "
|
|
370
|
+
--backend "iqm:emerald" \
|
|
357
371
|
--name "CLI Circuit" \
|
|
358
372
|
--subcategory "mathematics:linear-systems" \
|
|
359
373
|
--shots 100 \
|
|
@@ -367,11 +381,12 @@ Automatically **downloads and pretty-prints JSON output**.
|
|
|
367
381
|
### Backends
|
|
368
382
|
| Provider | Backend | Short Code |
|
|
369
383
|
| ------ | ------ | ------ |
|
|
370
|
-
|
|
|
384
|
+
| AQT | Ibex-Q1 | `aqt:ibex-q1` |
|
|
385
|
+
| IonQ | Forte Enterprise | `ionq:forte-ent` |
|
|
371
386
|
| IonQ | Forte-1 | `ionq:forte-1` |
|
|
372
387
|
| IQM | Emerald | `iqm:emerald` |
|
|
373
388
|
| IQM | Garnet | `iqm:garnet` |
|
|
374
|
-
| Rigetti |
|
|
389
|
+
| Rigetti | Cepheus-1-108Q | `rigetti:cepheus-108q`|
|
|
375
390
|
|
|
376
391
|
*Note: This list may be modified as backends are added or removed from the platform. For the most up-to-date list, run the following code:*
|
|
377
392
|
|
|
@@ -439,8 +454,8 @@ for cat in cats:
|
|
|
439
454
|
**Q: How do I get results as JSON?**
|
|
440
455
|
**A:** Use `scheduler.download_job_output(job)` — returns parsed object.
|
|
441
456
|
|
|
442
|
-
**Q: Why does auto mode fail on some QPUs?**
|
|
443
|
-
**A:** `auto`
|
|
457
|
+
**Q: Why does auto mode fail on some QPUs?**
|
|
458
|
+
**A:** `"auto"` selects the cheapest plan and priority (`PUBLIC` + `STANDARD`). If a backend only offers private execution, pass `execution_plan="private"` explicitly. Similarly, use `queue_priority="priority"` or `"instant"` for higher-priority queue access.
|
|
444
459
|
|
|
445
460
|
**Q: Can I hide the public plan citation notice?**
|
|
446
461
|
**A:** Setting `OPENQUANTUM_HIDE_CITATION_NOTICE=true` will omit the citation notice from output.
|
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: openquantum-sdk
|
|
3
|
-
Version: 0.3.4
|
|
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
|
-
|
|
12
1
|
# Open Quantum Python SDK
|
|
13
2
|
|
|
14
3
|
A lightweight, modern Python SDK for the **Open Quantum Platform**.
|
|
@@ -17,9 +6,11 @@ A lightweight, modern Python SDK for the **Open Quantum Platform**.
|
|
|
17
6
|
|
|
18
7
|
* **Management API** — list organizations, backend classes, and providers
|
|
19
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
|
|
20
10
|
* **Smart defaults** — auto-select execution plan & queue priority from quote
|
|
21
11
|
* **Thread-safe auth** — Keycloak client-credentials with auto-refresh
|
|
22
12
|
* **Automatic output download** — `download_job_output()` returns parsed JSON
|
|
13
|
+
* **Calibration download** — `download_job_calibration()` when the job has `calibration_data_url`
|
|
23
14
|
|
|
24
15
|
## Installation
|
|
25
16
|
|
|
@@ -35,7 +26,7 @@ Python 3.9+ required.
|
|
|
35
26
|
|
|
36
27
|
### Option 1: SDK Key File (Recommended)
|
|
37
28
|
|
|
38
|
-
Download from [Open Quantum SDK Keys](https://openquantum.com/keys):
|
|
29
|
+
Download from [Open Quantum SDK Keys](https://www.openquantum.com/keys):
|
|
39
30
|
|
|
40
31
|
```json
|
|
41
32
|
{
|
|
@@ -100,7 +91,7 @@ try:
|
|
|
100
91
|
# 3. Define config
|
|
101
92
|
config = JobSubmissionConfig(
|
|
102
93
|
organization_id=org_id,
|
|
103
|
-
backend_class_id="
|
|
94
|
+
backend_class_id="iqm:emerald",
|
|
104
95
|
name="Bell State SDK Quickstart",
|
|
105
96
|
job_subcategory_id="fin:port", # Finance Portfolio Optimization
|
|
106
97
|
shots=100,
|
|
@@ -126,6 +117,11 @@ try:
|
|
|
126
117
|
else:
|
|
127
118
|
print("No output available.")
|
|
128
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
|
+
|
|
129
125
|
except Exception as e:
|
|
130
126
|
print(f"\n--- JOB FAILED ---")
|
|
131
127
|
print(f"Error: {e}")
|
|
@@ -162,7 +158,7 @@ try:
|
|
|
162
158
|
print(f" - {bc.name} [{bc.type}] — {bc.short_code or bc.id}")
|
|
163
159
|
|
|
164
160
|
# 2. Choose IDs
|
|
165
|
-
MY_BACKEND = "
|
|
161
|
+
MY_BACKEND = "iqm:emerald"
|
|
166
162
|
MY_SUBCATEGORY = "fin:port"
|
|
167
163
|
|
|
168
164
|
# 3. Upload
|
|
@@ -269,6 +265,8 @@ finally:
|
|
|
269
265
|
| `queue_priority` | `QueuePriorityType \| "auto"` | `"auto"` | |
|
|
270
266
|
| `auto_approve_quote` | `bool` | `True` | Set `False` in notebooks |
|
|
271
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 |
|
|
272
270
|
|
|
273
271
|
## API Reference
|
|
274
272
|
|
|
@@ -287,6 +285,7 @@ finally:
|
|
|
287
285
|
| `cancel_job(job_id)` | `None` | Cancel running job |
|
|
288
286
|
| `list_jobs(organization_id, limit=20, cursor=..., status=...)` | `PaginatedJobs` | List your jobs |
|
|
289
287
|
| **`download_job_output(job)`** | `Any` | **Downloads + parses JSON output** |
|
|
288
|
+
| **`download_job_calibration(job)`** | `Any` | **Downloads + parses calibration JSON** (when tagged) |
|
|
290
289
|
| `submit_job(config, file_path=... or file_content=...)` | `JobRead` | **One-call submission + polling** |
|
|
291
290
|
|
|
292
291
|
### `ManagementClient`
|
|
@@ -304,15 +303,15 @@ finally:
|
|
|
304
303
|
| Model | Key Fields |
|
|
305
304
|
|-------|------------|
|
|
306
305
|
| `BackendClassRead` | `id`, `name`, `description`, `type`, `provider_id`, `short_code`, `queue_depth`, `accepting_jobs`, `status` |
|
|
307
|
-
| `ErrorResponse` | `status_code`, `message: List[str]`, `error_code` |
|
|
306
|
+
| `ErrorResponse` | `status_code`, `message: List[str]`, `type`, `error_code` (request id) |
|
|
308
307
|
| `JobCategoryRead` | `id`, `name`, `short_code` |
|
|
309
308
|
| `JobCreate` | `organization_id`, `job_preparation_id`, `execution_plan_id`, `queue_priority_id` |
|
|
310
|
-
| `JobList` | `id`, `backend_class_id`, `name`, `status`, `backend_name`, `output_data_url`, `credits_used`, `submitted_at` |
|
|
311
|
-
| `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` |
|
|
312
311
|
| `JobPreparationResponse` | `id` |
|
|
313
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` |
|
|
314
313
|
| `JobPreparationUploadResponse` | `id`, `url` |
|
|
315
|
-
| `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` |
|
|
316
315
|
| `OrganizationRead` | `id`, `name` |
|
|
317
316
|
| `PaginatedBackendClasses` | `backend_classes: List[BackendClassRead]`, `pagination: PaginationInfo` |
|
|
318
317
|
| `PaginatedJobCategories` | `categories: List[JobCategoryRead]`, `pagination: PaginationInfo` |
|
|
@@ -353,7 +352,7 @@ from openquantum_sdk.enums import ExecutionPlanType, QueuePriorityType
|
|
|
353
352
|
```bash
|
|
354
353
|
python -m openquantum_sdk --sdk-key ./key.json \
|
|
355
354
|
--input bell.qasm \
|
|
356
|
-
--backend "
|
|
355
|
+
--backend "iqm:emerald" \
|
|
357
356
|
--name "CLI Circuit" \
|
|
358
357
|
--subcategory "mathematics:linear-systems" \
|
|
359
358
|
--shots 100 \
|
|
@@ -367,11 +366,12 @@ Automatically **downloads and pretty-prints JSON output**.
|
|
|
367
366
|
### Backends
|
|
368
367
|
| Provider | Backend | Short Code |
|
|
369
368
|
| ------ | ------ | ------ |
|
|
370
|
-
|
|
|
369
|
+
| AQT | Ibex-Q1 | `aqt:ibex-q1` |
|
|
370
|
+
| IonQ | Forte Enterprise | `ionq:forte-ent` |
|
|
371
371
|
| IonQ | Forte-1 | `ionq:forte-1` |
|
|
372
372
|
| IQM | Emerald | `iqm:emerald` |
|
|
373
373
|
| IQM | Garnet | `iqm:garnet` |
|
|
374
|
-
| Rigetti |
|
|
374
|
+
| Rigetti | Cepheus-1-108Q | `rigetti:cepheus-108q`|
|
|
375
375
|
|
|
376
376
|
*Note: This list may be modified as backends are added or removed from the platform. For the most up-to-date list, run the following code:*
|
|
377
377
|
|
|
@@ -439,8 +439,8 @@ for cat in cats:
|
|
|
439
439
|
**Q: How do I get results as JSON?**
|
|
440
440
|
**A:** Use `scheduler.download_job_output(job)` — returns parsed object.
|
|
441
441
|
|
|
442
|
-
**Q: Why does auto mode fail on some QPUs?**
|
|
443
|
-
**A:** `auto`
|
|
442
|
+
**Q: Why does auto mode fail on some QPUs?**
|
|
443
|
+
**A:** `"auto"` selects the cheapest plan and priority (`PUBLIC` + `STANDARD`). If a backend only offers private execution, pass `execution_plan="private"` explicitly. Similarly, use `queue_priority="priority"` or `"instant"` for higher-priority queue access.
|
|
444
444
|
|
|
445
445
|
**Q: Can I hide the public plan citation notice?**
|
|
446
446
|
**A:** Setting `OPENQUANTUM_HIDE_CITATION_NOTICE=true` will omit the citation notice from output.
|
|
@@ -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:
|
|
@@ -121,8 +122,10 @@ def main():
|
|
|
121
122
|
creds=creds
|
|
122
123
|
)
|
|
123
124
|
|
|
124
|
-
scheduler = SchedulerClient(base_url=args.scheduler_base, auth=auth)
|
|
125
125
|
management = ManagementClient(base_url=args.management_base, auth=auth)
|
|
126
|
+
scheduler = SchedulerClient(
|
|
127
|
+
base_url=args.scheduler_base, auth=auth, management_client=management
|
|
128
|
+
)
|
|
126
129
|
|
|
127
130
|
try:
|
|
128
131
|
print("--- Open Quantum Job Submission CLI ---")
|
|
@@ -161,12 +164,13 @@ def main():
|
|
|
161
164
|
print(f"Final Status: {job.status}")
|
|
162
165
|
print(f"Submitted At: {job.submitted_at}")
|
|
163
166
|
print(f"Output URL: {job.output_data_url}")
|
|
167
|
+
print(f"Calibration URL: {job.calibration_data_url}")
|
|
164
168
|
print("---------------------")
|
|
165
169
|
|
|
166
170
|
if job.output_data_url:
|
|
167
171
|
print("\nFetching job output...")
|
|
168
172
|
try:
|
|
169
|
-
output_json = scheduler.download_job_output(job
|
|
173
|
+
output_json = scheduler.download_job_output(job)
|
|
170
174
|
print("\n--- JOB OUTPUT (JSON) ---")
|
|
171
175
|
print(json.dumps(output_json, indent=2))
|
|
172
176
|
print("-------------------------")
|
|
@@ -175,6 +179,19 @@ def main():
|
|
|
175
179
|
else:
|
|
176
180
|
print("\nNo output_data_url available (job may have failed or produced no output).")
|
|
177
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)
|
|
178
195
|
except requests.exceptions.HTTPError as e:
|
|
179
196
|
print(f"API error: {e.response.status_code} - {e.response.text}", file=sys.stderr)
|
|
180
197
|
sys.exit(1)
|
|
@@ -9,17 +9,28 @@ from urllib.parse import urljoin
|
|
|
9
9
|
|
|
10
10
|
@dataclass
|
|
11
11
|
class ClientCredentials:
|
|
12
|
+
"""SDK key credentials for machine-to-machine authentication.
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
client_id: The OAuth2 client ID (SDK key ID).
|
|
16
|
+
client_secret: The OAuth2 client secret (SDK key secret).
|
|
17
|
+
"""
|
|
18
|
+
|
|
12
19
|
client_id: str
|
|
13
20
|
client_secret: str
|
|
14
21
|
|
|
15
22
|
|
|
16
23
|
class ClientCredentialsAuth:
|
|
24
|
+
"""Keycloak client-credentials OAuth2 helper with thread-safe token caching.
|
|
25
|
+
|
|
26
|
+
Manages the full token lifecycle: initial fetch, automatic refresh when
|
|
27
|
+
the token is within ``leeway_seconds`` of expiry, and forced refresh on
|
|
28
|
+
401 retries. All token operations are thread-safe.
|
|
29
|
+
|
|
30
|
+
The token endpoint is constructed as:
|
|
31
|
+
``{keycloak_base}/realms/{realm}/protocol/openid-connect/token``
|
|
17
32
|
"""
|
|
18
|
-
|
|
19
|
-
- Fetches token from: {keycloak_base}/realms/{realm}/protocol/openid-connect/token
|
|
20
|
-
- Auto refreshes when within leeway of expiry
|
|
21
|
-
- Retries once on 401
|
|
22
|
-
"""
|
|
33
|
+
|
|
23
34
|
def __init__(
|
|
24
35
|
self,
|
|
25
36
|
creds: ClientCredentials,
|
|
@@ -29,6 +40,19 @@ class ClientCredentialsAuth:
|
|
|
29
40
|
leeway_seconds: int = 30,
|
|
30
41
|
session: Optional[requests.Session] = None,
|
|
31
42
|
):
|
|
43
|
+
"""Initialize the auth helper.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
creds: Client credentials containing the client ID and secret.
|
|
47
|
+
keycloak_base: Base URL of the Keycloak server.
|
|
48
|
+
Defaults to ``"https://id.openquantum.com"``.
|
|
49
|
+
realm: Keycloak realm name. Defaults to ``"platform"``.
|
|
50
|
+
scope: Optional OAuth2 scope to request.
|
|
51
|
+
leeway_seconds: Seconds before token expiry to trigger a
|
|
52
|
+
proactive refresh. Defaults to 30.
|
|
53
|
+
session: Optional ``requests.Session`` to use for token requests.
|
|
54
|
+
A new session is created if not provided.
|
|
55
|
+
"""
|
|
32
56
|
self.keycloak_base = keycloak_base.rstrip("/")
|
|
33
57
|
self.realm = realm
|
|
34
58
|
self.creds = creds
|
|
@@ -41,6 +65,7 @@ class ClientCredentialsAuth:
|
|
|
41
65
|
|
|
42
66
|
@property
|
|
43
67
|
def token_endpoint(self) -> str:
|
|
68
|
+
"""The full Keycloak token endpoint URL for this realm."""
|
|
44
69
|
return urljoin(
|
|
45
70
|
self.keycloak_base + "/",
|
|
46
71
|
f"realms/{self.realm}/protocol/openid-connect/token",
|
|
@@ -72,6 +97,22 @@ class ClientCredentialsAuth:
|
|
|
72
97
|
self._expires_at = time.time() + int(expires_in)
|
|
73
98
|
|
|
74
99
|
def get_access_token(self, force: bool = False) -> str:
|
|
100
|
+
"""Return a valid access token, refreshing if needed.
|
|
101
|
+
|
|
102
|
+
Uses double-checked locking to ensure only one thread fetches a
|
|
103
|
+
new token at a time.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
force: If ``True``, forces a token refresh regardless of
|
|
107
|
+
expiry status. Defaults to ``False``.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
A valid OAuth2 access token string.
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
RuntimeError: If the Keycloak response is missing an access token.
|
|
114
|
+
requests.HTTPError: If the token request fails.
|
|
115
|
+
"""
|
|
75
116
|
if force or self._needs_refresh():
|
|
76
117
|
with self._lock:
|
|
77
118
|
if force or self._needs_refresh():
|
|
@@ -79,6 +120,18 @@ class ClientCredentialsAuth:
|
|
|
79
120
|
return self._access_token # type: ignore[str-bytes-safe]
|
|
80
121
|
|
|
81
122
|
def apply_auth_header(self, headers: Dict[str, str]) -> Dict[str, str]:
|
|
123
|
+
"""Add a Bearer authorization header using the current access token.
|
|
124
|
+
|
|
125
|
+
Does not mutate the input dict; returns a new dict with the
|
|
126
|
+
``Authorization`` header added.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
headers: Existing headers to augment.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
A new headers dict containing all entries from ``headers`` plus
|
|
133
|
+
the ``Authorization: Bearer <token>`` header.
|
|
134
|
+
"""
|
|
82
135
|
token = self.get_access_token()
|
|
83
136
|
# don't mutate caller's header dict
|
|
84
137
|
return {**headers, "Authorization": f"Bearer {token}"}
|