iqm-station-control-client 8.1.0__tar.gz → 9.0.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-8.1.0 → iqm_station_control_client-9.0.0}/CHANGELOG.rst +33 -0
- {iqm_station_control_client-8.1.0/src/iqm_station_control_client.egg-info → iqm_station_control_client-9.0.0}/PKG-INFO +2 -2
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/requirements/base.in +1 -1
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/requirements/base.txt +1 -1
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/__init__.py +1 -1
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/iqm_server/error.py +1 -1
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/iqm_server/grpc_utils.py +5 -3
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/iqm_server/iqm_server_client.py +276 -45
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/list_models.py +18 -12
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/serializers/__init__.py +1 -1
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/serializers/channel_property_serializer.py +12 -6
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/serializers/datetime_serializers.py +1 -1
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/serializers/playlist_serializers.py +1 -1
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/serializers/run_serializers.py +1 -1
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/serializers/setting_node_serializer.py +1 -1
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/serializers/struct_serializer.py +1 -1
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/serializers/sweep_serializers.py +2 -3
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/serializers/task_serializers.py +1 -1
- iqm_station_control_client-9.0.0/src/iqm/station_control/client/station_control.py +595 -0
- iqm_station_control_client-9.0.0/src/iqm/station_control/client/utils.py +71 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/interface/__init__.py +1 -1
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/interface/list_with_meta.py +1 -1
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/interface/models/__init__.py +13 -1
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/interface/models/dut.py +1 -1
- iqm_station_control_client-9.0.0/src/iqm/station_control/interface/models/dynamic_quantum_architecture.py +98 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/interface/models/observation.py +1 -1
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/interface/models/observation_set.py +15 -1
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/interface/models/run.py +1 -1
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/interface/models/sequence.py +1 -1
- iqm_station_control_client-9.0.0/src/iqm/station_control/interface/models/static_quantum_architecture.py +40 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/interface/models/sweep.py +1 -1
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/interface/models/type_aliases.py +7 -1
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/interface/pydantic_base.py +1 -1
- iqm_station_control_client-9.0.0/src/iqm/station_control/interface/station_control.py +511 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0/src/iqm_station_control_client.egg-info}/PKG-INFO +2 -2
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm_station_control_client.egg-info/SOURCES.txt +3 -1
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm_station_control_client.egg-info/requires.txt +1 -1
- iqm_station_control_client-9.0.0/version.txt +1 -0
- iqm_station_control_client-8.1.0/src/iqm/station_control/client/iqm_server/meta_class.py +0 -38
- iqm_station_control_client-8.1.0/src/iqm/station_control/client/station_control.py +0 -876
- iqm_station_control_client-8.1.0/src/iqm/station_control/client/utils.py +0 -44
- iqm_station_control_client-8.1.0/version.txt +0 -1
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/LICENSE.txt +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/MANIFEST.in +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/README.rst +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/docs/API.rst +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/docs/Makefile +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/docs/_static/css/custom.css +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/docs/_static/images/favicon.ico +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/docs/_static/images/logo.png +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/docs/_templates/autosummary-class-template.rst +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/docs/_templates/autosummary-module-template.rst +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/docs/changelog.rst +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/docs/conf.py +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/docs/index.rst +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/docs/license.rst +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/pyproject.toml +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/setup.cfg +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/setup.py +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/iqm_server/__init__.py +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/iqm_server/proto/__init__.py +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/iqm_server/proto/calibration_pb2.py +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/iqm_server/proto/calibration_pb2.pyi +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/iqm_server/proto/calibration_pb2_grpc.py +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/iqm_server/proto/common_pb2.py +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/iqm_server/proto/common_pb2.pyi +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/iqm_server/proto/common_pb2_grpc.py +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/iqm_server/proto/job_pb2.py +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/iqm_server/proto/job_pb2.pyi +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/iqm_server/proto/job_pb2_grpc.py +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/iqm_server/proto/qc_pb2.py +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/iqm_server/proto/qc_pb2.pyi +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/iqm_server/proto/qc_pb2_grpc.py +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/iqm_server/proto/uuid_pb2.py +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/iqm_server/proto/uuid_pb2.pyi +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/iqm_server/proto/uuid_pb2_grpc.py +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/iqm_server/testing/__init__.py +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/client/iqm_server/testing/iqm_server_mock.py +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/interface/models/jobs.py +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm/station_control/interface/models/monitor.py +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm_station_control_client.egg-info/dependency_links.txt +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/src/iqm_station_control_client.egg-info/top_level.txt +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/tests/.pylintrc +0 -0
- {iqm_station_control_client-8.1.0 → iqm_station_control_client-9.0.0}/tests/__init__.py +0 -0
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
Changelog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
+
Version 9.0.0 (2025-06-13)
|
|
6
|
+
==========================
|
|
7
|
+
|
|
8
|
+
Features
|
|
9
|
+
--------
|
|
10
|
+
|
|
11
|
+
- Reintroduce :class:`.StationControlInterface` and make both :class:`.StationControlClient` and :class:`.IQMServerClient` inherit it
|
|
12
|
+
to implement the same interface.
|
|
13
|
+
- Add new methods to :class:`StationControlInterface`, implemented by :class:`StationControlClient`. :class:`IQMServerClient` doesn't
|
|
14
|
+
implement these new methods yet, but raises ``NotImplementedError`` instead. :issue:`SW-1078`
|
|
15
|
+
|
|
16
|
+
- :meth:`get_default_calibration_set`
|
|
17
|
+
- :meth:`get_default_calibration_set_observations`
|
|
18
|
+
- :meth:`get_dynamic_quantum_architecture`
|
|
19
|
+
- :meth:`get_default_dynamic_quantum_architecture`
|
|
20
|
+
- :meth:`get_static_quantum_architecture`
|
|
21
|
+
|
|
22
|
+
- Support also string UUIDs in :class:`StationControlClient` methods, instead of only :class:`UUID` objects. This always worked in
|
|
23
|
+
practice, since UUIDs were instantly serialized to strings anyway. However, it gave type warnings for the users
|
|
24
|
+
when string given. No functional changes, but no more type warnings.
|
|
25
|
+
|
|
26
|
+
Breaking changes
|
|
27
|
+
----------------
|
|
28
|
+
|
|
29
|
+
- Remove :meth:`StationControlClient.init`, :meth:`StationControlClient.get_calibration_set_values` and
|
|
30
|
+
:meth:`StationControlClient.get_latest_calibration_set_id` methods. Those aren't REST API related
|
|
31
|
+
and needed only by Pulla for now, and they don't belong to :class:`StationControlInterface` since there is already
|
|
32
|
+
:meth:`StationControlInterface.query_observation_sets`,
|
|
33
|
+
:meth:`StationControlInterface.get_observation_set` and
|
|
34
|
+
:meth:`StationControlInterface.get_observation_set_observations`
|
|
35
|
+
which can be used for the same purpose. :class:`IQMServerClient` still has the original methods
|
|
36
|
+
and it should be later refactored to implement the same interface as :class:`StationControlClient`.
|
|
37
|
+
|
|
5
38
|
Version 8.1.0 (2025-06-13)
|
|
6
39
|
==========================
|
|
7
40
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: iqm-station-control-client
|
|
3
|
-
Version:
|
|
3
|
+
Version: 9.0.0
|
|
4
4
|
Summary: Python client for communicating with Station Control Service
|
|
5
5
|
Author-email: IQM Finland Oy <info@meetiqm.com>
|
|
6
6
|
License: Apache License
|
|
@@ -214,7 +214,7 @@ Requires-Python: >=3.11
|
|
|
214
214
|
Description-Content-Type: text/x-rst
|
|
215
215
|
License-File: LICENSE.txt
|
|
216
216
|
Requires-Dist: iqm-exa-common<27,>=26
|
|
217
|
-
Requires-Dist: iqm-data-definitions<3.0,>=2.
|
|
217
|
+
Requires-Dist: iqm-data-definitions<3.0,>=2.13
|
|
218
218
|
Requires-Dist: opentelemetry-exporter-otlp==1.25.0
|
|
219
219
|
Requires-Dist: protobuf<5.0,>=4.25.3
|
|
220
220
|
Requires-Dist: grpcio<2.0,>=1.65.4
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
from exa.common.errors.
|
|
15
|
+
from exa.common.errors.station_control_errors import StationControlError
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class IqmServerError(StationControlError):
|
|
@@ -25,6 +25,7 @@ from pydantic import HttpUrl
|
|
|
25
25
|
|
|
26
26
|
from iqm.station_control.client.iqm_server import proto
|
|
27
27
|
from iqm.station_control.client.iqm_server.error import IqmServerError
|
|
28
|
+
from iqm.station_control.interface.models.type_aliases import StrUUID
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
class ClientCallDetails(grpc.ClientCallDetails):
|
|
@@ -43,8 +44,7 @@ class ApiTokenAuth(grpc.UnaryUnaryClientInterceptor, grpc.UnaryStreamClientInter
|
|
|
43
44
|
|
|
44
45
|
def _add_auth_header(self, client_call_details) -> ClientCallDetails:
|
|
45
46
|
details = ClientCallDetails(client_call_details)
|
|
46
|
-
|
|
47
|
-
details.metadata.append(("authorization", f"Bearer {token}"))
|
|
47
|
+
details.metadata.append(("authorization", self.get_token_callback()))
|
|
48
48
|
return details
|
|
49
49
|
|
|
50
50
|
def intercept_unary_stream(self, continuation, client_call_details, request):
|
|
@@ -115,7 +115,9 @@ def create_channel(
|
|
|
115
115
|
return channel
|
|
116
116
|
|
|
117
117
|
|
|
118
|
-
def to_proto_uuid(value:
|
|
118
|
+
def to_proto_uuid(value: StrUUID) -> proto.Uuid:
|
|
119
|
+
if isinstance(value, str):
|
|
120
|
+
value = uuid.UUID(value)
|
|
119
121
|
return proto.Uuid(raw=value.bytes)
|
|
120
122
|
|
|
121
123
|
|
|
@@ -11,23 +11,28 @@
|
|
|
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
|
-
"""
|
|
14
|
+
"""Client implementation for IQM Server."""
|
|
15
15
|
|
|
16
|
-
from collections.abc import Callable, Iterable
|
|
16
|
+
from collections.abc import Callable, Iterable, Sequence
|
|
17
17
|
from contextlib import contextmanager
|
|
18
18
|
import dataclasses
|
|
19
|
+
from importlib.metadata import version
|
|
19
20
|
from io import BytesIO
|
|
20
21
|
import json
|
|
21
22
|
import logging
|
|
23
|
+
import platform
|
|
22
24
|
from time import sleep
|
|
23
25
|
from typing import Any, TypeVar, cast
|
|
24
26
|
import uuid
|
|
27
|
+
from uuid import UUID
|
|
25
28
|
|
|
26
29
|
import grpc
|
|
27
30
|
from iqm.models.channel_properties import ChannelProperties
|
|
31
|
+
import requests
|
|
28
32
|
|
|
29
33
|
from exa.common.data.setting_node import SettingNode
|
|
30
34
|
from exa.common.data.value import ObservationValue, validate_value
|
|
35
|
+
from exa.common.errors.station_control_errors import map_from_status_code_to_error
|
|
31
36
|
from iqm.station_control.client.iqm_server import proto
|
|
32
37
|
from iqm.station_control.client.iqm_server.error import IqmServerError
|
|
33
38
|
from iqm.station_control.client.iqm_server.grpc_utils import (
|
|
@@ -39,42 +44,66 @@ from iqm.station_control.client.iqm_server.grpc_utils import (
|
|
|
39
44
|
to_datetime,
|
|
40
45
|
to_proto_uuid,
|
|
41
46
|
)
|
|
42
|
-
from iqm.station_control.client.iqm_server.meta_class import IqmServerClientMeta
|
|
43
47
|
from iqm.station_control.client.list_models import DutFieldDataList, DutList
|
|
44
48
|
from iqm.station_control.client.serializers import deserialize_sweep_results, serialize_sweep_job_request
|
|
45
49
|
from iqm.station_control.client.serializers.channel_property_serializer import unpack_channel_properties
|
|
46
50
|
from iqm.station_control.client.serializers.setting_node_serializer import deserialize_setting_node
|
|
47
51
|
from iqm.station_control.client.serializers.task_serializers import deserialize_sweep_job_request
|
|
48
|
-
from iqm.station_control.
|
|
52
|
+
from iqm.station_control.interface.list_with_meta import ListWithMeta
|
|
49
53
|
from iqm.station_control.interface.models import (
|
|
50
54
|
DutData,
|
|
51
55
|
DutFieldData,
|
|
56
|
+
DynamicQuantumArchitecture,
|
|
57
|
+
JobData,
|
|
52
58
|
JobExecutorStatus,
|
|
59
|
+
JobResult,
|
|
60
|
+
ObservationData,
|
|
61
|
+
ObservationDefinition,
|
|
62
|
+
ObservationLite,
|
|
63
|
+
ObservationSetData,
|
|
64
|
+
ObservationSetDefinition,
|
|
65
|
+
ObservationSetUpdate,
|
|
66
|
+
ObservationUpdate,
|
|
67
|
+
QualityMetrics,
|
|
68
|
+
RunData,
|
|
69
|
+
RunDefinition,
|
|
70
|
+
RunLite,
|
|
71
|
+
SequenceMetadataData,
|
|
72
|
+
SequenceMetadataDefinition,
|
|
73
|
+
SequenceResultData,
|
|
74
|
+
SequenceResultDefinition,
|
|
75
|
+
StaticQuantumArchitecture,
|
|
53
76
|
Statuses,
|
|
54
77
|
SweepData,
|
|
55
78
|
SweepDefinition,
|
|
56
79
|
SweepResults,
|
|
57
80
|
)
|
|
58
|
-
from iqm.station_control.interface.models.jobs import
|
|
81
|
+
from iqm.station_control.interface.models.jobs import JobError
|
|
59
82
|
from iqm.station_control.interface.models.sweep import SweepBase
|
|
83
|
+
from iqm.station_control.interface.models.type_aliases import GetObservationsMode, SoftwareVersionSet, StrUUID
|
|
84
|
+
from iqm.station_control.interface.station_control import StationControlInterface
|
|
60
85
|
|
|
61
86
|
logger = logging.getLogger(__name__)
|
|
62
87
|
|
|
63
88
|
T = TypeVar("T")
|
|
64
89
|
|
|
65
90
|
|
|
66
|
-
class IqmServerClient(
|
|
91
|
+
class IqmServerClient(StationControlInterface):
|
|
67
92
|
def __init__(
|
|
68
93
|
self,
|
|
69
94
|
root_url: str,
|
|
95
|
+
*,
|
|
70
96
|
get_token_callback: Callable[[], str] | None = None,
|
|
97
|
+
client_signature: str | None = None,
|
|
71
98
|
grpc_channel: grpc.Channel | None = None,
|
|
72
99
|
):
|
|
73
100
|
self.root_url = root_url
|
|
74
101
|
self._connection_params = parse_connection_params(root_url)
|
|
75
102
|
self._cached_resources = {}
|
|
76
103
|
self._latest_submitted_sweep = None
|
|
77
|
-
self.
|
|
104
|
+
self._get_token_callback = get_token_callback
|
|
105
|
+
self._signature = self._create_signature(client_signature)
|
|
106
|
+
self._channel = grpc_channel or create_channel(self._connection_params, self._get_token_callback)
|
|
78
107
|
self._current_qc = resolve_current_qc(self._channel, self._connection_params.quantum_computer)
|
|
79
108
|
|
|
80
109
|
def __del__(self):
|
|
@@ -86,9 +115,18 @@ class IqmServerClient(StationControlClient, metaclass=IqmServerClientMeta):
|
|
|
86
115
|
def get_about(self) -> dict:
|
|
87
116
|
return self._get_resource("about", parse_json)
|
|
88
117
|
|
|
118
|
+
def get_health(self) -> dict:
|
|
119
|
+
raise NotImplementedError
|
|
120
|
+
|
|
89
121
|
def get_configuration(self) -> dict:
|
|
90
122
|
return self._get_resource("configuration", parse_json)
|
|
91
123
|
|
|
124
|
+
def get_exa_configuration(self) -> str:
|
|
125
|
+
raise NotImplementedError
|
|
126
|
+
|
|
127
|
+
def get_or_create_software_version_set(self, software_version_set: SoftwareVersionSet) -> int:
|
|
128
|
+
raise NotImplementedError
|
|
129
|
+
|
|
92
130
|
def get_settings(self) -> SettingNode:
|
|
93
131
|
return self._get_resource("settings", deserialize_setting_node).copy()
|
|
94
132
|
|
|
@@ -98,14 +136,6 @@ class IqmServerClient(StationControlClient, metaclass=IqmServerClientMeta):
|
|
|
98
136
|
def get_channel_properties(self) -> dict[str, ChannelProperties]:
|
|
99
137
|
return self._get_resource("channel-properties", unpack_channel_properties)
|
|
100
138
|
|
|
101
|
-
def get_duts(self) -> list[DutData]:
|
|
102
|
-
return self._get_resource("duts", lambda data: DutList.model_validate(parse_json(data)))
|
|
103
|
-
|
|
104
|
-
def get_dut_fields(self, dut_label: str) -> list[DutFieldData]:
|
|
105
|
-
return self._get_resource(
|
|
106
|
-
f"dut-fields/{dut_label}", lambda data: DutFieldDataList.model_validate(parse_json(data))
|
|
107
|
-
)
|
|
108
|
-
|
|
109
139
|
def sweep(self, sweep_definition: SweepDefinition) -> dict:
|
|
110
140
|
with wrap_error("Job submission failed"):
|
|
111
141
|
jobs = proto.JobsStub(self._channel)
|
|
@@ -126,8 +156,11 @@ class IqmServerClient(StationControlClient, metaclass=IqmServerClientMeta):
|
|
|
126
156
|
"job_id": str(job_id),
|
|
127
157
|
}
|
|
128
158
|
|
|
129
|
-
def get_sweep(self, sweep_id:
|
|
159
|
+
def get_sweep(self, sweep_id: UUID) -> SweepData:
|
|
130
160
|
with wrap_error("Job loading failed"):
|
|
161
|
+
if isinstance(sweep_id, str):
|
|
162
|
+
sweep_id = uuid.UUID(sweep_id)
|
|
163
|
+
|
|
131
164
|
jobs = proto.JobsStub(self._channel)
|
|
132
165
|
job_lookup = proto.JobLookupV1(id=to_proto_uuid(sweep_id))
|
|
133
166
|
job: proto.JobV1 = jobs.GetJobV1(job_lookup)
|
|
@@ -146,18 +179,118 @@ class IqmServerClient(StationControlClient, metaclass=IqmServerClientMeta):
|
|
|
146
179
|
**{f.name: getattr(sweep, f.name) for f in dataclasses.fields(SweepBase)},
|
|
147
180
|
)
|
|
148
181
|
|
|
149
|
-
def
|
|
182
|
+
def delete_sweep(self, sweep_id: UUID) -> None:
|
|
183
|
+
raise NotImplementedError
|
|
184
|
+
|
|
185
|
+
def get_sweep_results(self, sweep_id: UUID) -> SweepResults:
|
|
150
186
|
with wrap_error("Job result loading failed"):
|
|
151
187
|
jobs = proto.JobsStub(self._channel)
|
|
152
188
|
data_chunks = jobs.GetJobResultsV1(proto.JobLookupV1(id=to_proto_uuid(sweep_id)))
|
|
153
189
|
return deserialize_sweep_results(load_all(data_chunks))
|
|
154
190
|
|
|
155
|
-
def
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
191
|
+
def run(
|
|
192
|
+
self,
|
|
193
|
+
run_definition: RunDefinition,
|
|
194
|
+
update_progress_callback: Callable[[Statuses], None] | None = None,
|
|
195
|
+
wait_job_completion: bool = True,
|
|
196
|
+
) -> bool:
|
|
197
|
+
raise NotImplementedError
|
|
198
|
+
|
|
199
|
+
def get_run(self, run_id: UUID) -> RunData:
|
|
200
|
+
raise NotImplementedError
|
|
201
|
+
|
|
202
|
+
def query_runs(self, **kwargs) -> ListWithMeta[RunLite]:
|
|
203
|
+
raise NotImplementedError
|
|
204
|
+
|
|
205
|
+
def create_observations(
|
|
206
|
+
self, observation_definitions: Sequence[ObservationDefinition]
|
|
207
|
+
) -> ListWithMeta[ObservationData]:
|
|
208
|
+
raise NotImplementedError
|
|
209
|
+
|
|
210
|
+
def get_observations(
|
|
211
|
+
self,
|
|
212
|
+
*,
|
|
213
|
+
mode: GetObservationsMode,
|
|
214
|
+
dut_label: str | None = None,
|
|
215
|
+
dut_field: str | None = None,
|
|
216
|
+
tags: list[str] | None = None,
|
|
217
|
+
invalid: bool | None = False,
|
|
218
|
+
run_ids: list[UUID] | None = None,
|
|
219
|
+
sequence_ids: list[UUID] | None = None,
|
|
220
|
+
limit: int | None = None,
|
|
221
|
+
) -> list[ObservationData]:
|
|
222
|
+
raise NotImplementedError
|
|
223
|
+
|
|
224
|
+
def query_observations(self, **kwargs) -> ListWithMeta[ObservationData]:
|
|
225
|
+
raise NotImplementedError
|
|
226
|
+
|
|
227
|
+
def update_observations(self, observation_updates: Sequence[ObservationUpdate]) -> list[ObservationData]:
|
|
228
|
+
raise NotImplementedError
|
|
229
|
+
|
|
230
|
+
def query_observation_sets(self, **kwargs) -> ListWithMeta[ObservationSetData]:
|
|
231
|
+
raise NotImplementedError
|
|
232
|
+
|
|
233
|
+
def create_observation_set(self, observation_set_definition: ObservationSetDefinition) -> ObservationSetData:
|
|
234
|
+
raise NotImplementedError
|
|
159
235
|
|
|
160
|
-
def
|
|
236
|
+
def get_observation_set(self, observation_set_id: UUID) -> ObservationSetData:
|
|
237
|
+
raise NotImplementedError
|
|
238
|
+
|
|
239
|
+
def update_observation_set(self, observation_set_update: ObservationSetUpdate) -> ObservationSetData:
|
|
240
|
+
raise NotImplementedError
|
|
241
|
+
|
|
242
|
+
def finalize_observation_set(self, observation_set_id: UUID) -> None:
|
|
243
|
+
raise NotImplementedError
|
|
244
|
+
|
|
245
|
+
def get_observation_set_observations(self, observation_set_id: UUID) -> list[ObservationLite]:
|
|
246
|
+
raise NotImplementedError
|
|
247
|
+
|
|
248
|
+
def get_default_calibration_set(self) -> ObservationSetData:
|
|
249
|
+
raise NotImplementedError
|
|
250
|
+
|
|
251
|
+
def get_default_calibration_set_observations(self) -> list[ObservationLite]:
|
|
252
|
+
raise NotImplementedError
|
|
253
|
+
|
|
254
|
+
def get_dynamic_quantum_architecture(self, calibration_set_id: UUID) -> DynamicQuantumArchitecture:
|
|
255
|
+
response = self._send_request(requests.get, f"api/v1/calibration/{calibration_set_id}/gates")
|
|
256
|
+
return DynamicQuantumArchitecture.model_validate_json(response.text)
|
|
257
|
+
|
|
258
|
+
def get_default_dynamic_quantum_architecture(self) -> DynamicQuantumArchitecture:
|
|
259
|
+
raise NotImplementedError
|
|
260
|
+
|
|
261
|
+
def get_default_calibration_set_quality_metrics(self) -> QualityMetrics:
|
|
262
|
+
raise NotImplementedError
|
|
263
|
+
|
|
264
|
+
def get_calibration_set_quality_metrics(self, calibration_set_id: UUID) -> QualityMetrics:
|
|
265
|
+
raise NotImplementedError
|
|
266
|
+
|
|
267
|
+
def get_duts(self) -> list[DutData]:
|
|
268
|
+
return self._get_resource("duts", lambda data: DutList.model_validate(parse_json(data)))
|
|
269
|
+
|
|
270
|
+
def get_dut_fields(self, dut_label: str) -> list[DutFieldData]:
|
|
271
|
+
return self._get_resource(
|
|
272
|
+
f"dut-fields/{dut_label}", lambda data: DutFieldDataList.model_validate(parse_json(data))
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
def query_sequence_metadatas(self, **kwargs) -> ListWithMeta[SequenceMetadataData]:
|
|
276
|
+
raise NotImplementedError
|
|
277
|
+
|
|
278
|
+
def create_sequence_metadata(
|
|
279
|
+
self, sequence_metadata_definition: SequenceMetadataDefinition
|
|
280
|
+
) -> SequenceMetadataData:
|
|
281
|
+
raise NotImplementedError
|
|
282
|
+
|
|
283
|
+
def save_sequence_result(self, sequence_result_definition: SequenceResultDefinition) -> SequenceResultData:
|
|
284
|
+
raise NotImplementedError
|
|
285
|
+
|
|
286
|
+
def get_sequence_result(self, sequence_id: UUID) -> SequenceResultData:
|
|
287
|
+
raise NotImplementedError
|
|
288
|
+
|
|
289
|
+
def get_static_quantum_architecture(self, dut_label: str) -> StaticQuantumArchitecture:
|
|
290
|
+
response = self._send_request(requests.get, "api/v1/quantum-architecture")
|
|
291
|
+
return StaticQuantumArchitecture.model_validate_json(response.text)
|
|
292
|
+
|
|
293
|
+
def get_job(self, job_id: StrUUID) -> JobData:
|
|
161
294
|
with wrap_error("Job loading failed"):
|
|
162
295
|
jobs = proto.JobsStub(self._channel)
|
|
163
296
|
job: proto.JobV1 = jobs.GetJobV1(proto.JobLookupV1(id=to_proto_uuid(job_id)))
|
|
@@ -175,6 +308,34 @@ class IqmServerClient(StationControlClient, metaclass=IqmServerClientMeta):
|
|
|
175
308
|
position=job.queue_position if job.HasField("queue_position") else None,
|
|
176
309
|
)
|
|
177
310
|
|
|
311
|
+
def abort_job(self, job_id: StrUUID) -> None:
|
|
312
|
+
with wrap_error("Job cancellation failed"):
|
|
313
|
+
jobs = proto.JobsStub(self._channel)
|
|
314
|
+
jobs.CancelJobV1(proto.JobLookupV1(id=to_proto_uuid(job_id)))
|
|
315
|
+
|
|
316
|
+
def get_calibration_set_values(self, calibration_set_id: StrUUID) -> dict[str, ObservationValue]:
|
|
317
|
+
with wrap_error("Calibration set loading failed"):
|
|
318
|
+
calibrations = proto.CalibrationsStub(self._channel)
|
|
319
|
+
data_chunks = calibrations.GetFullCalibrationDataV1(
|
|
320
|
+
proto.CalibrationLookupV1(
|
|
321
|
+
id=to_proto_uuid(calibration_set_id),
|
|
322
|
+
)
|
|
323
|
+
)
|
|
324
|
+
_, cal_set_values = parse_calibration_set(load_all(data_chunks))
|
|
325
|
+
return cal_set_values
|
|
326
|
+
|
|
327
|
+
def get_latest_calibration_set_id(self, dut_label: str) -> uuid.UUID:
|
|
328
|
+
with wrap_error("Calibration set metadata loading failed"):
|
|
329
|
+
calibrations = proto.CalibrationsStub(self._channel)
|
|
330
|
+
metadata: proto.CalibrationMetadataV1 = calibrations.GetLatestQuantumComputerCalibrationV1(
|
|
331
|
+
proto.LatestQuantumComputerCalibrationLookupV1(
|
|
332
|
+
qc_id=self._current_qc.id,
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
if metadata.dut_label != dut_label:
|
|
336
|
+
raise ValueError(f"No calibration set for dut_label = {dut_label}")
|
|
337
|
+
return from_proto_uuid(metadata.id)
|
|
338
|
+
|
|
178
339
|
def _wait_job_completion(
|
|
179
340
|
self,
|
|
180
341
|
task_id: str,
|
|
@@ -202,29 +363,6 @@ class IqmServerClient(StationControlClient, metaclass=IqmServerClientMeta):
|
|
|
202
363
|
except KeyboardInterrupt:
|
|
203
364
|
return True
|
|
204
365
|
|
|
205
|
-
def get_calibration_set_values(self, calibration_set_id: uuid.UUID) -> dict[str, ObservationValue]:
|
|
206
|
-
with wrap_error("Calibration set loading failed"):
|
|
207
|
-
calibrations = proto.CalibrationsStub(self._channel)
|
|
208
|
-
data_chunks = calibrations.GetFullCalibrationDataV1(
|
|
209
|
-
proto.CalibrationLookupV1(
|
|
210
|
-
id=to_proto_uuid(calibration_set_id),
|
|
211
|
-
)
|
|
212
|
-
)
|
|
213
|
-
_, cal_set_values = parse_calibration_set(load_all(data_chunks))
|
|
214
|
-
return cal_set_values
|
|
215
|
-
|
|
216
|
-
def get_latest_calibration_set_id(self, dut_label: str) -> uuid.UUID:
|
|
217
|
-
with wrap_error("Calibration set metadata loading failed"):
|
|
218
|
-
calibrations = proto.CalibrationsStub(self._channel)
|
|
219
|
-
metadata: proto.CalibrationMetadataV1 = calibrations.GetLatestQuantumComputerCalibrationV1(
|
|
220
|
-
proto.LatestQuantumComputerCalibrationLookupV1(
|
|
221
|
-
qc_id=self._current_qc.id,
|
|
222
|
-
)
|
|
223
|
-
)
|
|
224
|
-
if metadata.dut_label != dut_label:
|
|
225
|
-
raise ValueError(f"No calibration set for dut_label = {dut_label}")
|
|
226
|
-
return from_proto_uuid(metadata.id)
|
|
227
|
-
|
|
228
366
|
def _get_cached_sweep(self, sweep_id: uuid.UUID) -> SweepDefinition | None:
|
|
229
367
|
latest_submitted = self._latest_submitted_sweep
|
|
230
368
|
if latest_submitted and latest_submitted.sweep_id == sweep_id:
|
|
@@ -246,6 +384,94 @@ class IqmServerClient(StationControlClient, metaclass=IqmServerClientMeta):
|
|
|
246
384
|
self._cached_resources[resource_name] = resource
|
|
247
385
|
return resource
|
|
248
386
|
|
|
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
|
+
|
|
249
475
|
|
|
250
476
|
def resolve_current_qc(channel: grpc.Channel, alias: str) -> proto.QuantumComputerV1:
|
|
251
477
|
qcs = proto.QuantumComputersStub(channel)
|
|
@@ -334,3 +560,8 @@ def wrap_error(title: str):
|
|
|
334
560
|
raise extract_error(e, title) from e
|
|
335
561
|
except Exception as e:
|
|
336
562
|
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, {}]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2025 IQM
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -11,9 +11,12 @@
|
|
|
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
|
-
"""Station control client
|
|
14
|
+
"""Station control client list types for different models.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
These are used mainly for easy serialization and deserialization of list of objects.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from typing import Generic, TypeAlias, TypeVar
|
|
17
20
|
|
|
18
21
|
from pydantic import ConfigDict, RootModel
|
|
19
22
|
|
|
@@ -53,17 +56,20 @@ class ListModel(RootModel):
|
|
|
53
56
|
def __len__(self) -> int:
|
|
54
57
|
return len(self.root)
|
|
55
58
|
|
|
59
|
+
def __str__(self) -> str:
|
|
60
|
+
return str(self.root)
|
|
61
|
+
|
|
56
62
|
model_config = ConfigDict(
|
|
57
63
|
ser_json_inf_nan="constants", # Will serialize Infinity and NaN values as Infinity and NaN
|
|
58
64
|
)
|
|
59
65
|
|
|
60
66
|
|
|
61
|
-
DutList = ListModel[list[DutData]]
|
|
62
|
-
DutFieldDataList = ListModel[list[DutFieldData]]
|
|
63
|
-
ObservationDataList = ListModel[list[ObservationData]]
|
|
64
|
-
ObservationDefinitionList = ListModel[list[ObservationDefinition]]
|
|
65
|
-
ObservationLiteList = ListModel[list[ObservationLite]]
|
|
66
|
-
ObservationUpdateList = ListModel[list[ObservationUpdate]]
|
|
67
|
-
ObservationSetDataList = ListModel[list[ObservationSetData]]
|
|
68
|
-
SequenceMetadataDataList = ListModel[list[SequenceMetadataData]]
|
|
69
|
-
RunLiteList = ListModel[list[RunLite]]
|
|
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]]
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Copyright (c) 2019-2024 IQM Finland Oy.
|
|
3
|
-
# All rights reserved. Confidential and proprietary.
|
|
1
|
+
# Copyright 2025 IQM
|
|
4
2
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
8
14
|
"""Serializers and deserializers for :class:`~iqm.models.channel_properties.channel_properties.ChannelProperties`"""
|
|
9
15
|
|
|
10
16
|
from collections.abc import Iterable
|