iqm-station-control-client 11.2.1__py3-none-any.whl → 12.0.0__py3-none-any.whl
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.
- iqm/station_control/client/authentication.py +239 -0
- iqm/station_control/client/iqm_server/error.py +0 -30
- iqm/station_control/client/iqm_server/grpc_utils.py +0 -156
- iqm/station_control/client/iqm_server/iqm_server_client.py +0 -503
- iqm/station_control/client/list_models.py +16 -11
- iqm/station_control/client/qon.py +1 -1
- iqm/station_control/client/serializers/run_serializers.py +5 -4
- iqm/station_control/client/serializers/struct_serializer.py +1 -1
- iqm/station_control/client/station_control.py +140 -154
- iqm/station_control/client/utils.py +4 -42
- iqm/station_control/interface/models/__init__.py +21 -2
- iqm/station_control/interface/models/circuit.py +348 -0
- iqm/station_control/interface/models/dynamic_quantum_architecture.py +61 -3
- iqm/station_control/interface/models/jobs.py +42 -13
- iqm/station_control/interface/models/observation_set.py +28 -4
- iqm/station_control/interface/models/run.py +8 -8
- iqm/station_control/interface/models/sweep.py +7 -1
- iqm/station_control/interface/models/type_aliases.py +1 -2
- iqm/station_control/interface/station_control.py +1 -1
- {iqm_station_control_client-11.2.1.dist-info → iqm_station_control_client-12.0.0.dist-info}/METADATA +35 -36
- iqm_station_control_client-12.0.0.dist-info/RECORD +42 -0
- .DS_Store +0 -0
- iqm/.DS_Store +0 -0
- iqm/station_control/.DS_Store +0 -0
- iqm/station_control/client/.DS_Store +0 -0
- iqm/station_control/client/iqm_server/.DS_Store +0 -0
- iqm/station_control/client/iqm_server/__init__.py +0 -14
- iqm/station_control/client/iqm_server/proto/__init__.py +0 -43
- iqm/station_control/client/iqm_server/proto/calibration_pb2.py +0 -48
- iqm/station_control/client/iqm_server/proto/calibration_pb2.pyi +0 -45
- iqm/station_control/client/iqm_server/proto/calibration_pb2_grpc.py +0 -152
- iqm/station_control/client/iqm_server/proto/common_pb2.py +0 -43
- iqm/station_control/client/iqm_server/proto/common_pb2.pyi +0 -32
- iqm/station_control/client/iqm_server/proto/common_pb2_grpc.py +0 -17
- iqm/station_control/client/iqm_server/proto/job_pb2.py +0 -57
- iqm/station_control/client/iqm_server/proto/job_pb2.pyi +0 -107
- iqm/station_control/client/iqm_server/proto/job_pb2_grpc.py +0 -436
- iqm/station_control/client/iqm_server/proto/qc_pb2.py +0 -51
- iqm/station_control/client/iqm_server/proto/qc_pb2.pyi +0 -57
- iqm/station_control/client/iqm_server/proto/qc_pb2_grpc.py +0 -163
- iqm/station_control/client/iqm_server/proto/uuid_pb2.py +0 -39
- iqm/station_control/client/iqm_server/proto/uuid_pb2.pyi +0 -26
- iqm/station_control/client/iqm_server/proto/uuid_pb2_grpc.py +0 -17
- iqm/station_control/client/iqm_server/testing/__init__.py +0 -13
- iqm/station_control/client/iqm_server/testing/iqm_server_mock.py +0 -102
- iqm_station_control_client-11.2.1-py3-none-any.whl +0 -0
- iqm_station_control_client-11.2.1.dist-info/RECORD +0 -65
- {iqm_station_control_client-11.2.1.dist-info → iqm_station_control_client-12.0.0.dist-info}/LICENSE.txt +0 -0
- {iqm_station_control_client-11.2.1.dist-info → iqm_station_control_client-12.0.0.dist-info}/WHEEL +0 -0
- {iqm_station_control_client-11.2.1.dist-info → iqm_station_control_client-12.0.0.dist-info}/top_level.txt +0 -0
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
"""Client implementation for
|
|
14
|
+
"""Client implementation for Station Control service REST API."""
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
@@ -40,6 +40,7 @@ from exa.common.errors.station_control_errors import (
|
|
|
40
40
|
map_from_status_code_to_error,
|
|
41
41
|
)
|
|
42
42
|
from exa.common.qcm_data.qcm_data_client import QCMDataClient
|
|
43
|
+
from iqm.station_control.client.authentication import TokenManager
|
|
43
44
|
from iqm.station_control.client.list_models import (
|
|
44
45
|
DutFieldDataList,
|
|
45
46
|
DutList,
|
|
@@ -77,6 +78,7 @@ from iqm.station_control.interface.models import (
|
|
|
77
78
|
ObservationSetDefinition,
|
|
78
79
|
ObservationSetUpdate,
|
|
79
80
|
ObservationUpdate,
|
|
81
|
+
ProgressCallback,
|
|
80
82
|
QualityMetrics,
|
|
81
83
|
RunData,
|
|
82
84
|
RunDefinition,
|
|
@@ -87,12 +89,11 @@ from iqm.station_control.interface.models import (
|
|
|
87
89
|
SequenceResultDefinition,
|
|
88
90
|
SoftwareVersionSet,
|
|
89
91
|
StaticQuantumArchitecture,
|
|
90
|
-
|
|
92
|
+
StrUUID,
|
|
91
93
|
SweepData,
|
|
92
94
|
SweepDefinition,
|
|
93
95
|
SweepResults,
|
|
94
96
|
)
|
|
95
|
-
from iqm.station_control.interface.models.type_aliases import StrUUID
|
|
96
97
|
from iqm.station_control.interface.pydantic_base import PydanticBase
|
|
97
98
|
from iqm.station_control.interface.station_control import StationControlInterface
|
|
98
99
|
|
|
@@ -100,16 +101,19 @@ logger = logging.getLogger(__name__)
|
|
|
100
101
|
TypePydanticBase = TypeVar("TypePydanticBase", bound=PydanticBase)
|
|
101
102
|
|
|
102
103
|
|
|
103
|
-
class
|
|
104
|
-
"""
|
|
104
|
+
class StationControlClient(StationControlInterface):
|
|
105
|
+
"""Client implementation for station control service REST API.
|
|
105
106
|
|
|
106
107
|
Args:
|
|
107
|
-
root_url: Remote
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
root_url: Remote station control service URL.
|
|
109
|
+
token: Long-lived authentication token in plain text format.
|
|
110
|
+
If ``token`` is given no other user authentication parameters should be given.
|
|
111
|
+
tokens_file: Path to a tokens file used for authentication.
|
|
112
|
+
If ``tokens_file`` is given no other user authentication parameters should be given.
|
|
113
|
+
get_token_callback: Callback function that returns an Authorization header containing a bearer token.
|
|
114
|
+
If ``get_token_callback`` is given no other user authentication parameters should be given.
|
|
110
115
|
client_signature: String that is added to the User-Agent header of requests
|
|
111
116
|
sent to the server.
|
|
112
|
-
enable_opentelemetry: Iff True, enable Jaeger/OpenTelemetry tracing.
|
|
113
117
|
|
|
114
118
|
"""
|
|
115
119
|
|
|
@@ -117,144 +121,18 @@ class _StationControlClientBase(StationControlInterface):
|
|
|
117
121
|
self,
|
|
118
122
|
root_url: str,
|
|
119
123
|
*,
|
|
124
|
+
token: str | None = None,
|
|
125
|
+
tokens_file: str | None = None,
|
|
120
126
|
get_token_callback: Callable[[], str] | None = None,
|
|
121
127
|
client_signature: str | None = None,
|
|
122
|
-
enable_opentelemetry: bool = False,
|
|
123
128
|
):
|
|
124
129
|
self.root_url = root_url
|
|
125
|
-
|
|
126
|
-
self.
|
|
127
|
-
self.
|
|
128
|
-
|
|
129
|
-
@classmethod
|
|
130
|
-
def _create_signature(cls, client_signature: str | None) -> str:
|
|
131
|
-
signature = f"{platform.platform(terse=True)}"
|
|
132
|
-
signature += f", python {platform.python_version()}"
|
|
133
|
-
dist_pkg_name = "iqm-station-control-client"
|
|
134
|
-
signature += f", {cls.__name__} {dist_pkg_name} {version(dist_pkg_name)}"
|
|
135
|
-
if client_signature:
|
|
136
|
-
signature += f", {client_signature}"
|
|
137
|
-
return signature
|
|
138
|
-
|
|
139
|
-
def _send_request(
|
|
140
|
-
self,
|
|
141
|
-
http_method: Callable[..., requests.Response],
|
|
142
|
-
url_path: str,
|
|
143
|
-
*,
|
|
144
|
-
headers: dict[str, str] | None = None,
|
|
145
|
-
params: dict[str, Any] | None = None,
|
|
146
|
-
json_str: str | None = None,
|
|
147
|
-
octets: bytes | None = None,
|
|
148
|
-
timeout: int = 600,
|
|
149
|
-
) -> requests.Response:
|
|
150
|
-
"""Send an HTTP request.
|
|
151
|
-
|
|
152
|
-
Parameters ``json_str`` and ``octets`` are mutually exclusive.
|
|
153
|
-
The first non-None argument (in this order) will be used to construct the body of the request.
|
|
154
|
-
|
|
155
|
-
Args:
|
|
156
|
-
http_method: HTTP method to use for the request, any of requests.[post|get|put|head|delete|patch|options].
|
|
157
|
-
url_path: URL for the request.
|
|
158
|
-
headers: Additional HTTP headers for the request. Some may be overridden.
|
|
159
|
-
params: HTTP query parameters to store in the query string of the request URL.
|
|
160
|
-
json_str: JSON string to store in the body, may contain arbitrary Unicode characters.
|
|
161
|
-
octets: Pre-serialized binary data to store in the body.
|
|
162
|
-
timeout: Timeout for the request in seconds.
|
|
163
|
-
|
|
164
|
-
Returns:
|
|
165
|
-
Response to the request.
|
|
166
|
-
|
|
167
|
-
Raises:
|
|
168
|
-
StationControlError: Request was not successful.
|
|
169
|
-
|
|
170
|
-
"""
|
|
171
|
-
# Will raise an error if respectively an error response code is returned.
|
|
172
|
-
# http_method should be any of requests.[post|get|put|head|delete|patch|options]
|
|
173
|
-
|
|
174
|
-
request_kwargs = self._build_request_kwargs(
|
|
175
|
-
headers=headers or {}, params=params or {}, json_str=json_str, octets=octets, timeout=timeout
|
|
176
|
-
)
|
|
177
|
-
url = f"{self.root_url}/{url_path}"
|
|
178
|
-
# TODO SW-1387: Use v1 API
|
|
179
|
-
# url = f"{self.root_url}/{self.version}/{url_path}"
|
|
180
|
-
response = http_method(url, **request_kwargs)
|
|
181
|
-
if not response.ok:
|
|
182
|
-
try:
|
|
183
|
-
response_json = response.json()
|
|
184
|
-
error_message = response_json.get("message")
|
|
185
|
-
except (json.JSONDecodeError, KeyError):
|
|
186
|
-
error_message = response.text
|
|
187
|
-
|
|
188
|
-
try:
|
|
189
|
-
error_class = map_from_status_code_to_error(response.status_code) # type: ignore[arg-type]
|
|
190
|
-
except KeyError:
|
|
191
|
-
raise RuntimeError(f"Unexpected response status code {response.status_code}: {error_message}")
|
|
192
|
-
|
|
193
|
-
raise error_class(error_message)
|
|
194
|
-
return response
|
|
195
|
-
|
|
196
|
-
def _build_request_kwargs(
|
|
197
|
-
self,
|
|
198
|
-
*,
|
|
199
|
-
headers: dict[str, str],
|
|
200
|
-
params: dict[str, Any],
|
|
201
|
-
json_str: str | None = None,
|
|
202
|
-
octets: bytes | None = None,
|
|
203
|
-
timeout: int,
|
|
204
|
-
) -> dict[str, Any]:
|
|
205
|
-
"""Prepare the keyword arguments for an HTTP request."""
|
|
206
|
-
# add default headers
|
|
207
|
-
headers["User-Agent"] = self._signature
|
|
208
|
-
|
|
209
|
-
# json_str and octets are mutually exclusive
|
|
210
|
-
data: bytes | None = None
|
|
211
|
-
if json_str is not None:
|
|
212
|
-
# Must be able to handle JSON strings with arbitrary unicode characters, so we use an explicit
|
|
213
|
-
# encoding into bytes, and set the headers so the recipient can decode the request body correctly.
|
|
214
|
-
data = json_str.encode("utf-8")
|
|
215
|
-
headers["Content-Type"] = "application/json; charset=UTF-8"
|
|
216
|
-
elif octets is not None:
|
|
217
|
-
data = octets
|
|
218
|
-
headers["Content-Type"] = "application/protobuf"
|
|
219
|
-
|
|
220
|
-
if self._enable_opentelemetry:
|
|
221
|
-
parent_span_context = trace.set_span_in_context(trace.get_current_span())
|
|
222
|
-
propagate.inject(carrier=headers, context=parent_span_context)
|
|
223
|
-
|
|
224
|
-
# If token callback exists, use it to retrieve the token and add it to the headers
|
|
225
|
-
if self._get_token_callback:
|
|
226
|
-
headers["Authorization"] = self._get_token_callback()
|
|
227
|
-
|
|
228
|
-
kwargs = {
|
|
229
|
-
"headers": headers,
|
|
230
|
-
"params": params,
|
|
231
|
-
"data": data,
|
|
232
|
-
"timeout": timeout,
|
|
233
|
-
}
|
|
234
|
-
return _remove_empty_values(kwargs)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
class StationControlClient(_StationControlClientBase):
|
|
238
|
-
"""Client implementation for station control service REST API.
|
|
239
|
-
|
|
240
|
-
Args:
|
|
241
|
-
root_url: Remote station control service URL.
|
|
242
|
-
get_token_callback: A callback function that returns a token (str)
|
|
243
|
-
which will be passed in Authorization header in all requests.
|
|
244
|
-
client_signature: String that is added to the User-Agent header of requests
|
|
245
|
-
sent to the server.
|
|
246
|
-
|
|
247
|
-
"""
|
|
130
|
+
tm = TokenManager(token, tokens_file, get_token_callback, use_env_vars=False)
|
|
131
|
+
self._token_manager = tm
|
|
132
|
+
self._auth_header_callback = tm.get_auth_header_callback()
|
|
248
133
|
|
|
249
|
-
|
|
250
|
-
self
|
|
251
|
-
):
|
|
252
|
-
super().__init__(
|
|
253
|
-
root_url,
|
|
254
|
-
get_token_callback=get_token_callback,
|
|
255
|
-
client_signature=client_signature, # type: ignore[arg-type]
|
|
256
|
-
enable_opentelemetry=os.environ.get("JAEGER_OPENTELEMETRY_COLLECTOR_ENDPOINT", None) is not None,
|
|
257
|
-
)
|
|
134
|
+
self._signature = self._create_signature(client_signature)
|
|
135
|
+
self._enable_opentelemetry = os.environ.get("JAEGER_OPENTELEMETRY_COLLECTOR_ENDPOINT", None) is not None
|
|
258
136
|
# TODO SW-1387: Remove this when using v1 API, not needed
|
|
259
137
|
self._check_api_versions()
|
|
260
138
|
qcm_data_url = os.environ.get("CHIP_DESIGN_RECORD_FALLBACK_URL", None)
|
|
@@ -262,7 +140,7 @@ class StationControlClient(_StationControlClientBase):
|
|
|
262
140
|
|
|
263
141
|
@property
|
|
264
142
|
def version(self) -> str:
|
|
265
|
-
"""
|
|
143
|
+
"""Version of the Station Control API this client is using."""
|
|
266
144
|
return "v1"
|
|
267
145
|
|
|
268
146
|
@cache
|
|
@@ -336,7 +214,7 @@ class StationControlClient(_StationControlClientBase):
|
|
|
336
214
|
def run(
|
|
337
215
|
self,
|
|
338
216
|
run_definition: RunDefinition,
|
|
339
|
-
update_progress_callback:
|
|
217
|
+
update_progress_callback: ProgressCallback | None = None,
|
|
340
218
|
wait_job_completion: bool = True,
|
|
341
219
|
) -> bool:
|
|
342
220
|
data = serialize_run_job_request(run_definition, queue_name="sweeps")
|
|
@@ -496,7 +374,7 @@ class StationControlClient(_StationControlClientBase):
|
|
|
496
374
|
def abort_job(self, job_id: StrUUID) -> None:
|
|
497
375
|
self._send_request(requests.post, f"jobs/{job_id}/abort")
|
|
498
376
|
|
|
499
|
-
def _wait_job_completion(self, job_id: str, update_progress_callback:
|
|
377
|
+
def _wait_job_completion(self, job_id: str, update_progress_callback: ProgressCallback | None) -> bool:
|
|
500
378
|
logger.info("Waiting for job ID: %s", job_id)
|
|
501
379
|
update_progress_callback = update_progress_callback or (lambda status: None)
|
|
502
380
|
try:
|
|
@@ -510,7 +388,7 @@ class StationControlClient(_StationControlClientBase):
|
|
|
510
388
|
return False
|
|
511
389
|
|
|
512
390
|
def _poll_job_status_until_execution_start(
|
|
513
|
-
self, job_id: str, update_progress_callback:
|
|
391
|
+
self, job_id: str, update_progress_callback: ProgressCallback
|
|
514
392
|
) -> JobExecutorStatus:
|
|
515
393
|
# Keep polling job status as long as it's PENDING, and update progress with `update_progress_callback`.
|
|
516
394
|
max_seen_position = 0
|
|
@@ -532,7 +410,7 @@ class StationControlClient(_StationControlClientBase):
|
|
|
532
410
|
def _poll_job_status_until_terminal(
|
|
533
411
|
self,
|
|
534
412
|
job_id: str,
|
|
535
|
-
update_progress_callback:
|
|
413
|
+
update_progress_callback: ProgressCallback,
|
|
536
414
|
) -> None:
|
|
537
415
|
# Keep polling job status until it finishes, and update progress with `update_progress_callback`.
|
|
538
416
|
while True:
|
|
@@ -566,7 +444,6 @@ class StationControlClient(_StationControlClientBase):
|
|
|
566
444
|
# using the \uXXXX syntax, BaseModel.model_dump_json() keeps them in the produced JSON str.
|
|
567
445
|
return model.model_dump_json()
|
|
568
446
|
|
|
569
|
-
# TODO SW-1387: Remove this when using v1 API, not needed
|
|
570
447
|
def _check_api_versions(self): # noqa: ANN202
|
|
571
448
|
client_api_version = self._get_client_api_version()
|
|
572
449
|
# Parse versions using standard packaging.version implementation.
|
|
@@ -575,12 +452,6 @@ class StationControlClient(_StationControlClientBase):
|
|
|
575
452
|
self.get_about()["software_versions"]["iqm-station-control-client"].replace(" (local editable)", "+local")
|
|
576
453
|
)
|
|
577
454
|
|
|
578
|
-
if client_api_version.major != server_api_version.major:
|
|
579
|
-
raise ValueError(
|
|
580
|
-
f"station-control-client version '{client_api_version}' is not compatible with the station control "
|
|
581
|
-
f"server, please use station-control-client version compatible with version '{server_api_version}'."
|
|
582
|
-
)
|
|
583
|
-
|
|
584
455
|
if client_api_version.local or server_api_version.local:
|
|
585
456
|
logger.warning(
|
|
586
457
|
"Client ('%s') and/or server ('%s') is using a local version of the station-control-client. "
|
|
@@ -588,6 +459,14 @@ class StationControlClient(_StationControlClientBase):
|
|
|
588
459
|
client_api_version,
|
|
589
460
|
server_api_version,
|
|
590
461
|
)
|
|
462
|
+
elif client_api_version.major != server_api_version.major:
|
|
463
|
+
logger.warning(
|
|
464
|
+
"Client and server compatibility cannot be guaranteed. "
|
|
465
|
+
"The installed station-control-client package version '%s' has a different major to "
|
|
466
|
+
"station-control server version '%s'.",
|
|
467
|
+
client_api_version,
|
|
468
|
+
server_api_version,
|
|
469
|
+
)
|
|
591
470
|
elif client_api_version.minor > server_api_version.minor:
|
|
592
471
|
logger.warning(
|
|
593
472
|
"station-control-client version '%s' is newer minor version than '%s' used by the station control "
|
|
@@ -630,6 +509,113 @@ class StationControlClient(_StationControlClientBase):
|
|
|
630
509
|
return model.root
|
|
631
510
|
return model
|
|
632
511
|
|
|
512
|
+
@classmethod
|
|
513
|
+
def _create_signature(cls, client_signature: str | None) -> str:
|
|
514
|
+
signature = f"{platform.platform(terse=True)}"
|
|
515
|
+
signature += f", python {platform.python_version()}"
|
|
516
|
+
dist_pkg_name = "iqm-station-control-client"
|
|
517
|
+
signature += f", {cls.__name__} {dist_pkg_name} {version(dist_pkg_name)}"
|
|
518
|
+
if client_signature:
|
|
519
|
+
signature += f", {client_signature}"
|
|
520
|
+
return signature
|
|
521
|
+
|
|
522
|
+
def _send_request(
|
|
523
|
+
self,
|
|
524
|
+
http_method: Callable[..., requests.Response],
|
|
525
|
+
url_path: str,
|
|
526
|
+
*,
|
|
527
|
+
headers: dict[str, str] | None = None,
|
|
528
|
+
params: dict[str, Any] | None = None,
|
|
529
|
+
json_str: str | None = None,
|
|
530
|
+
octets: bytes | None = None,
|
|
531
|
+
timeout: int = 600,
|
|
532
|
+
) -> requests.Response:
|
|
533
|
+
"""Send an HTTP request.
|
|
534
|
+
|
|
535
|
+
Parameters ``json_str`` and ``octets`` are mutually exclusive.
|
|
536
|
+
The first non-None argument (in this order) will be used to construct the body of the request.
|
|
537
|
+
|
|
538
|
+
Args:
|
|
539
|
+
http_method: HTTP method to use for the request, any of requests.[post|get|put|head|delete|patch|options].
|
|
540
|
+
url_path: URL for the request.
|
|
541
|
+
headers: Additional HTTP headers for the request. Some may be overridden.
|
|
542
|
+
params: HTTP query parameters to store in the query string of the request URL.
|
|
543
|
+
json_str: JSON string to store in the body, may contain arbitrary Unicode characters.
|
|
544
|
+
octets: Pre-serialized binary data to store in the body.
|
|
545
|
+
timeout: Timeout for the request in seconds.
|
|
546
|
+
|
|
547
|
+
Returns:
|
|
548
|
+
Response to the request.
|
|
549
|
+
|
|
550
|
+
Raises:
|
|
551
|
+
StationControlError: Request was not successful.
|
|
552
|
+
|
|
553
|
+
"""
|
|
554
|
+
# Will raise an error if respectively an error response code is returned.
|
|
555
|
+
# http_method should be any of requests.[post|get|put|head|delete|patch|options]
|
|
556
|
+
|
|
557
|
+
request_kwargs = self._build_request_kwargs(
|
|
558
|
+
headers=headers or {}, params=params or {}, json_str=json_str, octets=octets, timeout=timeout
|
|
559
|
+
)
|
|
560
|
+
url = f"{self.root_url}/{url_path}"
|
|
561
|
+
# TODO SW-1387: Use v1 API
|
|
562
|
+
# url = f"{self.root_url}/{self.version}/{url_path}"
|
|
563
|
+
response = http_method(url, **request_kwargs)
|
|
564
|
+
if not response.ok:
|
|
565
|
+
try:
|
|
566
|
+
response_json = response.json()
|
|
567
|
+
error_message = response_json.get("message")
|
|
568
|
+
except (json.JSONDecodeError, KeyError):
|
|
569
|
+
error_message = response.text
|
|
570
|
+
|
|
571
|
+
try:
|
|
572
|
+
error_class = map_from_status_code_to_error(response.status_code) # type: ignore[arg-type]
|
|
573
|
+
except KeyError:
|
|
574
|
+
raise RuntimeError(f"Unexpected response status code {response.status_code}: {error_message}")
|
|
575
|
+
|
|
576
|
+
raise error_class(error_message)
|
|
577
|
+
return response
|
|
578
|
+
|
|
579
|
+
def _build_request_kwargs(
|
|
580
|
+
self,
|
|
581
|
+
*,
|
|
582
|
+
headers: dict[str, str],
|
|
583
|
+
params: dict[str, Any],
|
|
584
|
+
json_str: str | None = None,
|
|
585
|
+
octets: bytes | None = None,
|
|
586
|
+
timeout: int,
|
|
587
|
+
) -> dict[str, Any]:
|
|
588
|
+
"""Prepare the keyword arguments for an HTTP request."""
|
|
589
|
+
# add default headers
|
|
590
|
+
headers["User-Agent"] = self._signature
|
|
591
|
+
|
|
592
|
+
# json_str and octets are mutually exclusive
|
|
593
|
+
data: bytes | None = None
|
|
594
|
+
if json_str is not None:
|
|
595
|
+
# Must be able to handle JSON strings with arbitrary unicode characters, so we use an explicit
|
|
596
|
+
# encoding into bytes, and set the headers so the recipient can decode the request body correctly.
|
|
597
|
+
data = json_str.encode("utf-8")
|
|
598
|
+
headers["Content-Type"] = "application/json; charset=UTF-8"
|
|
599
|
+
elif octets is not None:
|
|
600
|
+
data = octets
|
|
601
|
+
headers["Content-Type"] = "application/protobuf"
|
|
602
|
+
|
|
603
|
+
if self._enable_opentelemetry:
|
|
604
|
+
parent_span_context = trace.set_span_in_context(trace.get_current_span())
|
|
605
|
+
propagate.inject(carrier=headers, context=parent_span_context)
|
|
606
|
+
|
|
607
|
+
# If auth header callback exists, use it to add the header
|
|
608
|
+
if self._auth_header_callback:
|
|
609
|
+
headers["Authorization"] = self._auth_header_callback()
|
|
610
|
+
|
|
611
|
+
kwargs = {
|
|
612
|
+
"headers": headers,
|
|
613
|
+
"params": params,
|
|
614
|
+
"data": data,
|
|
615
|
+
"timeout": timeout,
|
|
616
|
+
}
|
|
617
|
+
return _remove_empty_values(kwargs)
|
|
618
|
+
|
|
633
619
|
|
|
634
620
|
def _remove_empty_values(kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
635
621
|
"""Return a copy of the given dict without values that are None or {}."""
|
|
@@ -13,22 +13,17 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
"""Utility functions for IQM Station Control Client."""
|
|
15
15
|
|
|
16
|
-
from collections.abc import Callable
|
|
17
|
-
|
|
18
|
-
import requests
|
|
19
16
|
from tqdm.auto import tqdm
|
|
20
17
|
|
|
21
|
-
from iqm.station_control.
|
|
22
|
-
from iqm.station_control.
|
|
23
|
-
from iqm.station_control.interface.models import Statuses
|
|
24
|
-
from iqm.station_control.interface.station_control import StationControlInterface
|
|
18
|
+
from iqm.station_control.interface.models import ProgressCallback
|
|
19
|
+
from iqm.station_control.interface.models.jobs import _Progress
|
|
25
20
|
|
|
26
21
|
|
|
27
|
-
def get_progress_bar_callback() ->
|
|
22
|
+
def get_progress_bar_callback() -> ProgressCallback:
|
|
28
23
|
"""Returns a callback function that creates or updates existing progressbars when called."""
|
|
29
24
|
progress_bars = {}
|
|
30
25
|
|
|
31
|
-
def _create_and_update_progress_bars(statuses:
|
|
26
|
+
def _create_and_update_progress_bars(statuses: list[_Progress]) -> None:
|
|
32
27
|
for label, value, total in statuses:
|
|
33
28
|
if label not in progress_bars:
|
|
34
29
|
progress_bars[label] = tqdm(total=total, desc=label, leave=True)
|
|
@@ -36,36 +31,3 @@ def get_progress_bar_callback() -> Callable[[Statuses], None]:
|
|
|
36
31
|
progress_bars[label].refresh()
|
|
37
32
|
|
|
38
33
|
return _create_and_update_progress_bars
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def init_station_control(
|
|
42
|
-
root_url: str, get_token_callback: Callable[[], str] | None = None, **kwargs
|
|
43
|
-
) -> StationControlInterface:
|
|
44
|
-
"""Initialize a new station control instance connected to the given remote.
|
|
45
|
-
|
|
46
|
-
Client implementation is selected automatically based on the remote station: if the remote station
|
|
47
|
-
is running the IQM Server software stack, then the IQM Server client implementation (with a limited
|
|
48
|
-
feature set) is chosen. If the remote station is running the SC software stack, then the Station
|
|
49
|
-
Control client implementation (with the full feature set) is chosen.
|
|
50
|
-
|
|
51
|
-
Args:
|
|
52
|
-
root_url: Remote station control service URL. For IQM Server remotes, this is the "Quantum Computer URL"
|
|
53
|
-
value from the web dashboard.
|
|
54
|
-
get_token_callback: A callback function that returns a token (str) which will be passed in Authorization
|
|
55
|
-
header in all requests.
|
|
56
|
-
|
|
57
|
-
"""
|
|
58
|
-
try:
|
|
59
|
-
headers = {"Authorization": get_token_callback()} if get_token_callback else {}
|
|
60
|
-
response = requests.get(f"{root_url}/about", headers=headers)
|
|
61
|
-
response.raise_for_status()
|
|
62
|
-
about = response.json()
|
|
63
|
-
if isinstance(about, dict) and about.get("iqm_server") is True:
|
|
64
|
-
# If about information has iqm_server flag, it means that we're communicating
|
|
65
|
-
# with IQM server instead of direct Station Control service, hence we need to
|
|
66
|
-
# use the specialized client
|
|
67
|
-
return IqmServerClient(root_url, get_token_callback=get_token_callback, **kwargs)
|
|
68
|
-
# Using direct station control by default
|
|
69
|
-
return StationControlClient(root_url=root_url, get_token_callback=get_token_callback, **kwargs)
|
|
70
|
-
except Exception as e:
|
|
71
|
-
raise RuntimeError("Failed to initialize the client.") from e
|
|
@@ -13,6 +13,22 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
"""Station control interface models."""
|
|
15
15
|
|
|
16
|
+
from iqm.station_control.interface.models.circuit import (
|
|
17
|
+
CircuitBatch,
|
|
18
|
+
CircuitMeasurementCounts,
|
|
19
|
+
CircuitMeasurementCountsBatch,
|
|
20
|
+
CircuitMeasurementResults,
|
|
21
|
+
CircuitMeasurementResultsBatch,
|
|
22
|
+
DDMode,
|
|
23
|
+
DDStrategy,
|
|
24
|
+
HeraldingMode,
|
|
25
|
+
MoveGateFrameTrackingMode,
|
|
26
|
+
MoveGateValidationMode,
|
|
27
|
+
PRXSequence,
|
|
28
|
+
QIRCode,
|
|
29
|
+
QubitMapping,
|
|
30
|
+
RunRequest,
|
|
31
|
+
)
|
|
16
32
|
from iqm.station_control.interface.models.dut import DutData, DutFieldData
|
|
17
33
|
from iqm.station_control.interface.models.dynamic_quantum_architecture import (
|
|
18
34
|
DynamicQuantumArchitecture,
|
|
@@ -22,8 +38,11 @@ from iqm.station_control.interface.models.dynamic_quantum_architecture import (
|
|
|
22
38
|
)
|
|
23
39
|
from iqm.station_control.interface.models.jobs import (
|
|
24
40
|
JobData,
|
|
41
|
+
JobError,
|
|
25
42
|
JobExecutorStatus,
|
|
26
43
|
JobResult,
|
|
44
|
+
ProgressCallback,
|
|
45
|
+
Statuses,
|
|
27
46
|
TimelineEntry,
|
|
28
47
|
)
|
|
29
48
|
from iqm.station_control.interface.models.observation import (
|
|
@@ -49,11 +68,11 @@ from iqm.station_control.interface.models.sequence import (
|
|
|
49
68
|
SequenceResultDefinition,
|
|
50
69
|
)
|
|
51
70
|
from iqm.station_control.interface.models.static_quantum_architecture import StaticQuantumArchitecture
|
|
52
|
-
from iqm.station_control.interface.models.sweep import SweepData, SweepDefinition
|
|
71
|
+
from iqm.station_control.interface.models.sweep import SweepBase, SweepData, SweepDefinition
|
|
53
72
|
from iqm.station_control.interface.models.type_aliases import (
|
|
54
73
|
DutType,
|
|
55
74
|
GetObservationsMode,
|
|
56
75
|
SoftwareVersionSet,
|
|
57
|
-
|
|
76
|
+
StrUUID,
|
|
58
77
|
SweepResults,
|
|
59
78
|
)
|