iqm-station-control-client 9.0.0__tar.gz → 9.2.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.
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/CHANGELOG.rst +16 -0
- {iqm_station_control_client-9.0.0/src/iqm_station_control_client.egg-info → iqm_station_control_client-9.2.0}/PKG-INFO +1 -1
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/iqm_server/iqm_server_client.py +4 -102
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/iqm_server/proto/uuid_pb2.pyi +1 -1
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/list_models.py +9 -9
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/station_control.py +146 -107
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0/src/iqm_station_control_client.egg-info}/PKG-INFO +1 -1
- iqm_station_control_client-9.2.0/version.txt +1 -0
- iqm_station_control_client-9.0.0/version.txt +0 -1
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/LICENSE.txt +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/MANIFEST.in +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/README.rst +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/docs/API.rst +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/docs/Makefile +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/docs/_static/css/custom.css +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/docs/_static/images/favicon.ico +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/docs/_static/images/logo.png +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/docs/_templates/autosummary-class-template.rst +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/docs/_templates/autosummary-module-template.rst +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/docs/changelog.rst +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/docs/conf.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/docs/index.rst +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/docs/license.rst +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/pyproject.toml +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/requirements/base.in +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/requirements/base.txt +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/setup.cfg +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/setup.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/__init__.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/iqm_server/__init__.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/iqm_server/error.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/iqm_server/grpc_utils.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/iqm_server/proto/__init__.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/iqm_server/proto/calibration_pb2.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/iqm_server/proto/calibration_pb2.pyi +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/iqm_server/proto/calibration_pb2_grpc.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/iqm_server/proto/common_pb2.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/iqm_server/proto/common_pb2.pyi +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/iqm_server/proto/common_pb2_grpc.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/iqm_server/proto/job_pb2.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/iqm_server/proto/job_pb2.pyi +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/iqm_server/proto/job_pb2_grpc.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/iqm_server/proto/qc_pb2.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/iqm_server/proto/qc_pb2.pyi +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/iqm_server/proto/qc_pb2_grpc.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/iqm_server/proto/uuid_pb2.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/iqm_server/proto/uuid_pb2_grpc.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/iqm_server/testing/__init__.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/iqm_server/testing/iqm_server_mock.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/serializers/__init__.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/serializers/channel_property_serializer.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/serializers/datetime_serializers.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/serializers/playlist_serializers.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/serializers/run_serializers.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/serializers/setting_node_serializer.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/serializers/struct_serializer.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/serializers/sweep_serializers.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/serializers/task_serializers.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/client/utils.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/interface/__init__.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/interface/list_with_meta.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/interface/models/__init__.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/interface/models/dut.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/interface/models/dynamic_quantum_architecture.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/interface/models/jobs.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/interface/models/monitor.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/interface/models/observation.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/interface/models/observation_set.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/interface/models/run.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/interface/models/sequence.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/interface/models/static_quantum_architecture.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/interface/models/sweep.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/interface/models/type_aliases.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/interface/pydantic_base.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm/station_control/interface/station_control.py +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm_station_control_client.egg-info/SOURCES.txt +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm_station_control_client.egg-info/dependency_links.txt +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm_station_control_client.egg-info/requires.txt +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/src/iqm_station_control_client.egg-info/top_level.txt +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/tests/.pylintrc +0 -0
- {iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/tests/__init__.py +0 -0
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
Changelog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
+
Version 9.2.0 (2025-06-25)
|
|
6
|
+
==========================
|
|
7
|
+
|
|
8
|
+
Bug fixes
|
|
9
|
+
---------
|
|
10
|
+
|
|
11
|
+
- Fix failing type checks in mypy.
|
|
12
|
+
|
|
13
|
+
Version 9.1.0 (2025-06-24)
|
|
14
|
+
==========================
|
|
15
|
+
|
|
16
|
+
Bug fixes
|
|
17
|
+
---------
|
|
18
|
+
|
|
19
|
+
- Requests now always have User-Agent header.
|
|
20
|
+
|
|
5
21
|
Version 9.0.0 (2025-06-13)
|
|
6
22
|
==========================
|
|
7
23
|
|
|
@@ -16,11 +16,9 @@
|
|
|
16
16
|
from collections.abc import Callable, Iterable, Sequence
|
|
17
17
|
from contextlib import contextmanager
|
|
18
18
|
import dataclasses
|
|
19
|
-
from importlib.metadata import version
|
|
20
19
|
from io import BytesIO
|
|
21
20
|
import json
|
|
22
21
|
import logging
|
|
23
|
-
import platform
|
|
24
22
|
from time import sleep
|
|
25
23
|
from typing import Any, TypeVar, cast
|
|
26
24
|
import uuid
|
|
@@ -32,7 +30,6 @@ import requests
|
|
|
32
30
|
|
|
33
31
|
from exa.common.data.setting_node import SettingNode
|
|
34
32
|
from exa.common.data.value import ObservationValue, validate_value
|
|
35
|
-
from exa.common.errors.station_control_errors import map_from_status_code_to_error
|
|
36
33
|
from iqm.station_control.client.iqm_server import proto
|
|
37
34
|
from iqm.station_control.client.iqm_server.error import IqmServerError
|
|
38
35
|
from iqm.station_control.client.iqm_server.grpc_utils import (
|
|
@@ -49,6 +46,7 @@ from iqm.station_control.client.serializers import deserialize_sweep_results, se
|
|
|
49
46
|
from iqm.station_control.client.serializers.channel_property_serializer import unpack_channel_properties
|
|
50
47
|
from iqm.station_control.client.serializers.setting_node_serializer import deserialize_setting_node
|
|
51
48
|
from iqm.station_control.client.serializers.task_serializers import deserialize_sweep_job_request
|
|
49
|
+
from iqm.station_control.client.station_control import _StationControlClientBase
|
|
52
50
|
from iqm.station_control.interface.list_with_meta import ListWithMeta
|
|
53
51
|
from iqm.station_control.interface.models import (
|
|
54
52
|
DutData,
|
|
@@ -81,14 +79,13 @@ from iqm.station_control.interface.models import (
|
|
|
81
79
|
from iqm.station_control.interface.models.jobs import JobError
|
|
82
80
|
from iqm.station_control.interface.models.sweep import SweepBase
|
|
83
81
|
from iqm.station_control.interface.models.type_aliases import GetObservationsMode, SoftwareVersionSet, StrUUID
|
|
84
|
-
from iqm.station_control.interface.station_control import StationControlInterface
|
|
85
82
|
|
|
86
83
|
logger = logging.getLogger(__name__)
|
|
87
84
|
|
|
88
85
|
T = TypeVar("T")
|
|
89
86
|
|
|
90
87
|
|
|
91
|
-
class IqmServerClient(
|
|
88
|
+
class IqmServerClient(_StationControlClientBase):
|
|
92
89
|
def __init__(
|
|
93
90
|
self,
|
|
94
91
|
root_url: str,
|
|
@@ -97,12 +94,10 @@ class IqmServerClient(StationControlInterface):
|
|
|
97
94
|
client_signature: str | None = None,
|
|
98
95
|
grpc_channel: grpc.Channel | None = None,
|
|
99
96
|
):
|
|
100
|
-
|
|
97
|
+
super().__init__(root_url, get_token_callback=get_token_callback, client_signature=client_signature)
|
|
101
98
|
self._connection_params = parse_connection_params(root_url)
|
|
102
|
-
self._cached_resources = {}
|
|
99
|
+
self._cached_resources: dict[str, Any] = {}
|
|
103
100
|
self._latest_submitted_sweep = None
|
|
104
|
-
self._get_token_callback = get_token_callback
|
|
105
|
-
self._signature = self._create_signature(client_signature)
|
|
106
101
|
self._channel = grpc_channel or create_channel(self._connection_params, self._get_token_callback)
|
|
107
102
|
self._current_qc = resolve_current_qc(self._channel, self._connection_params.quantum_computer)
|
|
108
103
|
|
|
@@ -384,94 +379,6 @@ class IqmServerClient(StationControlInterface):
|
|
|
384
379
|
self._cached_resources[resource_name] = resource
|
|
385
380
|
return resource
|
|
386
381
|
|
|
387
|
-
@staticmethod
|
|
388
|
-
def _create_signature(client_signature: str) -> str:
|
|
389
|
-
signature = f"{platform.platform(terse=True)}"
|
|
390
|
-
signature += f", python {platform.python_version()}"
|
|
391
|
-
version_string = "iqm-station-control-client"
|
|
392
|
-
signature += f", IqmServerClient {version_string} {version(version_string)}"
|
|
393
|
-
if client_signature:
|
|
394
|
-
signature += f", {client_signature}"
|
|
395
|
-
return signature
|
|
396
|
-
|
|
397
|
-
def _send_request(
|
|
398
|
-
self,
|
|
399
|
-
http_method: Callable[..., requests.Response],
|
|
400
|
-
url_path: str,
|
|
401
|
-
*,
|
|
402
|
-
json_str: str | None = None,
|
|
403
|
-
octets: bytes | None = None,
|
|
404
|
-
params: dict[str, Any] | None = None,
|
|
405
|
-
headers: dict[str, str] | None = None,
|
|
406
|
-
timeout: int = 120,
|
|
407
|
-
) -> requests.Response:
|
|
408
|
-
"""Send an HTTP request.
|
|
409
|
-
|
|
410
|
-
Parameters ``json_str``, ``octets`` and ``params`` are mutually exclusive.
|
|
411
|
-
The first non-None argument (in this order) will be used to construct the body of the request.
|
|
412
|
-
|
|
413
|
-
Args:
|
|
414
|
-
http_method: HTTP method to use for the request, any of requests.[post|get|put|head|delete|patch|options].
|
|
415
|
-
url_path: URL for the request.
|
|
416
|
-
|
|
417
|
-
Returns:
|
|
418
|
-
Response to the request.
|
|
419
|
-
|
|
420
|
-
Raises:
|
|
421
|
-
StationControlError: Request was not successful.
|
|
422
|
-
|
|
423
|
-
"""
|
|
424
|
-
# Will raise an error if respectively an error response code is returned.
|
|
425
|
-
# http_method should be any of requests.[post|get|put|head|delete|patch|options]
|
|
426
|
-
|
|
427
|
-
request_kwargs = self._build_request_kwargs(
|
|
428
|
-
json_str=json_str, octets=octets, params=params or {}, headers=headers or {}, timeout=timeout
|
|
429
|
-
)
|
|
430
|
-
url = f"{self.root_url}/{url_path}"
|
|
431
|
-
response = http_method(url, **request_kwargs)
|
|
432
|
-
if not response.ok:
|
|
433
|
-
try:
|
|
434
|
-
response_json = response.json()
|
|
435
|
-
error_message = response_json["detail"]
|
|
436
|
-
except json.JSONDecodeError:
|
|
437
|
-
error_message = response.text
|
|
438
|
-
|
|
439
|
-
try:
|
|
440
|
-
error_class = map_from_status_code_to_error(response.status_code)
|
|
441
|
-
except KeyError:
|
|
442
|
-
raise RuntimeError(f"Unexpected response status code {response.status_code}: {error_message}")
|
|
443
|
-
raise error_class(error_message)
|
|
444
|
-
return response
|
|
445
|
-
|
|
446
|
-
def _build_request_kwargs(self, **kwargs):
|
|
447
|
-
kwargs.setdefault("headers", {"User-Agent": self._signature})
|
|
448
|
-
|
|
449
|
-
params = kwargs.get("params", {})
|
|
450
|
-
headers = kwargs["headers"]
|
|
451
|
-
|
|
452
|
-
if kwargs["json_str"] is not None:
|
|
453
|
-
# Must be able to handle JSON strings with arbitrary unicode characters, so we use an explicit
|
|
454
|
-
# encoding into bytes, and set the headers so the recipient can decode the request body correctly.
|
|
455
|
-
data = kwargs["json_str"].encode("utf-8")
|
|
456
|
-
headers["Content-Type"] = "application/json; charset=UTF-8"
|
|
457
|
-
elif kwargs["octets"] is not None:
|
|
458
|
-
data = kwargs["octets"]
|
|
459
|
-
headers["Content-Type"] = "application/octet-stream"
|
|
460
|
-
else:
|
|
461
|
-
data = None
|
|
462
|
-
|
|
463
|
-
# If token callback exists, use it to retrieve the token and add it to the headers
|
|
464
|
-
if self._get_token_callback:
|
|
465
|
-
headers["Authorization"] = self._get_token_callback()
|
|
466
|
-
|
|
467
|
-
kwargs = {
|
|
468
|
-
"params": params,
|
|
469
|
-
"data": data,
|
|
470
|
-
"headers": headers,
|
|
471
|
-
"timeout": kwargs["timeout"],
|
|
472
|
-
}
|
|
473
|
-
return _remove_empty_values(kwargs)
|
|
474
|
-
|
|
475
382
|
|
|
476
383
|
def resolve_current_qc(channel: grpc.Channel, alias: str) -> proto.QuantumComputerV1:
|
|
477
384
|
qcs = proto.QuantumComputersStub(channel)
|
|
@@ -560,8 +467,3 @@ def wrap_error(title: str):
|
|
|
560
467
|
raise extract_error(e, title) from e
|
|
561
468
|
except Exception as e:
|
|
562
469
|
raise IqmServerError(message=f"{title}: {e}", status_code=str(grpc.StatusCode.INTERNAL.name)) from e
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
def _remove_empty_values(kwargs):
|
|
566
|
-
# Remove None and {} values
|
|
567
|
-
return {key: value for key, value in kwargs.items() if value not in [None, {}]}
|
|
@@ -23,4 +23,4 @@ class Uuid(_message.Message):
|
|
|
23
23
|
STR_FIELD_NUMBER: _ClassVar[int]
|
|
24
24
|
raw: bytes
|
|
25
25
|
str: str
|
|
26
|
-
def __init__(self, raw: _Optional[bytes] = ..., str: _Optional[str] = ...) -> None:
|
|
26
|
+
def __init__(self, raw: _Optional[bytes] = ..., str: _Optional[str] = ...) -> None: ...# type: ignore[valid-type]
|
|
@@ -64,12 +64,12 @@ class ListModel(RootModel):
|
|
|
64
64
|
)
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
DutList: TypeAlias = ListModel[list[DutData]]
|
|
68
|
-
DutFieldDataList: TypeAlias = ListModel[list[DutFieldData]]
|
|
69
|
-
ObservationDataList: TypeAlias = ListModel[list[ObservationData]]
|
|
70
|
-
ObservationDefinitionList: TypeAlias = ListModel[list[ObservationDefinition]]
|
|
71
|
-
ObservationLiteList: TypeAlias = ListModel[list[ObservationLite]]
|
|
72
|
-
ObservationUpdateList: TypeAlias = ListModel[list[ObservationUpdate]]
|
|
73
|
-
ObservationSetDataList: TypeAlias = ListModel[list[ObservationSetData]]
|
|
74
|
-
SequenceMetadataDataList: TypeAlias = ListModel[list[SequenceMetadataData]]
|
|
75
|
-
RunLiteList: TypeAlias = ListModel[list[RunLite]]
|
|
67
|
+
DutList: TypeAlias = ListModel[list[DutData]] # type: ignore[type-arg]
|
|
68
|
+
DutFieldDataList: TypeAlias = ListModel[list[DutFieldData]] # type: ignore[type-arg]
|
|
69
|
+
ObservationDataList: TypeAlias = ListModel[list[ObservationData]] # type: ignore[type-arg]
|
|
70
|
+
ObservationDefinitionList: TypeAlias = ListModel[list[ObservationDefinition]] # type: ignore[type-arg]
|
|
71
|
+
ObservationLiteList: TypeAlias = ListModel[list[ObservationLite]] # type: ignore[type-arg]
|
|
72
|
+
ObservationUpdateList: TypeAlias = ListModel[list[ObservationUpdate]] # type: ignore[type-arg]
|
|
73
|
+
ObservationSetDataList: TypeAlias = ListModel[list[ObservationSetData]] # type: ignore[type-arg]
|
|
74
|
+
SequenceMetadataDataList: TypeAlias = ListModel[list[SequenceMetadataData]] # type: ignore[type-arg]
|
|
75
|
+
RunLiteList: TypeAlias = ListModel[list[RunLite]] # type: ignore[type-arg]
|
|
@@ -100,23 +100,160 @@ logger = logging.getLogger(__name__)
|
|
|
100
100
|
TypePydanticBase = TypeVar("TypePydanticBase", bound=PydanticBase)
|
|
101
101
|
|
|
102
102
|
|
|
103
|
-
class
|
|
103
|
+
class _StationControlClientBase(StationControlInterface):
|
|
104
|
+
"""Shared functionality for StationControlClient and IqmServerClient.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
root_url: Remote server URL.
|
|
108
|
+
get_token_callback: A callback function that returns a token
|
|
109
|
+
which will be passed in Authorization header in all requests.
|
|
110
|
+
client_signature: String that is added to the User-Agent header of requests
|
|
111
|
+
sent to the server.
|
|
112
|
+
enable_opentelemetry: Iff True, enable Jaeger/OpenTelemetry tracing.
|
|
113
|
+
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
def __init__(
|
|
117
|
+
self,
|
|
118
|
+
root_url: str,
|
|
119
|
+
*,
|
|
120
|
+
get_token_callback: Callable[[], str] | None = None,
|
|
121
|
+
client_signature: str | None = None,
|
|
122
|
+
enable_opentelemetry: bool = False,
|
|
123
|
+
):
|
|
124
|
+
self.root_url = root_url
|
|
125
|
+
self._get_token_callback = get_token_callback
|
|
126
|
+
self._signature = self._create_signature(client_signature)
|
|
127
|
+
self._enable_opentelemetry = enable_opentelemetry
|
|
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 = 120,
|
|
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
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Response to the request.
|
|
165
|
+
|
|
166
|
+
Raises:
|
|
167
|
+
StationControlError: Request was not successful.
|
|
168
|
+
|
|
169
|
+
"""
|
|
170
|
+
# Will raise an error if respectively an error response code is returned.
|
|
171
|
+
# http_method should be any of requests.[post|get|put|head|delete|patch|options]
|
|
172
|
+
|
|
173
|
+
request_kwargs = self._build_request_kwargs(
|
|
174
|
+
headers=headers or {}, params=params or {}, json_str=json_str, octets=octets, timeout=timeout
|
|
175
|
+
)
|
|
176
|
+
url = f"{self.root_url}/{url_path}"
|
|
177
|
+
# TODO SW-1387: Use v1 API
|
|
178
|
+
# url = f"{self.root_url}/{self.version}/{url_path}"
|
|
179
|
+
response = http_method(url, **request_kwargs)
|
|
180
|
+
if not response.ok:
|
|
181
|
+
try:
|
|
182
|
+
response_json = response.json()
|
|
183
|
+
error_message = response_json["detail"]
|
|
184
|
+
except json.JSONDecodeError:
|
|
185
|
+
error_message = response.text
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
error_class = map_from_status_code_to_error(response.status_code)
|
|
189
|
+
except KeyError:
|
|
190
|
+
raise RuntimeError(f"Unexpected response status code {response.status_code}: {error_message}")
|
|
191
|
+
|
|
192
|
+
raise error_class(error_message)
|
|
193
|
+
return response
|
|
194
|
+
|
|
195
|
+
def _build_request_kwargs(
|
|
196
|
+
self,
|
|
197
|
+
*,
|
|
198
|
+
headers: dict[str, str],
|
|
199
|
+
params: dict[str, Any],
|
|
200
|
+
json_str: str | None = None,
|
|
201
|
+
octets: bytes | None = None,
|
|
202
|
+
timeout: int,
|
|
203
|
+
) -> dict[str, Any]:
|
|
204
|
+
"""Prepare the keyword arguments for an HTTP request."""
|
|
205
|
+
# add default headers
|
|
206
|
+
headers["User-Agent"] = self._signature
|
|
207
|
+
|
|
208
|
+
# json_str and octets are mutually exclusive
|
|
209
|
+
data: bytes | None = None
|
|
210
|
+
if json_str is not None:
|
|
211
|
+
# Must be able to handle JSON strings with arbitrary unicode characters, so we use an explicit
|
|
212
|
+
# encoding into bytes, and set the headers so the recipient can decode the request body correctly.
|
|
213
|
+
data = json_str.encode("utf-8")
|
|
214
|
+
headers["Content-Type"] = "application/json; charset=UTF-8"
|
|
215
|
+
elif octets is not None:
|
|
216
|
+
data = octets
|
|
217
|
+
headers["Content-Type"] = "application/octet-stream"
|
|
218
|
+
|
|
219
|
+
if self._enable_opentelemetry:
|
|
220
|
+
parent_span_context = trace.set_span_in_context(trace.get_current_span())
|
|
221
|
+
propagate.inject(carrier=headers, context=parent_span_context)
|
|
222
|
+
|
|
223
|
+
# If token callback exists, use it to retrieve the token and add it to the headers
|
|
224
|
+
if self._get_token_callback:
|
|
225
|
+
headers["Authorization"] = self._get_token_callback()
|
|
226
|
+
|
|
227
|
+
kwargs = {
|
|
228
|
+
"headers": headers,
|
|
229
|
+
"params": params,
|
|
230
|
+
"data": data,
|
|
231
|
+
"timeout": timeout,
|
|
232
|
+
}
|
|
233
|
+
return _remove_empty_values(kwargs)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class StationControlClient(_StationControlClientBase):
|
|
104
237
|
"""Client implementation for station control service REST API.
|
|
105
238
|
|
|
106
239
|
Args:
|
|
107
240
|
root_url: Remote station control service URL.
|
|
108
241
|
get_token_callback: A callback function that returns a token (str)
|
|
109
242
|
which will be passed in Authorization header in all requests.
|
|
243
|
+
client_signature: String that is added to the User-Agent header of requests
|
|
244
|
+
sent to the server.
|
|
110
245
|
|
|
111
246
|
"""
|
|
112
247
|
|
|
113
248
|
def __init__(
|
|
114
249
|
self, root_url: str, *, get_token_callback: Callable[[], str] | None = None, client_signature: str | None = None
|
|
115
250
|
):
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
251
|
+
super().__init__(
|
|
252
|
+
root_url,
|
|
253
|
+
get_token_callback=get_token_callback,
|
|
254
|
+
client_signature=client_signature, # type: ignore[arg-type]
|
|
255
|
+
enable_opentelemetry=os.environ.get("JAEGER_OPENTELEMETRY_COLLECTOR_ENDPOINT", None) is not None,
|
|
256
|
+
)
|
|
120
257
|
# TODO SW-1387: Remove this when using v1 API, not needed
|
|
121
258
|
self._check_api_versions()
|
|
122
259
|
qcm_data_url = os.environ.get("CHIP_DESIGN_RECORD_FALLBACK_URL", None)
|
|
@@ -358,16 +495,6 @@ class StationControlClient(StationControlInterface):
|
|
|
358
495
|
def abort_job(self, job_id: StrUUID) -> None:
|
|
359
496
|
self._send_request(requests.post, f"jobs/{job_id}/abort")
|
|
360
497
|
|
|
361
|
-
@staticmethod
|
|
362
|
-
def _create_signature(client_signature: str) -> str:
|
|
363
|
-
signature = f"{platform.platform(terse=True)}"
|
|
364
|
-
signature += f", python {platform.python_version()}"
|
|
365
|
-
version_string = "iqm-station-control-client"
|
|
366
|
-
signature += f", StationControlClient {version_string} {version(version_string)}"
|
|
367
|
-
if client_signature:
|
|
368
|
-
signature += f", {client_signature}"
|
|
369
|
-
return signature
|
|
370
|
-
|
|
371
498
|
def _wait_job_completion(self, job_id: str, update_progress_callback: Callable[[Statuses], None] | None) -> bool:
|
|
372
499
|
logger.info("Waiting for job ID: %s", job_id)
|
|
373
500
|
update_progress_callback = update_progress_callback or (lambda status: None)
|
|
@@ -438,62 +565,6 @@ class StationControlClient(StationControlInterface):
|
|
|
438
565
|
# using the \uXXXX syntax, BaseModel.model_dump_json() keeps them in the produced JSON str.
|
|
439
566
|
return model.model_dump_json()
|
|
440
567
|
|
|
441
|
-
def _send_request(
|
|
442
|
-
self,
|
|
443
|
-
http_method: Callable[..., requests.Response],
|
|
444
|
-
url_path: str,
|
|
445
|
-
*,
|
|
446
|
-
json_str: str | None = None,
|
|
447
|
-
octets: bytes | None = None,
|
|
448
|
-
params: dict[str, Any] | None = None,
|
|
449
|
-
headers: dict[str, str] | None = None,
|
|
450
|
-
timeout: int = 120,
|
|
451
|
-
) -> requests.Response:
|
|
452
|
-
"""Send an HTTP request.
|
|
453
|
-
|
|
454
|
-
Parameters ``json_str``, ``octets`` and ``params`` are mutually exclusive.
|
|
455
|
-
The first non-None argument (in this order) will be used to construct the body of the request.
|
|
456
|
-
|
|
457
|
-
Args:
|
|
458
|
-
http_method: HTTP method to use for the request, any of requests.[post|get|put|head|delete|patch|options].
|
|
459
|
-
url_path: URL for the request.
|
|
460
|
-
json_str: JSON string to store in the body, may contain arbitrary Unicode characters.
|
|
461
|
-
octets: Pre-serialized binary data to store in the body.
|
|
462
|
-
params: HTTP query to store in the body.
|
|
463
|
-
headers: Additional HTTP headers for the request. Some may be overridden.
|
|
464
|
-
|
|
465
|
-
Returns:
|
|
466
|
-
Response to the request.
|
|
467
|
-
|
|
468
|
-
Raises:
|
|
469
|
-
StationControlError: Request was not successful.
|
|
470
|
-
|
|
471
|
-
"""
|
|
472
|
-
# Will raise an error if respectively an error response code is returned.
|
|
473
|
-
# http_method should be any of requests.[post|get|put|head|delete|patch|options]
|
|
474
|
-
|
|
475
|
-
request_kwargs = self._build_request_kwargs(
|
|
476
|
-
json_str=json_str, octets=octets, params=params or {}, headers=headers or {}, timeout=timeout
|
|
477
|
-
)
|
|
478
|
-
url = f"{self.root_url}/{url_path}"
|
|
479
|
-
# TODO SW-1387: Use v1 API
|
|
480
|
-
# url = f"{self.root_url}/{self.version}/{url_path}"
|
|
481
|
-
response = http_method(url, **request_kwargs)
|
|
482
|
-
if not response.ok:
|
|
483
|
-
try:
|
|
484
|
-
response_json = response.json()
|
|
485
|
-
error_message = response_json["detail"]
|
|
486
|
-
except json.JSONDecodeError:
|
|
487
|
-
error_message = response.text
|
|
488
|
-
|
|
489
|
-
try:
|
|
490
|
-
error_class = map_from_status_code_to_error(response.status_code)
|
|
491
|
-
except KeyError:
|
|
492
|
-
raise RuntimeError(f"Unexpected response status code {response.status_code}: {error_message}")
|
|
493
|
-
|
|
494
|
-
raise error_class(error_message)
|
|
495
|
-
return response
|
|
496
|
-
|
|
497
568
|
# TODO SW-1387: Remove this when using v1 API, not needed
|
|
498
569
|
def _check_api_versions(self):
|
|
499
570
|
client_api_version = self._get_client_api_version()
|
|
@@ -529,41 +600,9 @@ class StationControlClient(StationControlInterface):
|
|
|
529
600
|
def _get_client_api_version() -> Version:
|
|
530
601
|
return parse(version("iqm-station-control-client"))
|
|
531
602
|
|
|
532
|
-
def _build_request_kwargs(self, **kwargs):
|
|
533
|
-
kwargs.setdefault("headers", {"User-Agent": self._signature})
|
|
534
|
-
|
|
535
|
-
params = kwargs.get("params", {})
|
|
536
|
-
headers = kwargs["headers"]
|
|
537
|
-
|
|
538
|
-
if kwargs["json_str"] is not None:
|
|
539
|
-
# Must be able to handle JSON strings with arbitrary unicode characters, so we use an explicit
|
|
540
|
-
# encoding into bytes, and set the headers so the recipient can decode the request body correctly.
|
|
541
|
-
data = kwargs["json_str"].encode("utf-8")
|
|
542
|
-
headers["Content-Type"] = "application/json; charset=UTF-8"
|
|
543
|
-
elif kwargs["octets"] is not None:
|
|
544
|
-
data = kwargs["octets"]
|
|
545
|
-
headers["Content-Type"] = "application/octet-stream"
|
|
546
|
-
else:
|
|
547
|
-
data = None
|
|
548
|
-
if self._enable_opentelemetry:
|
|
549
|
-
parent_span_context = trace.set_span_in_context(trace.get_current_span())
|
|
550
|
-
propagate.inject(carrier=headers, context=parent_span_context)
|
|
551
|
-
|
|
552
|
-
# If token callback exists, use it to retrieve the token and add it to the headers
|
|
553
|
-
if self._get_token_callback:
|
|
554
|
-
headers["Authorization"] = self._get_token_callback()
|
|
555
|
-
|
|
556
|
-
kwargs = {
|
|
557
|
-
"params": params,
|
|
558
|
-
"data": data,
|
|
559
|
-
"headers": headers,
|
|
560
|
-
"timeout": kwargs["timeout"],
|
|
561
|
-
}
|
|
562
|
-
return _remove_empty_values(kwargs)
|
|
563
|
-
|
|
564
603
|
@staticmethod
|
|
565
604
|
def _clean_query_parameters(model: Any, **kwargs) -> dict[str, Any]:
|
|
566
|
-
if issubclass(model, PydanticBase) and "invalid" in model.model_fields
|
|
605
|
+
if issubclass(model, PydanticBase) and "invalid" in model.model_fields and "invalid" not in kwargs:
|
|
567
606
|
# Get only valid items by default, "invalid=None" would return also invalid ones.
|
|
568
607
|
# This default has to be set on the client side, server side uses default "None".
|
|
569
608
|
kwargs["invalid"] = False
|
|
@@ -572,7 +611,7 @@ class StationControlClient(StationControlInterface):
|
|
|
572
611
|
@staticmethod
|
|
573
612
|
def _deserialize_response(
|
|
574
613
|
response: requests.Response,
|
|
575
|
-
model_class: type[TypePydanticBase
|
|
614
|
+
model_class: type[TypePydanticBase | ListModel[list[TypePydanticBase]]],
|
|
576
615
|
*,
|
|
577
616
|
list_with_meta: bool = False,
|
|
578
617
|
) -> TypePydanticBase | ListWithMeta[TypePydanticBase]:
|
|
@@ -590,6 +629,6 @@ class StationControlClient(StationControlInterface):
|
|
|
590
629
|
return model
|
|
591
630
|
|
|
592
631
|
|
|
593
|
-
def _remove_empty_values(kwargs):
|
|
594
|
-
|
|
632
|
+
def _remove_empty_values(kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
633
|
+
"""Return a copy of the given dict without values that are None or {}."""
|
|
595
634
|
return {key: value for key, value in kwargs.items() if value not in [None, {}]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
9.2.0
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
9.0.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/docs/_static/css/custom.css
RENAMED
|
File without changes
|
|
File without changes
|
{iqm_station_control_client-9.0.0 → iqm_station_control_client-9.2.0}/docs/_static/images/logo.png
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|