qdsv-bridge 0.1.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.
- qdsv_bridge-0.1.0/.gitignore +7 -0
- qdsv_bridge-0.1.0/LICENSE +21 -0
- qdsv_bridge-0.1.0/PKG-INFO +206 -0
- qdsv_bridge-0.1.0/README.md +183 -0
- qdsv_bridge-0.1.0/examples/eeg_semantic_signal_classification.py +32 -0
- qdsv_bridge-0.1.0/pyproject.toml +39 -0
- qdsv_bridge-0.1.0/src/qdsv_bridge/__init__.py +4 -0
- qdsv_bridge-0.1.0/src/qdsv_bridge/cli.py +30 -0
- qdsv_bridge-0.1.0/src/qdsv_bridge/client.py +105 -0
- qdsv_bridge-0.1.0/src/qdsv_bridge/exceptions.py +20 -0
- qdsv_bridge-0.1.0/tests/test_client.py +96 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 QDSV / Qruba
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qdsv-bridge
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lightweight Python client for QDSV Bridge, a controlled semantic-to-IR/oracle/circuit compiler.
|
|
5
|
+
Project-URL: Homepage, https://qdsv.cloud
|
|
6
|
+
Project-URL: Documentation, https://qdsv.cloud/#bridge
|
|
7
|
+
Project-URL: Source, https://github.com/qdsvquantum-afk/qdsv-bridge
|
|
8
|
+
Author: QDSV / Qruba
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: circuit,qasm,qdsv,quantum,sdk,semantic-computation
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Requires-Dist: requests>=2.31
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# QDSV Bridge Developer Preview
|
|
25
|
+
|
|
26
|
+
Lightweight Python SDK for **QDSV Bridge**, a controlled semantic compiler that turns supported problem-family specifications into QDSV IR, oracle specs, QASM blueprints, or integration-ready circuit scaffolds.
|
|
27
|
+
|
|
28
|
+
QDSV Bridge is not a free circuit generator. It is a restricted semantic-to-circuit bridge for supported problem families.
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
problem family spec
|
|
32
|
+
-> family ontology validation
|
|
33
|
+
-> semantic ProblemSpec / IR
|
|
34
|
+
-> oracle specification
|
|
35
|
+
-> materialization policy
|
|
36
|
+
-> QASM / Qiskit blueprint when the target requires circuits
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The SDK is a client only. It does not include the private QDSV Runtime, CAP, backend selector, lowering internals, QuEST/Aer/IBM adapters, or advanced orchestration.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install qdsv-bridge
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Why This Exists
|
|
46
|
+
|
|
47
|
+
Traditional quantum workflows often start by asking users to choose a circuit, encoding, ansatz, or measurement pattern. That can force the data to adapt to a circuit.
|
|
48
|
+
|
|
49
|
+
QDSV Bridge starts from a controlled semantic family. The family defines what kind of problem is allowed, what concepts belong to it, what patterns are excluded, and what evidence must be produced. Circuit materialization is only one possible export when another platform requires it.
|
|
50
|
+
|
|
51
|
+
For example, in signal classification:
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
prepared signals
|
|
55
|
+
-> semantic_signal_classification
|
|
56
|
+
-> preserve signal geometry
|
|
57
|
+
-> oracle / IR / QASM blueprint
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This is designed to avoid a fixed pattern such as:
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
forced reduction -> angle encoding -> fixed ansatz -> fixed measurement
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Quick Start
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from qdsv_bridge import QDSVBridgeClient
|
|
70
|
+
|
|
71
|
+
client = QDSVBridgeClient()
|
|
72
|
+
|
|
73
|
+
spec = {
|
|
74
|
+
"family": "semantic_signal_classification",
|
|
75
|
+
"state_space": {
|
|
76
|
+
"kind": "finite_candidates",
|
|
77
|
+
"candidate_count": 300,
|
|
78
|
+
"candidate_id": "eeg_window",
|
|
79
|
+
},
|
|
80
|
+
"signals": [
|
|
81
|
+
"dwt_cD3_std_score",
|
|
82
|
+
"std_score",
|
|
83
|
+
"activity_score",
|
|
84
|
+
"energy_score",
|
|
85
|
+
"dwt_cD2_std_score",
|
|
86
|
+
"complexity_score",
|
|
87
|
+
],
|
|
88
|
+
"goal": {
|
|
89
|
+
"kind": "binary_marking",
|
|
90
|
+
"positive_state": "ictal",
|
|
91
|
+
},
|
|
92
|
+
"materialization_policy": {
|
|
93
|
+
"preserve_signal_geometry": True,
|
|
94
|
+
"avoid_fixed_ansatz": True,
|
|
95
|
+
"avoid_forced_dimensionality_reduction": True,
|
|
96
|
+
"report_information_loss": True,
|
|
97
|
+
},
|
|
98
|
+
"target": {
|
|
99
|
+
"format": "qasm3",
|
|
100
|
+
"backend_family": "qiskit",
|
|
101
|
+
},
|
|
102
|
+
"limits": {
|
|
103
|
+
"max_qubits": 10,
|
|
104
|
+
"max_depth": 300,
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
result = client.export(spec)
|
|
109
|
+
|
|
110
|
+
print(result["status"])
|
|
111
|
+
print(result["semantic_preservation_report"])
|
|
112
|
+
print(result["artifact"]["content"])
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Public Endpoints
|
|
116
|
+
|
|
117
|
+
The SDK calls the QDSV API:
|
|
118
|
+
|
|
119
|
+
- `GET /api/bridge/families`
|
|
120
|
+
- `POST /api/bridge/validate`
|
|
121
|
+
- `POST /api/bridge/compile`
|
|
122
|
+
- `POST /api/bridge/explain`
|
|
123
|
+
- `POST /api/bridge/export`
|
|
124
|
+
|
|
125
|
+
Default cloud endpoint:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
client = QDSVBridgeClient(api_url="https://api.qdsv.cloud/api")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Local/private Docker endpoint:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
client = QDSVBridgeClient.local()
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Supported Families
|
|
138
|
+
|
|
139
|
+
Developer Preview families:
|
|
140
|
+
|
|
141
|
+
- `semantic_signal_classification`
|
|
142
|
+
- `predicate_marking`
|
|
143
|
+
- `state_similarity`
|
|
144
|
+
- `combinatorial_relation`
|
|
145
|
+
- `distribution_sampling`
|
|
146
|
+
|
|
147
|
+
Each family has:
|
|
148
|
+
|
|
149
|
+
- ontology boundary;
|
|
150
|
+
- allowed state-space kinds;
|
|
151
|
+
- allowed goal kinds;
|
|
152
|
+
- allowed semantic operations;
|
|
153
|
+
- excluded patterns;
|
|
154
|
+
- public limits;
|
|
155
|
+
- export targets;
|
|
156
|
+
- evidence / digest contract.
|
|
157
|
+
|
|
158
|
+
## What It Exports
|
|
159
|
+
|
|
160
|
+
Depending on `target.format`, QDSV Bridge can export:
|
|
161
|
+
|
|
162
|
+
- `problem_spec`
|
|
163
|
+
- `ir`
|
|
164
|
+
- `oracle_spec`
|
|
165
|
+
- `qasm2`
|
|
166
|
+
- `qasm3`
|
|
167
|
+
- `qiskit_blueprint`
|
|
168
|
+
|
|
169
|
+
QASM outputs are bridge blueprints with explicit semantic oracle insertion points and QDSV digests. External platforms should inspect the returned preservation report, qubit/depth limits and oracle digest before executing or optimizing the generated artifact.
|
|
170
|
+
|
|
171
|
+
## Important Boundaries
|
|
172
|
+
|
|
173
|
+
QDSV Bridge does not expose:
|
|
174
|
+
|
|
175
|
+
- QDSV Runtime internals;
|
|
176
|
+
- CAP internals;
|
|
177
|
+
- backend selection heuristics;
|
|
178
|
+
- private QuEST/Aer/IBM adapters;
|
|
179
|
+
- native lowering internals;
|
|
180
|
+
- unrestricted circuit generation;
|
|
181
|
+
- arbitrary Python execution;
|
|
182
|
+
- user-supplied free circuits as family contracts.
|
|
183
|
+
|
|
184
|
+
The product principle is:
|
|
185
|
+
|
|
186
|
+
```text
|
|
187
|
+
controlled semantic family
|
|
188
|
+
-> flexible materialization
|
|
189
|
+
-> auditable export
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Not:
|
|
193
|
+
|
|
194
|
+
```text
|
|
195
|
+
any input
|
|
196
|
+
-> arbitrary circuit
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Open SDK, Private Runtime
|
|
200
|
+
|
|
201
|
+
This repository is open-core:
|
|
202
|
+
|
|
203
|
+
- **Open under MIT:** Python client SDK, examples, docs and tests.
|
|
204
|
+
- **Not included:** QDSV Runtime, ontology compiler internals, CAP, lowering, advanced materialization, backend adapters, private endpoints, secrets or production configuration.
|
|
205
|
+
|
|
206
|
+
QDSV, QIntent and Qruba names and marks are project marks of their respective owners. The MIT License for this repository does not grant trademark rights.
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# QDSV Bridge Developer Preview
|
|
2
|
+
|
|
3
|
+
Lightweight Python SDK for **QDSV Bridge**, a controlled semantic compiler that turns supported problem-family specifications into QDSV IR, oracle specs, QASM blueprints, or integration-ready circuit scaffolds.
|
|
4
|
+
|
|
5
|
+
QDSV Bridge is not a free circuit generator. It is a restricted semantic-to-circuit bridge for supported problem families.
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
problem family spec
|
|
9
|
+
-> family ontology validation
|
|
10
|
+
-> semantic ProblemSpec / IR
|
|
11
|
+
-> oracle specification
|
|
12
|
+
-> materialization policy
|
|
13
|
+
-> QASM / Qiskit blueprint when the target requires circuits
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The SDK is a client only. It does not include the private QDSV Runtime, CAP, backend selector, lowering internals, QuEST/Aer/IBM adapters, or advanced orchestration.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install qdsv-bridge
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Why This Exists
|
|
23
|
+
|
|
24
|
+
Traditional quantum workflows often start by asking users to choose a circuit, encoding, ansatz, or measurement pattern. That can force the data to adapt to a circuit.
|
|
25
|
+
|
|
26
|
+
QDSV Bridge starts from a controlled semantic family. The family defines what kind of problem is allowed, what concepts belong to it, what patterns are excluded, and what evidence must be produced. Circuit materialization is only one possible export when another platform requires it.
|
|
27
|
+
|
|
28
|
+
For example, in signal classification:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
prepared signals
|
|
32
|
+
-> semantic_signal_classification
|
|
33
|
+
-> preserve signal geometry
|
|
34
|
+
-> oracle / IR / QASM blueprint
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This is designed to avoid a fixed pattern such as:
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
forced reduction -> angle encoding -> fixed ansatz -> fixed measurement
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from qdsv_bridge import QDSVBridgeClient
|
|
47
|
+
|
|
48
|
+
client = QDSVBridgeClient()
|
|
49
|
+
|
|
50
|
+
spec = {
|
|
51
|
+
"family": "semantic_signal_classification",
|
|
52
|
+
"state_space": {
|
|
53
|
+
"kind": "finite_candidates",
|
|
54
|
+
"candidate_count": 300,
|
|
55
|
+
"candidate_id": "eeg_window",
|
|
56
|
+
},
|
|
57
|
+
"signals": [
|
|
58
|
+
"dwt_cD3_std_score",
|
|
59
|
+
"std_score",
|
|
60
|
+
"activity_score",
|
|
61
|
+
"energy_score",
|
|
62
|
+
"dwt_cD2_std_score",
|
|
63
|
+
"complexity_score",
|
|
64
|
+
],
|
|
65
|
+
"goal": {
|
|
66
|
+
"kind": "binary_marking",
|
|
67
|
+
"positive_state": "ictal",
|
|
68
|
+
},
|
|
69
|
+
"materialization_policy": {
|
|
70
|
+
"preserve_signal_geometry": True,
|
|
71
|
+
"avoid_fixed_ansatz": True,
|
|
72
|
+
"avoid_forced_dimensionality_reduction": True,
|
|
73
|
+
"report_information_loss": True,
|
|
74
|
+
},
|
|
75
|
+
"target": {
|
|
76
|
+
"format": "qasm3",
|
|
77
|
+
"backend_family": "qiskit",
|
|
78
|
+
},
|
|
79
|
+
"limits": {
|
|
80
|
+
"max_qubits": 10,
|
|
81
|
+
"max_depth": 300,
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
result = client.export(spec)
|
|
86
|
+
|
|
87
|
+
print(result["status"])
|
|
88
|
+
print(result["semantic_preservation_report"])
|
|
89
|
+
print(result["artifact"]["content"])
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Public Endpoints
|
|
93
|
+
|
|
94
|
+
The SDK calls the QDSV API:
|
|
95
|
+
|
|
96
|
+
- `GET /api/bridge/families`
|
|
97
|
+
- `POST /api/bridge/validate`
|
|
98
|
+
- `POST /api/bridge/compile`
|
|
99
|
+
- `POST /api/bridge/explain`
|
|
100
|
+
- `POST /api/bridge/export`
|
|
101
|
+
|
|
102
|
+
Default cloud endpoint:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
client = QDSVBridgeClient(api_url="https://api.qdsv.cloud/api")
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Local/private Docker endpoint:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
client = QDSVBridgeClient.local()
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Supported Families
|
|
115
|
+
|
|
116
|
+
Developer Preview families:
|
|
117
|
+
|
|
118
|
+
- `semantic_signal_classification`
|
|
119
|
+
- `predicate_marking`
|
|
120
|
+
- `state_similarity`
|
|
121
|
+
- `combinatorial_relation`
|
|
122
|
+
- `distribution_sampling`
|
|
123
|
+
|
|
124
|
+
Each family has:
|
|
125
|
+
|
|
126
|
+
- ontology boundary;
|
|
127
|
+
- allowed state-space kinds;
|
|
128
|
+
- allowed goal kinds;
|
|
129
|
+
- allowed semantic operations;
|
|
130
|
+
- excluded patterns;
|
|
131
|
+
- public limits;
|
|
132
|
+
- export targets;
|
|
133
|
+
- evidence / digest contract.
|
|
134
|
+
|
|
135
|
+
## What It Exports
|
|
136
|
+
|
|
137
|
+
Depending on `target.format`, QDSV Bridge can export:
|
|
138
|
+
|
|
139
|
+
- `problem_spec`
|
|
140
|
+
- `ir`
|
|
141
|
+
- `oracle_spec`
|
|
142
|
+
- `qasm2`
|
|
143
|
+
- `qasm3`
|
|
144
|
+
- `qiskit_blueprint`
|
|
145
|
+
|
|
146
|
+
QASM outputs are bridge blueprints with explicit semantic oracle insertion points and QDSV digests. External platforms should inspect the returned preservation report, qubit/depth limits and oracle digest before executing or optimizing the generated artifact.
|
|
147
|
+
|
|
148
|
+
## Important Boundaries
|
|
149
|
+
|
|
150
|
+
QDSV Bridge does not expose:
|
|
151
|
+
|
|
152
|
+
- QDSV Runtime internals;
|
|
153
|
+
- CAP internals;
|
|
154
|
+
- backend selection heuristics;
|
|
155
|
+
- private QuEST/Aer/IBM adapters;
|
|
156
|
+
- native lowering internals;
|
|
157
|
+
- unrestricted circuit generation;
|
|
158
|
+
- arbitrary Python execution;
|
|
159
|
+
- user-supplied free circuits as family contracts.
|
|
160
|
+
|
|
161
|
+
The product principle is:
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
controlled semantic family
|
|
165
|
+
-> flexible materialization
|
|
166
|
+
-> auditable export
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Not:
|
|
170
|
+
|
|
171
|
+
```text
|
|
172
|
+
any input
|
|
173
|
+
-> arbitrary circuit
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Open SDK, Private Runtime
|
|
177
|
+
|
|
178
|
+
This repository is open-core:
|
|
179
|
+
|
|
180
|
+
- **Open under MIT:** Python client SDK, examples, docs and tests.
|
|
181
|
+
- **Not included:** QDSV Runtime, ontology compiler internals, CAP, lowering, advanced materialization, backend adapters, private endpoints, secrets or production configuration.
|
|
182
|
+
|
|
183
|
+
QDSV, QIntent and Qruba names and marks are project marks of their respective owners. The MIT License for this repository does not grant trademark rights.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from qdsv_bridge import QDSVBridgeClient
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
spec = {
|
|
5
|
+
"family": "semantic_signal_classification",
|
|
6
|
+
"state_space": {"kind": "finite_candidates", "candidate_count": 300, "candidate_id": "eeg_window"},
|
|
7
|
+
"signals": [
|
|
8
|
+
"dwt_cD3_std_score",
|
|
9
|
+
"std_score",
|
|
10
|
+
"activity_score",
|
|
11
|
+
"energy_score",
|
|
12
|
+
"dwt_cD2_std_score",
|
|
13
|
+
"complexity_score",
|
|
14
|
+
],
|
|
15
|
+
"goal": {"kind": "binary_marking", "positive_state": "ictal"},
|
|
16
|
+
"materialization_policy": {
|
|
17
|
+
"preserve_signal_geometry": True,
|
|
18
|
+
"avoid_fixed_ansatz": True,
|
|
19
|
+
"avoid_forced_dimensionality_reduction": True,
|
|
20
|
+
"report_information_loss": True,
|
|
21
|
+
},
|
|
22
|
+
"target": {"format": "qasm3", "backend_family": "qiskit"},
|
|
23
|
+
"limits": {"max_qubits": 10, "max_depth": 300},
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
client = QDSVBridgeClient()
|
|
28
|
+
result = client.export(spec)
|
|
29
|
+
|
|
30
|
+
print(result["status"])
|
|
31
|
+
print(result["semantic_preservation_report"])
|
|
32
|
+
print(result["artifact"]["content"])
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.25"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "qdsv-bridge"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Lightweight Python client for QDSV Bridge, a controlled semantic-to-IR/oracle/circuit compiler."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "QDSV / Qruba" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["qdsv", "semantic-computation", "quantum", "circuit", "qasm", "sdk"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Intended Audience :: Science/Research",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
23
|
+
"Topic :: Scientific/Engineering",
|
|
24
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"requests>=2.31",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://qdsv.cloud"
|
|
32
|
+
Documentation = "https://qdsv.cloud/#bridge"
|
|
33
|
+
Source = "https://github.com/qdsvquantum-afk/qdsv-bridge"
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
qdsv-bridge = "qdsv_bridge.cli:main"
|
|
37
|
+
|
|
38
|
+
[tool.hatch.build.targets.wheel]
|
|
39
|
+
packages = ["src/qdsv_bridge"]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .client import QDSVBridgeClient
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main() -> None:
|
|
11
|
+
parser = argparse.ArgumentParser(description="QDSV Bridge SDK CLI")
|
|
12
|
+
parser.add_argument("command", choices=["families", "validate", "compile", "explain", "export"])
|
|
13
|
+
parser.add_argument("spec", nargs="?", help="Path to a Semantic Circuit Spec JSON file.")
|
|
14
|
+
parser.add_argument("--api-url", default=None)
|
|
15
|
+
parser.add_argument("--local", action="store_true", help="Use http://localhost:18080/api")
|
|
16
|
+
args = parser.parse_args()
|
|
17
|
+
|
|
18
|
+
client = QDSVBridgeClient.local() if args.local else QDSVBridgeClient(api_url=args.api_url)
|
|
19
|
+
if args.command == "families":
|
|
20
|
+
print(json.dumps(client.families(), indent=2, ensure_ascii=False))
|
|
21
|
+
return
|
|
22
|
+
if not args.spec:
|
|
23
|
+
parser.error("spec JSON file is required for this command")
|
|
24
|
+
spec = json.loads(Path(args.spec).read_text(encoding="utf-8"))
|
|
25
|
+
result = getattr(client, args.command)(spec)
|
|
26
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
if __name__ == "__main__":
|
|
30
|
+
main()
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any, Mapping
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
from .exceptions import QDSVBridgeAPIError, QDSVBridgeHTTPError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
DEFAULT_API_URL = "https://api.qdsv.cloud/api"
|
|
12
|
+
PRIVATE_NODE_UNAVAILABLE_MESSAGE = (
|
|
13
|
+
"Private QDSV node temporarily unavailable. It may be offline, reserved for "
|
|
14
|
+
"private processing, or busy. Try again later or use QDSVBridgeClient() for "
|
|
15
|
+
"public cloud examples."
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class QDSVBridgeClient:
|
|
20
|
+
"""Client for QDSV Bridge public API endpoints."""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
api_url: str | None = None,
|
|
25
|
+
api_key: str | None = None,
|
|
26
|
+
*,
|
|
27
|
+
timeout: float = 30.0,
|
|
28
|
+
license_key: str | None = None,
|
|
29
|
+
sdk_name: str = "qdsv-bridge",
|
|
30
|
+
) -> None:
|
|
31
|
+
self.api_url = self._normalize_api_url(
|
|
32
|
+
api_url or os.getenv("QDSV_BRIDGE_API_URL") or os.getenv("QDSV_API_URL") or DEFAULT_API_URL
|
|
33
|
+
)
|
|
34
|
+
self.api_key = api_key or os.getenv("QDSV_BRIDGE_API_KEY") or os.getenv("QDSV_API_KEY")
|
|
35
|
+
self.license_key = license_key or os.getenv("QDSV_LICENSE_KEY")
|
|
36
|
+
self.timeout = timeout
|
|
37
|
+
self.sdk_name = sdk_name
|
|
38
|
+
self._private_node = self._looks_like_private_node(self.api_url)
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def local(
|
|
42
|
+
cls,
|
|
43
|
+
*,
|
|
44
|
+
api_url: str = "http://localhost:18080/api",
|
|
45
|
+
api_key: str | None = None,
|
|
46
|
+
timeout: float = 30.0,
|
|
47
|
+
license_key: str | None = None,
|
|
48
|
+
) -> "QDSVBridgeClient":
|
|
49
|
+
return cls(api_url=api_url, api_key=api_key, timeout=timeout, license_key=license_key)
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def _normalize_api_url(value: str) -> str:
|
|
53
|
+
clean = str(value or "").strip().rstrip("/")
|
|
54
|
+
if not clean:
|
|
55
|
+
return DEFAULT_API_URL
|
|
56
|
+
return clean if clean.lower().endswith("/api") else f"{clean}/api"
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def _looks_like_private_node(api_url: str) -> bool:
|
|
60
|
+
clean = str(api_url or "").lower()
|
|
61
|
+
return "localhost" in clean or "127.0.0.1" in clean or "qintent-local.qdsv.cloud" in clean or "qruba.site" in clean
|
|
62
|
+
|
|
63
|
+
def _headers(self) -> dict[str, str]:
|
|
64
|
+
headers = {"Content-Type": "application/json", "x-sdk-name": self.sdk_name}
|
|
65
|
+
if self.api_key:
|
|
66
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
67
|
+
if self.license_key:
|
|
68
|
+
headers["x-license-key"] = self.license_key
|
|
69
|
+
return headers
|
|
70
|
+
|
|
71
|
+
def _request(self, method: str, path: str, *, json: Mapping[str, Any] | None = None) -> dict[str, Any]:
|
|
72
|
+
url = f"{self.api_url}{path}"
|
|
73
|
+
kwargs: dict[str, Any] = {"headers": self._headers(), "timeout": self.timeout}
|
|
74
|
+
if json is not None:
|
|
75
|
+
kwargs["json"] = dict(json)
|
|
76
|
+
try:
|
|
77
|
+
response = requests.request(method, url, **kwargs)
|
|
78
|
+
except requests.RequestException as exc:
|
|
79
|
+
if self._private_node:
|
|
80
|
+
raise QDSVBridgeAPIError(PRIVATE_NODE_UNAVAILABLE_MESSAGE) from exc
|
|
81
|
+
raise QDSVBridgeAPIError(str(exc)) from exc
|
|
82
|
+
try:
|
|
83
|
+
payload = response.json()
|
|
84
|
+
except ValueError:
|
|
85
|
+
payload = {"status": "ERROR", "message": response.text}
|
|
86
|
+
if not response.ok:
|
|
87
|
+
raise QDSVBridgeHTTPError(response.status_code, payload)
|
|
88
|
+
if not isinstance(payload, dict):
|
|
89
|
+
raise QDSVBridgeAPIError(f"Unexpected API response type: {type(payload).__name__}")
|
|
90
|
+
return payload
|
|
91
|
+
|
|
92
|
+
def families(self) -> dict[str, Any]:
|
|
93
|
+
return self._request("GET", "/bridge/families")
|
|
94
|
+
|
|
95
|
+
def validate(self, spec: Mapping[str, Any]) -> dict[str, Any]:
|
|
96
|
+
return self._request("POST", "/bridge/validate", json={"spec": dict(spec)})
|
|
97
|
+
|
|
98
|
+
def compile(self, spec: Mapping[str, Any]) -> dict[str, Any]:
|
|
99
|
+
return self._request("POST", "/bridge/compile", json={"spec": dict(spec)})
|
|
100
|
+
|
|
101
|
+
def explain(self, spec: Mapping[str, Any]) -> dict[str, Any]:
|
|
102
|
+
return self._request("POST", "/bridge/explain", json={"spec": dict(spec)})
|
|
103
|
+
|
|
104
|
+
def export(self, spec: Mapping[str, Any]) -> dict[str, Any]:
|
|
105
|
+
return self._request("POST", "/bridge/export", json={"spec": dict(spec)})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class QDSVBridgeError(Exception):
|
|
7
|
+
"""Base exception for the QDSV Bridge SDK."""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class QDSVBridgeHTTPError(QDSVBridgeError):
|
|
11
|
+
"""Raised when the API returns an HTTP error response."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, status_code: int, payload: Any) -> None:
|
|
14
|
+
self.status_code = status_code
|
|
15
|
+
self.payload = payload
|
|
16
|
+
super().__init__(f"QDSV Bridge API HTTP {status_code}: {payload}")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class QDSVBridgeAPIError(QDSVBridgeError):
|
|
20
|
+
"""Raised when a transport error prevents calling the API."""
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
from qdsv_bridge import QDSVBridgeClient
|
|
7
|
+
from qdsv_bridge.exceptions import QDSVBridgeAPIError, QDSVBridgeHTTPError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_normalizes_api_url() -> None:
|
|
11
|
+
assert QDSVBridgeClient("https://api.qdsv.cloud").api_url == "https://api.qdsv.cloud/api"
|
|
12
|
+
assert QDSVBridgeClient("https://api.qdsv.cloud/api").api_url == "https://api.qdsv.cloud/api"
|
|
13
|
+
assert QDSVBridgeClient.local().api_url == "http://localhost:18080/api"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_families_get_has_no_json_body(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
17
|
+
calls = {}
|
|
18
|
+
|
|
19
|
+
class FakeResponse:
|
|
20
|
+
ok = True
|
|
21
|
+
status_code = 200
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def json():
|
|
25
|
+
return {"status": "SUCCESS", "families": {}}
|
|
26
|
+
|
|
27
|
+
def fake_request(method, url, **kwargs):
|
|
28
|
+
calls["method"] = method
|
|
29
|
+
calls["url"] = url
|
|
30
|
+
calls["kwargs"] = kwargs
|
|
31
|
+
return FakeResponse()
|
|
32
|
+
|
|
33
|
+
monkeypatch.setattr("qdsv_bridge.client.requests.request", fake_request)
|
|
34
|
+
result = QDSVBridgeClient().families()
|
|
35
|
+
|
|
36
|
+
assert result["status"] == "SUCCESS"
|
|
37
|
+
assert calls["method"] == "GET"
|
|
38
|
+
assert calls["url"].endswith("/bridge/families")
|
|
39
|
+
assert "json" not in calls["kwargs"]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_export_posts_spec(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
43
|
+
calls = {}
|
|
44
|
+
|
|
45
|
+
class FakeResponse:
|
|
46
|
+
ok = True
|
|
47
|
+
status_code = 200
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def json():
|
|
51
|
+
return {"status": "SUCCESS", "artifact": {"format": "qasm3"}}
|
|
52
|
+
|
|
53
|
+
def fake_request(method, url, **kwargs):
|
|
54
|
+
calls["method"] = method
|
|
55
|
+
calls["url"] = url
|
|
56
|
+
calls["kwargs"] = kwargs
|
|
57
|
+
return FakeResponse()
|
|
58
|
+
|
|
59
|
+
monkeypatch.setattr("qdsv_bridge.client.requests.request", fake_request)
|
|
60
|
+
result = QDSVBridgeClient().export({"family": "semantic_signal_classification"})
|
|
61
|
+
|
|
62
|
+
assert result["artifact"]["format"] == "qasm3"
|
|
63
|
+
assert calls["method"] == "POST"
|
|
64
|
+
assert calls["url"].endswith("/bridge/export")
|
|
65
|
+
assert calls["kwargs"]["json"]["spec"]["family"] == "semantic_signal_classification"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_private_node_transport_error_is_user_friendly(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
69
|
+
def fake_request(method, url, **kwargs):
|
|
70
|
+
raise requests.ConnectionError("connection refused")
|
|
71
|
+
|
|
72
|
+
monkeypatch.setattr("qdsv_bridge.client.requests.request", fake_request)
|
|
73
|
+
with pytest.raises(QDSVBridgeAPIError) as exc:
|
|
74
|
+
QDSVBridgeClient.local().families()
|
|
75
|
+
|
|
76
|
+
assert "Private QDSV node temporarily unavailable" in str(exc.value)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_http_error_is_not_hidden(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
80
|
+
class FakeResponse:
|
|
81
|
+
ok = False
|
|
82
|
+
status_code = 400
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def json():
|
|
86
|
+
return {"detail": {"error_code": "E_BRIDGE_UNSUPPORTED_FAMILY"}}
|
|
87
|
+
|
|
88
|
+
def fake_request(method, url, **kwargs):
|
|
89
|
+
return FakeResponse()
|
|
90
|
+
|
|
91
|
+
monkeypatch.setattr("qdsv_bridge.client.requests.request", fake_request)
|
|
92
|
+
with pytest.raises(QDSVBridgeHTTPError) as exc:
|
|
93
|
+
QDSVBridgeClient.local().families()
|
|
94
|
+
|
|
95
|
+
assert exc.value.status_code == 400
|
|
96
|
+
assert exc.value.payload["detail"]["error_code"] == "E_BRIDGE_UNSUPPORTED_FAMILY"
|