openquantum-sdk 0.4.0__tar.gz → 0.4.2__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.4.0 → openquantum_sdk-0.4.2}/PKG-INFO +13 -7
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/README.md +3 -4
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/openquantum_sdk/__init__.py +5 -1
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/openquantum_sdk/__main__.py +8 -3
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/openquantum_sdk/backends.py +2 -2
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/openquantum_sdk/clients.py +53 -15
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/openquantum_sdk/models.py +43 -13
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/openquantum_sdk.egg-info/PKG-INFO +13 -7
- openquantum_sdk-0.4.2/openquantum_sdk.egg-info/requires.txt +15 -0
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/pyproject.toml +12 -5
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/requirements.txt +0 -1
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/tests/test_clients.py +52 -1
- openquantum_sdk-0.4.0/openquantum_sdk.egg-info/requires.txt +0 -7
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/MANIFEST.in +0 -0
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/openquantum_sdk/auth.py +0 -0
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/openquantum_sdk/enums.py +0 -0
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/openquantum_sdk/qiskit/__init__.py +0 -0
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/openquantum_sdk/utils.py +0 -0
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/openquantum_sdk.egg-info/SOURCES.txt +0 -0
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/openquantum_sdk.egg-info/dependency_links.txt +0 -0
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/openquantum_sdk.egg-info/entry_points.txt +0 -0
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/openquantum_sdk.egg-info/top_level.txt +0 -0
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/requirements-dev.txt +0 -0
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/setup.cfg +0 -0
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/tests/__init__.py +0 -0
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/tests/test_backends.py +0 -0
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/tests/test_clients_plan_priority.py +0 -0
- {openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/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.4.
|
|
3
|
+
Version: 0.4.2
|
|
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,10 +8,17 @@ 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: dev
|
|
12
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
13
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
14
|
+
Requires-Dist: pytest-mock; extra == "dev"
|
|
15
|
+
Requires-Dist: requests-mock>=1.10.0; extra == "dev"
|
|
16
|
+
Requires-Dist: flake8; extra == "dev"
|
|
17
|
+
Requires-Dist: black; extra == "dev"
|
|
11
18
|
Provides-Extra: qiskit
|
|
12
|
-
Requires-Dist: openquantum-sdk-qiskit>=0.3.
|
|
19
|
+
Requires-Dist: openquantum-sdk-qiskit>=0.3.1; extra == "qiskit"
|
|
13
20
|
Provides-Extra: pennylane
|
|
14
|
-
Requires-Dist: openquantum-sdk-pennylane>=0.2.
|
|
21
|
+
Requires-Dist: openquantum-sdk-pennylane>=0.2.1; extra == "pennylane"
|
|
15
22
|
|
|
16
23
|
# Open Quantum Python SDK
|
|
17
24
|
|
|
@@ -35,7 +42,7 @@ pip install -U openquantum-sdk
|
|
|
35
42
|
pip install -e .
|
|
36
43
|
```
|
|
37
44
|
|
|
38
|
-
Python 3.
|
|
45
|
+
Python 3.8+ required.
|
|
39
46
|
|
|
40
47
|
## Authentication
|
|
41
48
|
|
|
@@ -220,9 +227,8 @@ try:
|
|
|
220
227
|
cheapest_plan = min(result.quote, key=lambda p: p.price)
|
|
221
228
|
cheapest_prio = min(cheapest_plan.queue_priorities, key=lambda q: q.price_increase)
|
|
222
229
|
|
|
223
|
-
# 7. Create job
|
|
230
|
+
# 7. Create job (organization comes from the preparation)
|
|
224
231
|
job = JobCreate(
|
|
225
|
-
organization_id=org.id,
|
|
226
232
|
job_preparation_id=prep_resp.id,
|
|
227
233
|
execution_plan_id=cheapest_plan.execution_plan_id,
|
|
228
234
|
queue_priority_id=cheapest_prio.queue_priority_id,
|
|
@@ -320,7 +326,7 @@ finally:
|
|
|
320
326
|
| `BackendClassRead` | `id`, `name`, `description`, `type`, `provider_id`, `short_code`, `queue_depth`, `accepting_jobs`, `status` |
|
|
321
327
|
| `ErrorResponse` | `status_code`, `message: List[str]`, `type`, `error_code` (request id) |
|
|
322
328
|
| `JobCategoryRead` | `id`, `name`, `short_code` |
|
|
323
|
-
| `JobCreate` | `
|
|
329
|
+
| `JobCreate` | `job_preparation_id`, `execution_plan_id`, `queue_priority_id`, optional `organization_id` |
|
|
324
330
|
| `JobList` | `id`, `backend_class_id`, `name`, `status`, `backend_name`, `output_data_url`, `calibration_data_url`, `credits_used`, `submitted_at` |
|
|
325
331
|
| `JobPreparationCreate` | `organization_id`, `backend_class_id`, `name`, `upload_endpoint_id`, `job_subcategory_id`, `shots`, `configuration_data`, `input_format`, `submitted_with` |
|
|
326
332
|
| `JobPreparationResponse` | `id` |
|
|
@@ -20,7 +20,7 @@ pip install -U openquantum-sdk
|
|
|
20
20
|
pip install -e .
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
Python 3.
|
|
23
|
+
Python 3.8+ required.
|
|
24
24
|
|
|
25
25
|
## Authentication
|
|
26
26
|
|
|
@@ -205,9 +205,8 @@ try:
|
|
|
205
205
|
cheapest_plan = min(result.quote, key=lambda p: p.price)
|
|
206
206
|
cheapest_prio = min(cheapest_plan.queue_priorities, key=lambda q: q.price_increase)
|
|
207
207
|
|
|
208
|
-
# 7. Create job
|
|
208
|
+
# 7. Create job (organization comes from the preparation)
|
|
209
209
|
job = JobCreate(
|
|
210
|
-
organization_id=org.id,
|
|
211
210
|
job_preparation_id=prep_resp.id,
|
|
212
211
|
execution_plan_id=cheapest_plan.execution_plan_id,
|
|
213
212
|
queue_priority_id=cheapest_prio.queue_priority_id,
|
|
@@ -305,7 +304,7 @@ finally:
|
|
|
305
304
|
| `BackendClassRead` | `id`, `name`, `description`, `type`, `provider_id`, `short_code`, `queue_depth`, `accepting_jobs`, `status` |
|
|
306
305
|
| `ErrorResponse` | `status_code`, `message: List[str]`, `type`, `error_code` (request id) |
|
|
307
306
|
| `JobCategoryRead` | `id`, `name`, `short_code` |
|
|
308
|
-
| `JobCreate` | `
|
|
307
|
+
| `JobCreate` | `job_preparation_id`, `execution_plan_id`, `queue_priority_id`, optional `organization_id` |
|
|
309
308
|
| `JobList` | `id`, `backend_class_id`, `name`, `status`, `backend_name`, `output_data_url`, `calibration_data_url`, `credits_used`, `submitted_at` |
|
|
310
309
|
| `JobPreparationCreate` | `organization_id`, `backend_class_id`, `name`, `upload_endpoint_id`, `job_subcategory_id`, `shots`, `configuration_data`, `input_format`, `submitted_with` |
|
|
311
310
|
| `JobPreparationResponse` | `id` |
|
|
@@ -4,11 +4,13 @@ from .models import (
|
|
|
4
4
|
ApiErrorType,
|
|
5
5
|
TermsOfUseRequiredError,
|
|
6
6
|
TERMS_OF_USE_REQUIRED_MESSAGE,
|
|
7
|
+
EmailVerificationRequiredError,
|
|
8
|
+
EMAIL_VERIFICATION_REQUIRED_MESSAGE,
|
|
7
9
|
is_not_found_error,
|
|
8
10
|
)
|
|
9
11
|
from .utils import poll_for_status
|
|
10
12
|
|
|
11
|
-
__version__ = "0.4.
|
|
13
|
+
__version__ = "0.4.2"
|
|
12
14
|
__all__ = [
|
|
13
15
|
"SchedulerClient",
|
|
14
16
|
"ManagementClient",
|
|
@@ -17,5 +19,7 @@ __all__ = [
|
|
|
17
19
|
"ApiErrorType",
|
|
18
20
|
"TermsOfUseRequiredError",
|
|
19
21
|
"TERMS_OF_USE_REQUIRED_MESSAGE",
|
|
22
|
+
"EmailVerificationRequiredError",
|
|
23
|
+
"EMAIL_VERIFICATION_REQUIRED_MESSAGE",
|
|
20
24
|
"is_not_found_error",
|
|
21
25
|
]
|
|
@@ -7,7 +7,7 @@ import argparse
|
|
|
7
7
|
import requests
|
|
8
8
|
from .auth import ClientCredentials, ClientCredentialsAuth
|
|
9
9
|
from .clients import SchedulerClient, ManagementClient, JobSubmissionConfig
|
|
10
|
-
from .models import APIError, TermsOfUseRequiredError
|
|
10
|
+
from .models import APIError, TermsOfUseRequiredError, EmailVerificationRequiredError
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def load_creds_from_file(path: str) -> ClientCredentials:
|
|
@@ -72,8 +72,10 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
|
|
72
72
|
p.add_argument(
|
|
73
73
|
"-s", "--shots",
|
|
74
74
|
type=int,
|
|
75
|
-
default=
|
|
76
|
-
help=
|
|
75
|
+
default=None,
|
|
76
|
+
help=(
|
|
77
|
+
"Prefer configuration_data.shots long-term."
|
|
78
|
+
),
|
|
77
79
|
)
|
|
78
80
|
|
|
79
81
|
# --- Auth ---
|
|
@@ -192,6 +194,9 @@ def main():
|
|
|
192
194
|
except TermsOfUseRequiredError as e:
|
|
193
195
|
print(str(e), file=sys.stderr)
|
|
194
196
|
sys.exit(1)
|
|
197
|
+
except EmailVerificationRequiredError as e:
|
|
198
|
+
print(str(e), file=sys.stderr)
|
|
199
|
+
sys.exit(1)
|
|
195
200
|
except requests.exceptions.HTTPError as e:
|
|
196
201
|
print(f"API error: {e.response.status_code} - {e.response.text}", file=sys.stderr)
|
|
197
202
|
sys.exit(1)
|
|
@@ -68,8 +68,8 @@ class BackendCapabilities:
|
|
|
68
68
|
the DeviceFeatures enum. These drive precompilation,
|
|
69
69
|
validation, and SDK-side decisions before jobs reach QPUs.
|
|
70
70
|
supported_formats: Circuit input language families this backend
|
|
71
|
-
accepts (e.g. ``"qasm"
|
|
72
|
-
|
|
71
|
+
accepts (e.g. ``"qasm"``). Defaults to ``["qasm"]``.
|
|
72
|
+
OpenQASM 2 and 3 both map to ``"qasm"``.
|
|
73
73
|
"""
|
|
74
74
|
|
|
75
75
|
short_code: str
|
|
@@ -12,6 +12,7 @@ from .utils import strip_gate_definitions
|
|
|
12
12
|
from .models import (
|
|
13
13
|
APIError,
|
|
14
14
|
TermsOfUseRequiredError,
|
|
15
|
+
EmailVerificationRequiredError,
|
|
15
16
|
error_from_response,
|
|
16
17
|
is_not_found_error,
|
|
17
18
|
BackendClassRead,
|
|
@@ -54,7 +55,10 @@ class JobSubmissionConfig:
|
|
|
54
55
|
backend_class_id: Backend class ID or short_code (e.g., ``"ionq:aria-1"``).
|
|
55
56
|
name: Human-readable name for the job (visible in the portal).
|
|
56
57
|
job_subcategory_id: Job subcategory ID or short_code (e.g., ``"phys:oth"``).
|
|
57
|
-
shots:
|
|
58
|
+
shots: Optional measurement shot count (legacy). Prefer
|
|
59
|
+
``configuration_data["shots"]``. Omit when the backend does not
|
|
60
|
+
use shots. When set, the SDK dual-writes into configuration_data
|
|
61
|
+
if missing.
|
|
58
62
|
organization_id: Organization UUID. Optional if the user belongs to
|
|
59
63
|
exactly one organization — the SDK resolves it automatically.
|
|
60
64
|
configuration_data: Optional backend-specific configuration dictionary.
|
|
@@ -71,10 +75,10 @@ class JobSubmissionConfig:
|
|
|
71
75
|
Defaults to 86 400 (1 day).
|
|
72
76
|
submitted_with: Client/surface that submitted this preparation
|
|
73
77
|
(e.g. ``"sdk"``, ``"qiskit"``, ``"cli"``). Defaults to ``"sdk"``.
|
|
74
|
-
input_format: Circuit input language family (``"qasm"``
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
input_format: Circuit input language family (e.g. ``"qasm"``).
|
|
79
|
+
Defaults to ``"qasm"``. OpenQASM 2/3 both use ``"qasm"``.
|
|
80
|
+
Use the format advertised by the backend's
|
|
81
|
+
``supported_formats`` for non-QASM payloads.
|
|
78
82
|
|
|
79
83
|
Example:
|
|
80
84
|
>>> config = JobSubmissionConfig(
|
|
@@ -89,7 +93,7 @@ class JobSubmissionConfig:
|
|
|
89
93
|
backend_class_id: str
|
|
90
94
|
name: str
|
|
91
95
|
job_subcategory_id: str
|
|
92
|
-
shots: int
|
|
96
|
+
shots: Optional[int] = None
|
|
93
97
|
organization_id: Optional[str] = None
|
|
94
98
|
configuration_data: Optional[Dict[str, Any]] = None
|
|
95
99
|
|
|
@@ -105,7 +109,7 @@ class JobSubmissionConfig:
|
|
|
105
109
|
# Attribution for analytics / provenance
|
|
106
110
|
submitted_with: Optional[str] = "sdk"
|
|
107
111
|
|
|
108
|
-
# Circuit language family (qasm
|
|
112
|
+
# Circuit language family (e.g. "qasm"). None → API default "qasm".
|
|
109
113
|
input_format: Optional[str] = None
|
|
110
114
|
|
|
111
115
|
# INTERNAL: Fixed preparation timeout and polling
|
|
@@ -178,7 +182,7 @@ class BaseClient:
|
|
|
178
182
|
error_code=payload.get("error_code"),
|
|
179
183
|
)
|
|
180
184
|
raise error_from_response(err)
|
|
181
|
-
except (TermsOfUseRequiredError, APIError):
|
|
185
|
+
except (TermsOfUseRequiredError, EmailVerificationRequiredError, APIError):
|
|
182
186
|
raise
|
|
183
187
|
except ValueError:
|
|
184
188
|
pass
|
|
@@ -473,6 +477,25 @@ class SchedulerClient(BaseClient):
|
|
|
473
477
|
"Content may not be JSON or is malformed."
|
|
474
478
|
) from exc
|
|
475
479
|
|
|
480
|
+
@staticmethod
|
|
481
|
+
def _normalize_shots_and_configuration(
|
|
482
|
+
shots: Optional[int],
|
|
483
|
+
configuration_data: Optional[Dict[str, Any]],
|
|
484
|
+
) -> tuple[Optional[int], Dict[str, Any]]:
|
|
485
|
+
"""Dual-write shots into configuration_data when first-class shots is set."""
|
|
486
|
+
config: Dict[str, Any] = dict(configuration_data or {})
|
|
487
|
+
config_shots = config.get("shots")
|
|
488
|
+
if shots is not None and config_shots is not None and shots != config_shots:
|
|
489
|
+
raise ValueError(
|
|
490
|
+
f"shots mismatch: first-class value ({shots}) differs from "
|
|
491
|
+
f"configuration_data.shots ({config_shots})"
|
|
492
|
+
)
|
|
493
|
+
if shots is not None and config_shots is None:
|
|
494
|
+
config["shots"] = shots
|
|
495
|
+
if shots is None and isinstance(config_shots, int):
|
|
496
|
+
shots = config_shots
|
|
497
|
+
return shots, config
|
|
498
|
+
|
|
476
499
|
def prepare_job(self, preparation: JobPreparationCreate) -> JobPreparationResponse:
|
|
477
500
|
"""Submit a job preparation request.
|
|
478
501
|
|
|
@@ -486,11 +509,20 @@ class SchedulerClient(BaseClient):
|
|
|
486
509
|
Returns:
|
|
487
510
|
A ``JobPreparationResponse`` containing the preparation ``id``.
|
|
488
511
|
"""
|
|
512
|
+
shots, configuration_data = self._normalize_shots_and_configuration(
|
|
513
|
+
preparation.shots,
|
|
514
|
+
preparation.configuration_data,
|
|
515
|
+
)
|
|
489
516
|
payload = {
|
|
490
517
|
key: value
|
|
491
518
|
for key, value in asdict(preparation).items()
|
|
492
519
|
if value is not None
|
|
493
520
|
}
|
|
521
|
+
payload["configuration_data"] = configuration_data
|
|
522
|
+
if shots is not None:
|
|
523
|
+
payload["shots"] = shots
|
|
524
|
+
elif "shots" in payload:
|
|
525
|
+
del payload["shots"]
|
|
494
526
|
data = self._request("POST", "/v1/jobs/prepare", json=payload)
|
|
495
527
|
return JobPreparationResponse.from_dict(data)
|
|
496
528
|
|
|
@@ -548,7 +580,7 @@ class SchedulerClient(BaseClient):
|
|
|
548
580
|
input_data_url=data["input_data_url"],
|
|
549
581
|
job_category_id=data["job_category_id"],
|
|
550
582
|
job_subcategory_id=data["job_subcategory_id"],
|
|
551
|
-
shots=data
|
|
583
|
+
shots=data.get("shots"),
|
|
552
584
|
message=data.get("message"),
|
|
553
585
|
quote=quote,
|
|
554
586
|
configuration_data=data.get("configuration_data", {}),
|
|
@@ -559,10 +591,12 @@ class SchedulerClient(BaseClient):
|
|
|
559
591
|
"""Create and submit a job from a completed preparation.
|
|
560
592
|
|
|
561
593
|
This deducts credits and places the job in the execution queue.
|
|
594
|
+
Organization is taken from the preparation; ``organization_id`` on
|
|
595
|
+
``JobCreate`` is optional.
|
|
562
596
|
|
|
563
597
|
Args:
|
|
564
|
-
job: A ``JobCreate`` with the
|
|
565
|
-
|
|
598
|
+
job: A ``JobCreate`` with the preparation ID, chosen execution
|
|
599
|
+
plan, and queue priority.
|
|
566
600
|
|
|
567
601
|
Returns:
|
|
568
602
|
A ``JobRead`` with the new job's ID and initial status.
|
|
@@ -580,7 +614,8 @@ class SchedulerClient(BaseClient):
|
|
|
580
614
|
)
|
|
581
615
|
self._public_plan_notice_shown = True
|
|
582
616
|
|
|
583
|
-
|
|
617
|
+
payload = {k: v for k, v in asdict(job).items() if v is not None}
|
|
618
|
+
data = self._request("POST", "/v1/jobs", json=payload)
|
|
584
619
|
return JobRead.from_dict(data)
|
|
585
620
|
|
|
586
621
|
def get_job(self, job_id: str) -> JobRead:
|
|
@@ -700,14 +735,18 @@ class SchedulerClient(BaseClient):
|
|
|
700
735
|
print("Step 1/5: Uploading job input...")
|
|
701
736
|
upload_id = self.upload_job_input(file_content=file_content, file_path=file_path)
|
|
702
737
|
|
|
738
|
+
shots, configuration_data = self._normalize_shots_and_configuration(
|
|
739
|
+
config.shots,
|
|
740
|
+
config.configuration_data or {},
|
|
741
|
+
)
|
|
703
742
|
prep = JobPreparationCreate(
|
|
704
743
|
organization_id=organization_id,
|
|
705
744
|
backend_class_id=config.backend_class_id,
|
|
706
745
|
name=config.name,
|
|
707
746
|
upload_endpoint_id=upload_id,
|
|
708
747
|
job_subcategory_id=config.job_subcategory_id,
|
|
709
|
-
shots=
|
|
710
|
-
configuration_data=
|
|
748
|
+
shots=shots,
|
|
749
|
+
configuration_data=configuration_data,
|
|
711
750
|
submitted_with=config.submitted_with,
|
|
712
751
|
input_format=config.input_format,
|
|
713
752
|
)
|
|
@@ -744,7 +783,6 @@ class SchedulerClient(BaseClient):
|
|
|
744
783
|
print(f"Step 3/5: Quote approved automatically ({reason})")
|
|
745
784
|
|
|
746
785
|
job_create = JobCreate(
|
|
747
|
-
organization_id=organization_id,
|
|
748
786
|
job_preparation_id=prep_resp.id,
|
|
749
787
|
execution_plan_id=exec_plan_id,
|
|
750
788
|
queue_priority_id=queue_prio_id,
|
|
@@ -56,6 +56,7 @@ class ApiErrorType:
|
|
|
56
56
|
INSUFFICIENT_PERMISSIONS = "INSUFFICIENT_PERMISSIONS"
|
|
57
57
|
ORG_MEMBERSHIP_REQUIRED = "ORG_MEMBERSHIP_REQUIRED"
|
|
58
58
|
TERMS_OF_USE_REQUIRED = "TERMS_OF_USE_REQUIRED"
|
|
59
|
+
EMAIL_VERIFICATION_REQUIRED = "EMAIL_VERIFICATION_REQUIRED"
|
|
59
60
|
NOT_FOUND = "NOT_FOUND"
|
|
60
61
|
CONFLICT = "CONFLICT"
|
|
61
62
|
VALIDATION_ERROR = "VALIDATION_ERROR"
|
|
@@ -110,6 +111,10 @@ TERMS_OF_USE_REQUIRED_MESSAGE = (
|
|
|
110
111
|
"Please visit https://www.openquantum.com to accept the Terms of Use before submitting jobs."
|
|
111
112
|
)
|
|
112
113
|
|
|
114
|
+
EMAIL_VERIFICATION_REQUIRED_MESSAGE = (
|
|
115
|
+
"Please verify your email address at https://www.openquantum.com before submitting jobs."
|
|
116
|
+
)
|
|
117
|
+
|
|
113
118
|
|
|
114
119
|
class TermsOfUseRequiredError(APIError):
|
|
115
120
|
"""Raised when job prepare/create is blocked until Terms of Use are accepted.
|
|
@@ -131,6 +136,25 @@ class TermsOfUseRequiredError(APIError):
|
|
|
131
136
|
Exception.__init__(self, TERMS_OF_USE_REQUIRED_MESSAGE)
|
|
132
137
|
|
|
133
138
|
|
|
139
|
+
class EmailVerificationRequiredError(APIError):
|
|
140
|
+
"""Raised when job prepare/create is blocked until the user's email is verified.
|
|
141
|
+
|
|
142
|
+
Users must visit https://www.openquantum.com, sign in, and complete email
|
|
143
|
+
verification before submitting jobs. If an SDK key was created before
|
|
144
|
+
verification, create a new key after verifying.
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
def __init__(self, error_response: Optional[ErrorResponse] = None):
|
|
148
|
+
if error_response is None:
|
|
149
|
+
error_response = ErrorResponse(
|
|
150
|
+
status_code=403,
|
|
151
|
+
message=EMAIL_VERIFICATION_REQUIRED_MESSAGE,
|
|
152
|
+
type=ApiErrorType.EMAIL_VERIFICATION_REQUIRED,
|
|
153
|
+
)
|
|
154
|
+
self.error_response = error_response
|
|
155
|
+
Exception.__init__(self, EMAIL_VERIFICATION_REQUIRED_MESSAGE)
|
|
156
|
+
|
|
157
|
+
|
|
134
158
|
def error_from_response(error_response: ErrorResponse) -> APIError:
|
|
135
159
|
"""Map an API ``ErrorResponse`` to the most specific SDK exception.
|
|
136
160
|
|
|
@@ -138,6 +162,8 @@ def error_from_response(error_response: ErrorResponse) -> APIError:
|
|
|
138
162
|
"""
|
|
139
163
|
if error_response.type == ApiErrorType.TERMS_OF_USE_REQUIRED:
|
|
140
164
|
return TermsOfUseRequiredError(error_response)
|
|
165
|
+
if error_response.type == ApiErrorType.EMAIL_VERIFICATION_REQUIRED:
|
|
166
|
+
return EmailVerificationRequiredError(error_response)
|
|
141
167
|
return APIError(error_response)
|
|
142
168
|
|
|
143
169
|
|
|
@@ -323,16 +349,18 @@ class JobPreparationCreate:
|
|
|
323
349
|
upload_endpoint_id: ID returned by the upload endpoint for the
|
|
324
350
|
input data file.
|
|
325
351
|
job_subcategory_id: Job subcategory UUID or short code.
|
|
326
|
-
shots:
|
|
352
|
+
shots: Optional measurement shot count (legacy first-class field).
|
|
353
|
+
Prefer ``configuration_data["shots"]``. Omit for backends that
|
|
354
|
+
do not use shots. When set, the SDK dual-writes into
|
|
355
|
+
configuration_data if missing.
|
|
327
356
|
configuration_data: Optional backend-specific configuration.
|
|
328
357
|
submitted_with: Client/surface that submitted this preparation
|
|
329
358
|
(e.g. ``"sdk"``, ``"qiskit"``, ``"pennylane"``, ``"cli"``).
|
|
330
359
|
Optional; omit or ``None`` for unknown/legacy clients.
|
|
331
|
-
input_format: Circuit input language family (``"qasm"``
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
``
|
|
335
|
-
in the backend's ``constraint_data.supported_formats``.
|
|
360
|
+
input_format: Circuit input language family (e.g. ``"qasm"``).
|
|
361
|
+
Defaults to ``"qasm"``. OpenQASM 2 and 3 both use ``"qasm"``;
|
|
362
|
+
version is detected from the file. Must be listed in the
|
|
363
|
+
backend's ``constraint_data.supported_formats``.
|
|
336
364
|
"""
|
|
337
365
|
|
|
338
366
|
organization_id: str
|
|
@@ -340,7 +368,7 @@ class JobPreparationCreate:
|
|
|
340
368
|
name: str
|
|
341
369
|
upload_endpoint_id: str
|
|
342
370
|
job_subcategory_id: str
|
|
343
|
-
shots: int
|
|
371
|
+
shots: Optional[int] = None
|
|
344
372
|
configuration_data: Dict[str, Any] = field(default_factory=dict)
|
|
345
373
|
submitted_with: Optional[str] = None
|
|
346
374
|
input_format: Optional[str] = None
|
|
@@ -433,12 +461,12 @@ class JobPreparationResultResponse:
|
|
|
433
461
|
input_data_url: URL where the input data is stored.
|
|
434
462
|
job_category_id: Resolved job category UUID.
|
|
435
463
|
job_subcategory_id: Resolved job subcategory UUID.
|
|
436
|
-
shots:
|
|
464
|
+
shots: Resolved shot count when applicable; may be ``None`` for jobs
|
|
465
|
+
without shots.
|
|
437
466
|
quote: List of available execution plans with pricing details.
|
|
438
467
|
message: Optional status or error message.
|
|
439
468
|
configuration_data: Backend-specific configuration echoed back.
|
|
440
|
-
input_format: Circuit input language family (``"qasm"``
|
|
441
|
-
``"qblox_sequence"``).
|
|
469
|
+
input_format: Circuit input language family (e.g. ``"qasm"``).
|
|
442
470
|
extra: Additional fields returned by the API not mapped to attributes.
|
|
443
471
|
"""
|
|
444
472
|
|
|
@@ -449,7 +477,7 @@ class JobPreparationResultResponse:
|
|
|
449
477
|
input_data_url: str
|
|
450
478
|
job_category_id: str
|
|
451
479
|
job_subcategory_id: str
|
|
452
|
-
shots: int
|
|
480
|
+
shots: Optional[int]
|
|
453
481
|
quote: List[QuotePlan]
|
|
454
482
|
message: Optional[str] = None
|
|
455
483
|
configuration_data: Dict[str, Any] = field(default_factory=dict)
|
|
@@ -464,16 +492,18 @@ class JobCreate:
|
|
|
464
492
|
Sent to ``POST /v1/jobs`` to finalize and enqueue the job.
|
|
465
493
|
|
|
466
494
|
Attributes:
|
|
467
|
-
organization_id: UUID of the organization submitting the job.
|
|
468
495
|
job_preparation_id: ID of the completed preparation.
|
|
469
496
|
execution_plan_id: UUID of the chosen execution plan from the quote.
|
|
470
497
|
queue_priority_id: UUID of the chosen queue priority from the quote.
|
|
498
|
+
organization_id: Optional organization UUID. If omitted, the server
|
|
499
|
+
uses the organization from the job preparation. If provided, it
|
|
500
|
+
must match the preparation organization.
|
|
471
501
|
"""
|
|
472
502
|
|
|
473
|
-
organization_id: str
|
|
474
503
|
job_preparation_id: str
|
|
475
504
|
execution_plan_id: str
|
|
476
505
|
queue_priority_id: str
|
|
506
|
+
organization_id: Optional[str] = None
|
|
477
507
|
|
|
478
508
|
|
|
479
509
|
@dataclass
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openquantum-sdk
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
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,10 +8,17 @@ 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: dev
|
|
12
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
13
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
14
|
+
Requires-Dist: pytest-mock; extra == "dev"
|
|
15
|
+
Requires-Dist: requests-mock>=1.10.0; extra == "dev"
|
|
16
|
+
Requires-Dist: flake8; extra == "dev"
|
|
17
|
+
Requires-Dist: black; extra == "dev"
|
|
11
18
|
Provides-Extra: qiskit
|
|
12
|
-
Requires-Dist: openquantum-sdk-qiskit>=0.3.
|
|
19
|
+
Requires-Dist: openquantum-sdk-qiskit>=0.3.1; extra == "qiskit"
|
|
13
20
|
Provides-Extra: pennylane
|
|
14
|
-
Requires-Dist: openquantum-sdk-pennylane>=0.2.
|
|
21
|
+
Requires-Dist: openquantum-sdk-pennylane>=0.2.1; extra == "pennylane"
|
|
15
22
|
|
|
16
23
|
# Open Quantum Python SDK
|
|
17
24
|
|
|
@@ -35,7 +42,7 @@ pip install -U openquantum-sdk
|
|
|
35
42
|
pip install -e .
|
|
36
43
|
```
|
|
37
44
|
|
|
38
|
-
Python 3.
|
|
45
|
+
Python 3.8+ required.
|
|
39
46
|
|
|
40
47
|
## Authentication
|
|
41
48
|
|
|
@@ -220,9 +227,8 @@ try:
|
|
|
220
227
|
cheapest_plan = min(result.quote, key=lambda p: p.price)
|
|
221
228
|
cheapest_prio = min(cheapest_plan.queue_priorities, key=lambda q: q.price_increase)
|
|
222
229
|
|
|
223
|
-
# 7. Create job
|
|
230
|
+
# 7. Create job (organization comes from the preparation)
|
|
224
231
|
job = JobCreate(
|
|
225
|
-
organization_id=org.id,
|
|
226
232
|
job_preparation_id=prep_resp.id,
|
|
227
233
|
execution_plan_id=cheapest_plan.execution_plan_id,
|
|
228
234
|
queue_priority_id=cheapest_prio.queue_priority_id,
|
|
@@ -320,7 +326,7 @@ finally:
|
|
|
320
326
|
| `BackendClassRead` | `id`, `name`, `description`, `type`, `provider_id`, `short_code`, `queue_depth`, `accepting_jobs`, `status` |
|
|
321
327
|
| `ErrorResponse` | `status_code`, `message: List[str]`, `type`, `error_code` (request id) |
|
|
322
328
|
| `JobCategoryRead` | `id`, `name`, `short_code` |
|
|
323
|
-
| `JobCreate` | `
|
|
329
|
+
| `JobCreate` | `job_preparation_id`, `execution_plan_id`, `queue_priority_id`, optional `organization_id` |
|
|
324
330
|
| `JobList` | `id`, `backend_class_id`, `name`, `status`, `backend_name`, `output_data_url`, `calibration_data_url`, `credits_used`, `submitted_at` |
|
|
325
331
|
| `JobPreparationCreate` | `organization_id`, `backend_class_id`, `name`, `upload_endpoint_id`, `job_subcategory_id`, `shots`, `configuration_data`, `input_format`, `submitted_with` |
|
|
326
332
|
| `JobPreparationResponse` | `id` |
|
|
@@ -4,13 +4,12 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "openquantum-sdk"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.2"
|
|
8
8
|
description = "Python SDK for Open Quantum"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
11
|
-
# Remove 'dataclasses' on Python 3.11+; it's stdlib and the backport causes issues
|
|
12
11
|
dependencies = [
|
|
13
|
-
"requests>=2.25.0"
|
|
12
|
+
"requests>=2.25.0",
|
|
14
13
|
]
|
|
15
14
|
classifiers = [
|
|
16
15
|
"Programming Language :: Python :: 3",
|
|
@@ -19,8 +18,16 @@ classifiers = [
|
|
|
19
18
|
]
|
|
20
19
|
|
|
21
20
|
[project.optional-dependencies]
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
dev = [
|
|
22
|
+
"pytest>=7.0",
|
|
23
|
+
"pytest-cov",
|
|
24
|
+
"pytest-mock",
|
|
25
|
+
"requests-mock>=1.10.0",
|
|
26
|
+
"flake8",
|
|
27
|
+
"black",
|
|
28
|
+
]
|
|
29
|
+
qiskit = ["openquantum-sdk-qiskit>=0.3.1"]
|
|
30
|
+
pennylane = ["openquantum-sdk-pennylane>=0.2.1"]
|
|
24
31
|
|
|
25
32
|
[project.scripts]
|
|
26
33
|
openquantum-sdk = "openquantum_sdk.__main__:main"
|
|
@@ -20,6 +20,8 @@ from openquantum_sdk.models import (
|
|
|
20
20
|
OrganizationRead,
|
|
21
21
|
TERMS_OF_USE_REQUIRED_MESSAGE,
|
|
22
22
|
TermsOfUseRequiredError,
|
|
23
|
+
EMAIL_VERIFICATION_REQUIRED_MESSAGE,
|
|
24
|
+
EmailVerificationRequiredError,
|
|
23
25
|
)
|
|
24
26
|
|
|
25
27
|
|
|
@@ -499,7 +501,6 @@ def test_create_job_terms_of_use_required(mock_scheduler):
|
|
|
499
501
|
},
|
|
500
502
|
)
|
|
501
503
|
job = JobCreate(
|
|
502
|
-
organization_id="org-123",
|
|
503
504
|
job_preparation_id="prep-1",
|
|
504
505
|
execution_plan_id="plan-1",
|
|
505
506
|
queue_priority_id="prio-1",
|
|
@@ -508,6 +509,55 @@ def test_create_job_terms_of_use_required(mock_scheduler):
|
|
|
508
509
|
mock_scheduler.create_job(job)
|
|
509
510
|
|
|
510
511
|
|
|
512
|
+
def test_prepare_job_email_verification_required(mock_scheduler):
|
|
513
|
+
mock_scheduler.session.mocker.post(
|
|
514
|
+
"https://scheduler.openquantum.com/v1/jobs/prepare",
|
|
515
|
+
status_code=403,
|
|
516
|
+
json={
|
|
517
|
+
"status_code": 403,
|
|
518
|
+
"message": [EMAIL_VERIFICATION_REQUIRED_MESSAGE],
|
|
519
|
+
"type": "EMAIL_VERIFICATION_REQUIRED",
|
|
520
|
+
"error_code": "req-email",
|
|
521
|
+
},
|
|
522
|
+
)
|
|
523
|
+
prep = JobPreparationCreate(
|
|
524
|
+
organization_id="org-123",
|
|
525
|
+
backend_class_id="backend-1",
|
|
526
|
+
name="Test",
|
|
527
|
+
upload_endpoint_id="upload-1",
|
|
528
|
+
job_subcategory_id="sub-1",
|
|
529
|
+
shots=10,
|
|
530
|
+
configuration_data={},
|
|
531
|
+
)
|
|
532
|
+
with pytest.raises(
|
|
533
|
+
EmailVerificationRequiredError, match="verify your email"
|
|
534
|
+
) as exc_info:
|
|
535
|
+
mock_scheduler.prepare_job(prep)
|
|
536
|
+
assert str(exc_info.value) == EMAIL_VERIFICATION_REQUIRED_MESSAGE
|
|
537
|
+
assert exc_info.value.error_response.status_code == 403
|
|
538
|
+
assert exc_info.value.error_response.type == "EMAIL_VERIFICATION_REQUIRED"
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def test_create_job_email_verification_required(mock_scheduler):
|
|
542
|
+
mock_scheduler.session.mocker.post(
|
|
543
|
+
"https://scheduler.openquantum.com/v1/jobs",
|
|
544
|
+
status_code=403,
|
|
545
|
+
json={
|
|
546
|
+
"status_code": 403,
|
|
547
|
+
"message": EMAIL_VERIFICATION_REQUIRED_MESSAGE,
|
|
548
|
+
"type": "EMAIL_VERIFICATION_REQUIRED",
|
|
549
|
+
"error_code": "req-email",
|
|
550
|
+
},
|
|
551
|
+
)
|
|
552
|
+
job = JobCreate(
|
|
553
|
+
job_preparation_id="prep-1",
|
|
554
|
+
execution_plan_id="plan-1",
|
|
555
|
+
queue_priority_id="prio-1",
|
|
556
|
+
)
|
|
557
|
+
with pytest.raises(EmailVerificationRequiredError, match="email"):
|
|
558
|
+
mock_scheduler.create_job(job)
|
|
559
|
+
|
|
560
|
+
|
|
511
561
|
def test_prepare_job_org_membership_required(mock_scheduler):
|
|
512
562
|
from openquantum_sdk.models import APIError, ApiErrorType
|
|
513
563
|
|
|
@@ -533,6 +583,7 @@ def test_prepare_job_org_membership_required(mock_scheduler):
|
|
|
533
583
|
with pytest.raises(APIError) as exc_info:
|
|
534
584
|
mock_scheduler.prepare_job(prep)
|
|
535
585
|
assert not isinstance(exc_info.value, TermsOfUseRequiredError)
|
|
586
|
+
assert not isinstance(exc_info.value, EmailVerificationRequiredError)
|
|
536
587
|
assert (
|
|
537
588
|
exc_info.value.error_response.type
|
|
538
589
|
== ApiErrorType.ORG_MEMBERSHIP_REQUIRED
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{openquantum_sdk-0.4.0 → openquantum_sdk-0.4.2}/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
|