iqm-station-control-client 3.11__py3-none-any.whl → 3.13__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.
Files changed (32) hide show
  1. iqm/station_control/client/iqm_server/__init__.py +14 -0
  2. iqm/station_control/client/iqm_server/error.py +30 -0
  3. iqm/station_control/client/iqm_server/grpc_utils.py +154 -0
  4. iqm/station_control/client/iqm_server/iqm_server_client.py +332 -0
  5. iqm/station_control/client/iqm_server/meta_class.py +38 -0
  6. iqm/station_control/client/iqm_server/proto/__init__.py +43 -0
  7. iqm/station_control/client/iqm_server/proto/calibration_pb2.py +48 -0
  8. iqm/station_control/client/iqm_server/proto/calibration_pb2.pyi +45 -0
  9. iqm/station_control/client/iqm_server/proto/calibration_pb2_grpc.py +152 -0
  10. iqm/station_control/client/iqm_server/proto/common_pb2.py +43 -0
  11. iqm/station_control/client/iqm_server/proto/common_pb2.pyi +32 -0
  12. iqm/station_control/client/iqm_server/proto/common_pb2_grpc.py +17 -0
  13. iqm/station_control/client/iqm_server/proto/job_pb2.py +57 -0
  14. iqm/station_control/client/iqm_server/proto/job_pb2.pyi +107 -0
  15. iqm/station_control/client/iqm_server/proto/job_pb2_grpc.py +436 -0
  16. iqm/station_control/client/iqm_server/proto/qc_pb2.py +51 -0
  17. iqm/station_control/client/iqm_server/proto/qc_pb2.pyi +57 -0
  18. iqm/station_control/client/iqm_server/proto/qc_pb2_grpc.py +163 -0
  19. iqm/station_control/client/iqm_server/proto/uuid_pb2.py +39 -0
  20. iqm/station_control/client/iqm_server/proto/uuid_pb2.pyi +26 -0
  21. iqm/station_control/client/iqm_server/proto/uuid_pb2_grpc.py +17 -0
  22. iqm/station_control/client/iqm_server/testing/__init__.py +13 -0
  23. iqm/station_control/client/iqm_server/testing/iqm_server_mock.py +102 -0
  24. iqm/station_control/client/serializers/task_serializers.py +28 -1
  25. iqm/station_control/client/station_control.py +77 -1
  26. iqm/station_control/client/utils.py +16 -1
  27. {iqm_station_control_client-3.11.dist-info → iqm_station_control_client-3.13.dist-info}/METADATA +2 -1
  28. iqm_station_control_client-3.13.dist-info/RECORD +52 -0
  29. iqm_station_control_client-3.11.dist-info/RECORD +0 -29
  30. {iqm_station_control_client-3.11.dist-info → iqm_station_control_client-3.13.dist-info}/LICENSE.txt +0 -0
  31. {iqm_station_control_client-3.11.dist-info → iqm_station_control_client-3.13.dist-info}/WHEEL +0 -0
  32. {iqm_station_control_client-3.11.dist-info → iqm_station_control_client-3.13.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,163 @@
1
+ # Copyright 2025 IQM
2
+ #
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.
14
+ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
15
+ """Client and server classes corresponding to protobuf-defined services."""
16
+ import grpc
17
+
18
+ from . import common_pb2 as common__pb2
19
+ from . import qc_pb2 as qc__pb2
20
+
21
+
22
+ class QuantumComputersStub(object):
23
+ """
24
+ Quantum Computer management APIs.
25
+ """
26
+
27
+ def __init__(self, channel):
28
+ """Constructor.
29
+
30
+ Args:
31
+ channel: A grpc.Channel.
32
+ """
33
+ self.GetQuantumComputerV1 = channel.unary_unary(
34
+ '/iqm.server.QuantumComputers/GetQuantumComputerV1',
35
+ request_serializer=qc__pb2.QuantumComputerLookupV1.SerializeToString,
36
+ response_deserializer=qc__pb2.QuantumComputerV1.FromString,
37
+ )
38
+ self.ListQuantumComputersV1 = channel.unary_unary(
39
+ '/iqm.server.QuantumComputers/ListQuantumComputersV1',
40
+ request_serializer=qc__pb2.ListQuantumComputerFiltersV1.SerializeToString,
41
+ response_deserializer=qc__pb2.QuantumComputersListV1.FromString,
42
+ )
43
+ self.GetQuantumComputerResourceV1 = channel.unary_stream(
44
+ '/iqm.server.QuantumComputers/GetQuantumComputerResourceV1',
45
+ request_serializer=qc__pb2.QuantumComputerResourceLookupV1.SerializeToString,
46
+ response_deserializer=common__pb2.DataChunk.FromString,
47
+ )
48
+
49
+
50
+ class QuantumComputersServicer(object):
51
+ """
52
+ Quantum Computer management APIs.
53
+ """
54
+
55
+ def GetQuantumComputerV1(self, request, context):
56
+ """
57
+ Returns the details for the requested quantum computer. The quantum computer
58
+ can be queried by its id or alias. If the given lookup does not match any
59
+ existing quantum computer, the RPC call returns an error with a GRPC status
60
+ `NOT_FOUND`.
61
+ """
62
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
63
+ context.set_details('Method not implemented!')
64
+ raise NotImplementedError('Method not implemented!')
65
+
66
+ def ListQuantumComputersV1(self, request, context):
67
+ """
68
+ Returns a list of quantum computers matching the given filters.
69
+ """
70
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
71
+ context.set_details('Method not implemented!')
72
+ raise NotImplementedError('Method not implemented!')
73
+
74
+ def GetQuantumComputerResourceV1(self, request, context):
75
+ """*
76
+ Returns the contents of a given resource by name and QC id (e.g. chip design record, static architecture, etc).
77
+ See the full list of available resources at `app/backend/lib/iqm-core-client/src/station_control_http_client/resources.rs`.
78
+ Each resource is an opaque binary blob, the interpretation of which is up to the client.
79
+ """
80
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
81
+ context.set_details('Method not implemented!')
82
+ raise NotImplementedError('Method not implemented!')
83
+
84
+
85
+ def add_QuantumComputersServicer_to_server(servicer, server):
86
+ rpc_method_handlers = {
87
+ 'GetQuantumComputerV1': grpc.unary_unary_rpc_method_handler(
88
+ servicer.GetQuantumComputerV1,
89
+ request_deserializer=qc__pb2.QuantumComputerLookupV1.FromString,
90
+ response_serializer=qc__pb2.QuantumComputerV1.SerializeToString,
91
+ ),
92
+ 'ListQuantumComputersV1': grpc.unary_unary_rpc_method_handler(
93
+ servicer.ListQuantumComputersV1,
94
+ request_deserializer=qc__pb2.ListQuantumComputerFiltersV1.FromString,
95
+ response_serializer=qc__pb2.QuantumComputersListV1.SerializeToString,
96
+ ),
97
+ 'GetQuantumComputerResourceV1': grpc.unary_stream_rpc_method_handler(
98
+ servicer.GetQuantumComputerResourceV1,
99
+ request_deserializer=qc__pb2.QuantumComputerResourceLookupV1.FromString,
100
+ response_serializer=common__pb2.DataChunk.SerializeToString,
101
+ ),
102
+ }
103
+ generic_handler = grpc.method_handlers_generic_handler(
104
+ 'iqm.server.QuantumComputers', rpc_method_handlers)
105
+ server.add_generic_rpc_handlers((generic_handler,))
106
+
107
+
108
+ # This class is part of an EXPERIMENTAL API.
109
+ class QuantumComputers(object):
110
+ """
111
+ Quantum Computer management APIs.
112
+ """
113
+
114
+ @staticmethod
115
+ def GetQuantumComputerV1(request,
116
+ target,
117
+ options=(),
118
+ channel_credentials=None,
119
+ call_credentials=None,
120
+ insecure=False,
121
+ compression=None,
122
+ wait_for_ready=None,
123
+ timeout=None,
124
+ metadata=None):
125
+ return grpc.experimental.unary_unary(request, target, '/iqm.server.QuantumComputers/GetQuantumComputerV1',
126
+ qc__pb2.QuantumComputerLookupV1.SerializeToString,
127
+ qc__pb2.QuantumComputerV1.FromString,
128
+ options, channel_credentials,
129
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
130
+
131
+ @staticmethod
132
+ def ListQuantumComputersV1(request,
133
+ target,
134
+ options=(),
135
+ channel_credentials=None,
136
+ call_credentials=None,
137
+ insecure=False,
138
+ compression=None,
139
+ wait_for_ready=None,
140
+ timeout=None,
141
+ metadata=None):
142
+ return grpc.experimental.unary_unary(request, target, '/iqm.server.QuantumComputers/ListQuantumComputersV1',
143
+ qc__pb2.ListQuantumComputerFiltersV1.SerializeToString,
144
+ qc__pb2.QuantumComputersListV1.FromString,
145
+ options, channel_credentials,
146
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
147
+
148
+ @staticmethod
149
+ def GetQuantumComputerResourceV1(request,
150
+ target,
151
+ options=(),
152
+ channel_credentials=None,
153
+ call_credentials=None,
154
+ insecure=False,
155
+ compression=None,
156
+ wait_for_ready=None,
157
+ timeout=None,
158
+ metadata=None):
159
+ return grpc.experimental.unary_stream(request, target, '/iqm.server.QuantumComputers/GetQuantumComputerResourceV1',
160
+ qc__pb2.QuantumComputerResourceLookupV1.SerializeToString,
161
+ common__pb2.DataChunk.FromString,
162
+ options, channel_credentials,
163
+ insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
@@ -0,0 +1,39 @@
1
+ # Copyright 2025 IQM
2
+ #
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.
14
+ # -*- coding: utf-8 -*-
15
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
16
+ # source: uuid.proto
17
+ # Protobuf Python Version: 4.25.1
18
+ """Generated protocol buffer code."""
19
+ from google.protobuf import descriptor as _descriptor
20
+ from google.protobuf import descriptor_pool as _descriptor_pool
21
+ from google.protobuf import symbol_database as _symbol_database
22
+ from google.protobuf.internal import builder as _builder
23
+ # @@protoc_insertion_point(imports)
24
+
25
+ _sym_db = _symbol_database.Default()
26
+
27
+
28
+
29
+
30
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\nuuid.proto\x12\niqm.server\",\n\x04Uuid\x12\r\n\x03raw\x18\x01 \x01(\x0cH\x00\x12\r\n\x03str\x18\x02 \x01(\tH\x00\x42\x06\n\x04\x64\x61ta')
31
+
32
+ _globals = globals()
33
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
34
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'uuid_pb2', _globals)
35
+ if _descriptor._USE_C_DESCRIPTORS == False:
36
+ DESCRIPTOR._options = None
37
+ _globals['_UUID']._serialized_start=26
38
+ _globals['_UUID']._serialized_end=70
39
+ # @@protoc_insertion_point(module_scope)
@@ -0,0 +1,26 @@
1
+ # Copyright 2025 IQM
2
+ #
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.
14
+ from google.protobuf import descriptor as _descriptor
15
+ from google.protobuf import message as _message
16
+ from typing import ClassVar as _ClassVar, Optional as _Optional
17
+
18
+ DESCRIPTOR: _descriptor.FileDescriptor
19
+
20
+ class Uuid(_message.Message):
21
+ __slots__ = ("raw", "str")
22
+ RAW_FIELD_NUMBER: _ClassVar[int]
23
+ STR_FIELD_NUMBER: _ClassVar[int]
24
+ raw: bytes
25
+ str: str
26
+ def __init__(self, raw: _Optional[bytes] = ..., str: _Optional[str] = ...) -> None: ...
@@ -0,0 +1,17 @@
1
+ # Copyright 2025 IQM
2
+ #
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.
14
+ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
15
+ """Client and server classes corresponding to protobuf-defined services."""
16
+ import grpc
17
+
@@ -0,0 +1,13 @@
1
+ # Copyright 2025 IQM
2
+ #
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.
@@ -0,0 +1,102 @@
1
+ # Copyright 2025 IQM
2
+ #
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.
14
+ """Internal testing utilities for IqmServerClient"""
15
+
16
+ from collections.abc import Iterator
17
+ from datetime import datetime
18
+ import uuid
19
+
20
+ from google.protobuf import timestamp_pb2
21
+ import grpc
22
+
23
+ from iqm.station_control.client.iqm_server import proto
24
+ from iqm.station_control.client.iqm_server.grpc_utils import from_proto_uuid
25
+
26
+
27
+ class IqmServerMockBase(proto.QuantumComputersServicer, proto.CalibrationsServicer, proto.JobsServicer):
28
+ """Base class for IQM server mocks. Only meant for testing IQM library packages, do *not*
29
+ use outside of tests!
30
+ """
31
+
32
+ @staticmethod
33
+ def proto_uuid(base: uuid.UUID | None = None) -> proto.Uuid:
34
+ """Helper function for generating protobuf UUIDs"""
35
+ return proto.Uuid(raw=(base or uuid.uuid4()).bytes)
36
+
37
+ @staticmethod
38
+ def parse_uuid(value: proto.Uuid) -> uuid.UUID:
39
+ """Helper function for generating protobuf UUIDs"""
40
+ return from_proto_uuid(value)
41
+
42
+ @staticmethod
43
+ def proto_timestamp(base: datetime | None = None) -> timestamp_pb2.Timestamp:
44
+ """Helper function for generating protobuf timestamps"""
45
+ timestamp = timestamp_pb2.Timestamp()
46
+ timestamp.FromDatetime(base or datetime.now())
47
+ return timestamp
48
+
49
+ def channel(self) -> grpc.Channel:
50
+ """Gets a `grpc.Channel` that connects to this mock server instance. Can be used to initialize
51
+ a new `IqmServerClient` that uses this mock server instance as a backend for the
52
+ invoked GRPC calls.
53
+ """
54
+ return _MockChannel(self)
55
+
56
+ @staticmethod
57
+ def chunk_stream(data: bytes) -> Iterator[proto.DataChunk]:
58
+ """A utility function for converting a binary data blob into a`(stream DataChunk)`."""
59
+ yield proto.DataChunk(data=data)
60
+
61
+
62
+ class _MockChannel(grpc.Channel):
63
+ def __init__(self, mock: IqmServerMockBase):
64
+ self._mock = mock
65
+
66
+ def subscribe(self, callback, try_to_connect=False):
67
+ pass
68
+
69
+ def unsubscribe(self, callback):
70
+ pass
71
+
72
+ def unary_unary(self, method, *args, **kwargs):
73
+ return self._create_callable(method)
74
+
75
+ def unary_stream(self, method, *args, **kwargs):
76
+ return self._create_callable(method)
77
+
78
+ def stream_unary(self, method, *args, **kwargs):
79
+ return self._create_callable(method)
80
+
81
+ def stream_stream(self, method, *args, **kwargs):
82
+ return self._create_callable(method)
83
+
84
+ def close(self):
85
+ pass
86
+
87
+ def _create_callable(self, fq_method: str):
88
+ _, fn_name = fq_method.lstrip("/").split("/")
89
+ f = getattr(self._mock, fn_name)
90
+
91
+ def callable(request):
92
+ return f(request, _MockContext())
93
+
94
+ return callable
95
+
96
+
97
+ class _MockContext:
98
+ def set_code(self, code):
99
+ pass
100
+
101
+ def set_details(self, details):
102
+ pass
@@ -16,11 +16,16 @@
16
16
  from typing import Any
17
17
  import uuid
18
18
 
19
+ from iqm.data_definitions.station_control.v1.sweep_request_pb2 import SweepRequest as SweepDefinitionProto
20
+
19
21
  # FIXME: Re-enable `no-name-in-module` after pylint supports .pyi files: https://github.com/PyCQA/pylint/issues/4987
20
22
  from iqm.data_definitions.station_control.v1.task_service_pb2 import SweepTaskRequest as SweepTaskRequestProto
21
23
 
22
24
  from iqm.station_control.client.serializers.run_serializers import serialize_run_definition
23
- from iqm.station_control.client.serializers.sweep_serializers import serialize_sweep_definition
25
+ from iqm.station_control.client.serializers.sweep_serializers import (
26
+ deserialize_sweep_definition,
27
+ serialize_sweep_definition,
28
+ )
24
29
  from iqm.station_control.interface.models import RunDefinition, SweepDefinition
25
30
 
26
31
 
@@ -56,6 +61,28 @@ def serialize_sweep_task_request(sweep_definition: SweepDefinition, queue_name:
56
61
  return _serialize_task_request(payload, queue_name, sweep_definition.sweep_id)
57
62
 
58
63
 
64
+ def deserialize_sweep_task_request(data: bytes) -> tuple[SweepDefinition, str]:
65
+ """Deserializes `sweep_definition` and `queue_name` from the serialized bitstring.
66
+
67
+ Args:
68
+ data: The serialized data
69
+
70
+ Returns:
71
+ Deserialized tuple :class:`~iqm.station_control.interface.model.SweepDefinition
72
+ and queue name (string).
73
+
74
+ """
75
+ sweep_task_request_proto = SweepTaskRequestProto()
76
+ sweep_task_request_proto.ParseFromString(data)
77
+ sweep_definition_proto = SweepDefinitionProto()
78
+ if sweep_task_request_proto.payload.Unpack(sweep_definition_proto) is False:
79
+ raise ValueError("Can't unpack SweepDefinition from task TaskRequest")
80
+
81
+ sweep_definition = deserialize_sweep_definition(sweep_definition_proto)
82
+ queue_name = sweep_task_request_proto.queue_name
83
+ return sweep_definition, queue_name
84
+
85
+
59
86
  def _serialize_task_request(payload: Any, queue_name: str, sweep_id: uuid.UUID) -> bytes:
60
87
  sweep_task_request_proto = SweepTaskRequestProto(queue_name=queue_name, sweep_id=str(sweep_id))
61
88
  sweep_task_request_proto.payload.Pack(payload, type_url_prefix="iqm-data-definitions")
@@ -1,4 +1,4 @@
1
- # Copyright 2024 IQM
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.
@@ -13,6 +13,8 @@
13
13
  # limitations under the License.
14
14
  """Station control client implementation."""
15
15
 
16
+ from __future__ import annotations
17
+
16
18
  from collections.abc import Callable, Sequence
17
19
  from functools import cache
18
20
  from importlib.metadata import version
@@ -29,6 +31,7 @@ from packaging.version import Version, parse
29
31
  import requests
30
32
 
31
33
  from exa.common.data.setting_node import SettingNode
34
+ from exa.common.data.value import ObservationValue
32
35
  from exa.common.errors.server_errors import (
33
36
  STATUS_CODE_TO_ERROR_MAPPING,
34
37
  InternalServerError,
@@ -58,6 +61,7 @@ from iqm.station_control.client.serializers import (
58
61
  from iqm.station_control.client.serializers.channel_property_serializer import unpack_channel_properties
59
62
  from iqm.station_control.client.serializers.setting_node_serializer import deserialize_setting_node
60
63
  from iqm.station_control.client.serializers.sweep_serializers import deserialize_sweep_data
64
+ from iqm.station_control.client.utils import calset_from_observations
61
65
  from iqm.station_control.interface.list_with_meta import ListWithMeta
62
66
  from iqm.station_control.interface.models import (
63
67
  DutData,
@@ -158,6 +162,42 @@ class StationControlClient:
158
162
  """Return the version of the station control API this client is using."""
159
163
  return "v1"
160
164
 
165
+ @staticmethod
166
+ def init(root_url: str, get_token_callback: Callable[[], str] | None = None, **kwargs) -> StationControlClient:
167
+ """Initialize a new station control client instance connected to the given remote.
168
+
169
+ Client implementation is selected automatically based on the remote station: if the remote station
170
+ is running the IQM Server software stack, then the IQM Server client implementation (with a limited
171
+ feature set) is chosen. If the remote station is running the SC software stack, then the Station
172
+ Control client implementation (with the full feature set) is chosen.
173
+
174
+ Args:
175
+ root_url: Remote station control service URL. For IQM Server remotes, this is the "Quantum Computer URL"
176
+ value from the web dashboard.
177
+ get_token_callback: A callback function that returns a token (str) which will be passed in Authorization
178
+ header in all requests.
179
+
180
+ """
181
+ try:
182
+ headers = {"Authorization": f"Bearer {get_token_callback()}"} if get_token_callback else {}
183
+ response = requests.get(f"{root_url}/about", headers=headers)
184
+ response.raise_for_status()
185
+ about = response.json()
186
+ if isinstance(about, dict) and about.get("iqm_server") is True:
187
+ # If about information has iqm_server flag, it means that we're communicating
188
+ # with IQM server instead of direct Station Control service, hence we need to
189
+ # use the specialized client
190
+
191
+ # Must be imported here in order to avoid circular dependencies
192
+ from iqm.station_control.client.iqm_server.iqm_server_client import IqmServerClient
193
+
194
+ return IqmServerClient(root_url, get_token_callback, **kwargs)
195
+ # Using direct station control by default
196
+ return StationControlClient(root_url, get_token_callback)
197
+
198
+ except Exception as e:
199
+ raise StationControlError("Failed to connect to the remote server") from e
200
+
161
201
  @cache
162
202
  def get_about(self) -> dict:
163
203
  """Return information about the station control."""
@@ -543,6 +583,42 @@ class StationControlClient:
543
583
  response = self._send_request(requests.get, f"observation-sets/{observation_set_id}/observations")
544
584
  return ObservationLiteList.model_validate(response.json())
545
585
 
586
+ def get_calibration_set_values(self, calibration_set_id: uuid.UUID) -> dict[str, ObservationValue]:
587
+ """Get saved calibration set observations by UUID
588
+
589
+ Args:
590
+ calibration_set_id: UUID of the calibration set to retrieve.
591
+
592
+ Returns:
593
+ Dictionary of observations belonging to the given calibration set.
594
+
595
+ """
596
+ observation_set = self.get_observation_set(calibration_set_id)
597
+ if observation_set.observation_set_type != "calibration-set":
598
+ raise ValueError("Observation set type is not 'calibration-set'")
599
+ observations = self.get_observation_set_observations(calibration_set_id)
600
+ return calset_from_observations(observations)
601
+
602
+ def get_latest_calibration_set_id(self, dut_label: str) -> uuid.UUID:
603
+ """Get UUID of the latest saved calibration set for the given dut_label.
604
+
605
+ Args:
606
+ dut_label: Target DUT label
607
+
608
+ Returns:
609
+ UUID of the latest saved calibration set.
610
+
611
+ """
612
+ observation_sets = self.query_observation_sets(
613
+ observation_set_type="calibration-set",
614
+ dut_label=dut_label,
615
+ invalid=False,
616
+ end_timestamp__isnull=False, # Finalized
617
+ order_by="-end_timestamp", # This requires SC version > 35.15
618
+ limit=1,
619
+ )
620
+ return observation_sets[0].observation_set_id
621
+
546
622
  def get_duts(self) -> list[DutData]:
547
623
  """Get DUTs of the station control."""
548
624
  response = self._send_request(requests.get, "duts")
@@ -8,11 +8,13 @@
8
8
 
9
9
  """Utility functions for IQM Station Control Client."""
10
10
 
11
- from collections.abc import Callable
11
+ from collections.abc import Callable, Iterable
12
12
 
13
13
  from tqdm.auto import tqdm
14
14
 
15
+ from exa.common.data.value import ObservationValue
15
16
  from iqm.station_control.interface.models import Statuses
17
+ from iqm.station_control.interface.models.observation import ObservationBase
16
18
 
17
19
 
18
20
  def get_progress_bar_callback() -> Callable[[Statuses], None]:
@@ -27,3 +29,16 @@ def get_progress_bar_callback() -> Callable[[Statuses], None]:
27
29
  progress_bars[label].refresh()
28
30
 
29
31
  return _create_and_update_progress_bars
32
+
33
+
34
+ def calset_from_observations(calset_observations: Iterable[ObservationBase]) -> dict[str, ObservationValue]:
35
+ """Create a calibration set from the given observations.
36
+
37
+ Args:
38
+ calset_observations: observations that form a calibration set
39
+
40
+ Returns:
41
+ calibration set
42
+
43
+ """
44
+ return {obs.dut_field: obs.value for obs in calset_observations}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iqm-station-control-client
3
- Version: 3.11
3
+ Version: 3.13
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
@@ -217,6 +217,7 @@ Requires-Dist: iqm-exa-common <27,>=26
217
217
  Requires-Dist: iqm-data-definitions <3.0,>=2.8
218
218
  Requires-Dist: opentelemetry-exporter-otlp ==1.25.0
219
219
  Requires-Dist: protobuf <5.0,>=4.25.3
220
+ Requires-Dist: grpcio <2.0,>=1.65.4
220
221
  Requires-Dist: pydantic <3.0,>=2.10.4
221
222
  Requires-Dist: PyYAML ==6.0
222
223
  Requires-Dist: requests ==2.32.3
@@ -0,0 +1,52 @@
1
+ iqm/station_control/client/__init__.py,sha256=BmBIBdZa10r-IWCFzZ1-0DG6GQKPIXqGXltfXop4ZeQ,942
2
+ iqm/station_control/client/list_models.py,sha256=SjD0DbCrM9z1SSuGoQS83lyJmDLuMOatpJUoW8itW9s,2335
3
+ iqm/station_control/client/station_control.py,sha256=XdYBblHtMJ6H81PC5R9Orazo6gfVS577tfA7e4D-x7I,37900
4
+ iqm/station_control/client/utils.py,sha256=cpS3hXEeeIXeqd_vBnnwo3JHS83FrNpG07SiTUwUx-I,1650
5
+ iqm/station_control/client/iqm_server/__init__.py,sha256=nLsRHN1rnOKXwuzaq_liUpAYV3sis5jkyHccSdacV7U,624
6
+ iqm/station_control/client/iqm_server/error.py,sha256=ZLV2-gxFLHZjZVkI3L5sWcBMiay7NT-ijIEvrXgVJT8,1166
7
+ iqm/station_control/client/iqm_server/grpc_utils.py,sha256=iVJ_cVv7ZNx-MonuUi6_V4Y6dvJjBg9fOkW9phcOGQE,5720
8
+ iqm/station_control/client/iqm_server/iqm_server_client.py,sha256=U2jvNFQ3lDEGCFy5I3uKl1ElHPMrzaAblWU_bf0bXZs,14657
9
+ iqm/station_control/client/iqm_server/meta_class.py,sha256=pePJ0Xy0aiJg-bZWK8D87gblq6imfXLsZHjpZkf5D9s,1399
10
+ iqm/station_control/client/iqm_server/proto/__init__.py,sha256=mOJQ_H-NEyJMffRaDSSZeXrScHaHaHEXULv-O_OJA3A,1345
11
+ iqm/station_control/client/iqm_server/proto/calibration_pb2.py,sha256=gum0DGmqxhbfaar8SqahmSif1pB6hgo0pVcnoi3VMUo,3017
12
+ iqm/station_control/client/iqm_server/proto/calibration_pb2.pyi,sha256=4lTHY_GhrsLIHqoGDkNLYu56QHzX_iHEbLaYq-HR1m8,2016
13
+ iqm/station_control/client/iqm_server/proto/calibration_pb2_grpc.py,sha256=wBHT2cipAgo5VSIN85Ms89lGDhmCfHc-bUj2yyhqE3s,6816
14
+ iqm/station_control/client/iqm_server/proto/common_pb2.py,sha256=G0l5_PiIAmoCMEZqgMNgtQ4FvXLrK-2K_y-9IsdvjiY,1798
15
+ iqm/station_control/client/iqm_server/proto/common_pb2.pyi,sha256=v7DSBqS2hxJxcsVnwxnZG3CdLc3p28sD2-OVw561JGw,1133
16
+ iqm/station_control/client/iqm_server/proto/common_pb2_grpc.py,sha256=SF40l84__r-OGGNYBru5ik9gih-XqeTq2iwM5gMN5Qc,726
17
+ iqm/station_control/client/iqm_server/proto/job_pb2.py,sha256=2hfP2Qap6y9diiXpf71qywWWxQZkOTou5UjOYlMm7O4,4687
18
+ iqm/station_control/client/iqm_server/proto/job_pb2.pyi,sha256=U9wMSuX7AxDnx7Mpn5Rp31wNg1r_-qTa7iAbQL3badI,4967
19
+ iqm/station_control/client/iqm_server/proto/job_pb2_grpc.py,sha256=JbfVZKaJnYZdUU7ssX2Llx4HbbiVEcFgYU2wE_Jm1fU,17002
20
+ iqm/station_control/client/iqm_server/proto/qc_pb2.py,sha256=-ika0EK0l_I9V5-kcG226FqNIoODI_QQex9X5ihblig,3244
21
+ iqm/station_control/client/iqm_server/proto/qc_pb2.pyi,sha256=s4P3k2wA1XG1wRF_y9BQD7VQgDONQnhOzxItih8_d0I,2491
22
+ iqm/station_control/client/iqm_server/proto/qc_pb2_grpc.py,sha256=6l6OECi0td_Ld7kd1MIrGjQmvs1F6Sd-vqeCfYeAS94,6958
23
+ iqm/station_control/client/iqm_server/proto/uuid_pb2.py,sha256=H1O7qvtRg2zXqOVfWCjZH-2WjXdzyNjbSv2g0fKnfV0,1616
24
+ iqm/station_control/client/iqm_server/proto/uuid_pb2.pyi,sha256=9LXcqNoQS1iapCsoMIZchKPP6-5jcbJqGLne9D4Fu5w,1029
25
+ iqm/station_control/client/iqm_server/proto/uuid_pb2_grpc.py,sha256=SF40l84__r-OGGNYBru5ik9gih-XqeTq2iwM5gMN5Qc,726
26
+ iqm/station_control/client/iqm_server/testing/__init__.py,sha256=wCNfJHIR_bqG3ZBlgm55v90Rih7VCpfctoIMfwRMgjk,567
27
+ iqm/station_control/client/iqm_server/testing/iqm_server_mock.py,sha256=X_Chi8TKx95PiuhFfGnRu9LxeIpnKKynW_8tXwxFQD8,3340
28
+ iqm/station_control/client/serializers/__init__.py,sha256=8os3EGOtNTRFaviZdGwDyMt9GUpM3ZP7icPKAxOg1qg,1438
29
+ iqm/station_control/client/serializers/channel_property_serializer.py,sha256=ChlX8B-blM5hjv3pUExHOd-vE3O_myPwILu36KZYYNU,7121
30
+ iqm/station_control/client/serializers/datetime_serializers.py,sha256=Ke6VRHa_359xYxXTegs8iweoDfuGeBDgkoOtGBbyC1Q,1122
31
+ iqm/station_control/client/serializers/playlist_serializers.py,sha256=S8RuKdqeJxqUf7_kqTDXIKnuo6g-WpzGY7cesSQa3Rw,18086
32
+ iqm/station_control/client/serializers/run_serializers.py,sha256=4zH0I5EvvaP7wgLMprXXWa36nAPO4Lv0fPkCrDC_v-g,6698
33
+ iqm/station_control/client/serializers/setting_node_serializer.py,sha256=m4Sbm8Qr3GiSNiE-Jh8gFEgfscfN1xxELb0vCa9cK70,1197
34
+ iqm/station_control/client/serializers/struct_serializer.py,sha256=QztBsbRlRG_UrtpQLE3bi0WKEVn48kVB91H1g26PvqQ,3270
35
+ iqm/station_control/client/serializers/sweep_serializers.py,sha256=2COfE3ewusjeIp7q3sQWEpc9nzUrfjMNTeemihtpzKA,5725
36
+ iqm/station_control/client/serializers/task_serializers.py,sha256=m0H2JTxak_eAyl88Ptj7EmJhcAIW5ozPJEVYfZYBIu8,3709
37
+ iqm/station_control/interface/__init__.py,sha256=MIQla-cBKPbZqBkp-LNyPfjiV0gzf-IFEwrMMhsnKlg,785
38
+ iqm/station_control/interface/list_with_meta.py,sha256=GAXIDEXKeua6-2FoQ_O1tkhx-d8pBMGHaIkdvgg-cag,1185
39
+ iqm/station_control/interface/pydantic_base.py,sha256=MVzcsH7wG1DON-qTw6KLpUDay7_b_9CDQgymVzg9HwI,1303
40
+ iqm/station_control/interface/models/__init__.py,sha256=PbENb1YhX2OLGTiJNvj70e5Acj7f8jMP3hp5ncH5U-0,1527
41
+ iqm/station_control/interface/models/dut.py,sha256=dd1SpcsBe4P057jvcPqv39SjzekewwP07hThFe5ulNA,1216
42
+ iqm/station_control/interface/models/observation.py,sha256=Jce4lIsUtHRIFT3nr-cbKvh3dbR2Y_yM5x0yyvUdjF8,3261
43
+ iqm/station_control/interface/models/observation_set.py,sha256=Ko2o3-9I38NfjNF2IQPcwfbwpkTQ3PIU7fUiSaDleX8,3031
44
+ iqm/station_control/interface/models/run.py,sha256=m-iE3QMPQUOF7bsw8JCAM1Bd6bDVhAgxrtc_AC7rCkc,4097
45
+ iqm/station_control/interface/models/sequence.py,sha256=uOqMwF1x-vW6UHs2WnPD3PsuSgV3a8OTAsgn_4UENLw,2723
46
+ iqm/station_control/interface/models/sweep.py,sha256=6SQ4Ty4_Rm1KTeR7YfrLmwyD-AnNE495LMxYu8dM4Ko,2947
47
+ iqm/station_control/interface/models/type_aliases.py,sha256=3LB9viZVi8osavY5kKF8TH1crayG7-MLjgBqXDCqL2s,1018
48
+ iqm_station_control_client-3.13.dist-info/LICENSE.txt,sha256=R6Q7eUrLyoCQgWYorQ8WJmVmWKYU3dxA3jYUp0wwQAw,11332
49
+ iqm_station_control_client-3.13.dist-info/METADATA,sha256=103CpVF03ANgJUbtEAf8wN5UmOdgV1rX-P8OBRia2Qw,14008
50
+ iqm_station_control_client-3.13.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
51
+ iqm_station_control_client-3.13.dist-info/top_level.txt,sha256=NB4XRfyDS6_wG9gMsyX-9LTU7kWnTQxNvkbzIxGv3-c,4
52
+ iqm_station_control_client-3.13.dist-info/RECORD,,
@@ -1,29 +0,0 @@
1
- iqm/station_control/client/__init__.py,sha256=BmBIBdZa10r-IWCFzZ1-0DG6GQKPIXqGXltfXop4ZeQ,942
2
- iqm/station_control/client/list_models.py,sha256=SjD0DbCrM9z1SSuGoQS83lyJmDLuMOatpJUoW8itW9s,2335
3
- iqm/station_control/client/station_control.py,sha256=vMnreheUnxP_EGn0qfOfN0-gnSKgU_bNvsUyWwCEBKw,34298
4
- iqm/station_control/client/utils.py,sha256=3VVLCXt6rcKNQc4HerlMxpxkRfTFMYCwOQHF2DS1OG8,1143
5
- iqm/station_control/client/serializers/__init__.py,sha256=8os3EGOtNTRFaviZdGwDyMt9GUpM3ZP7icPKAxOg1qg,1438
6
- iqm/station_control/client/serializers/channel_property_serializer.py,sha256=ChlX8B-blM5hjv3pUExHOd-vE3O_myPwILu36KZYYNU,7121
7
- iqm/station_control/client/serializers/datetime_serializers.py,sha256=Ke6VRHa_359xYxXTegs8iweoDfuGeBDgkoOtGBbyC1Q,1122
8
- iqm/station_control/client/serializers/playlist_serializers.py,sha256=S8RuKdqeJxqUf7_kqTDXIKnuo6g-WpzGY7cesSQa3Rw,18086
9
- iqm/station_control/client/serializers/run_serializers.py,sha256=4zH0I5EvvaP7wgLMprXXWa36nAPO4Lv0fPkCrDC_v-g,6698
10
- iqm/station_control/client/serializers/setting_node_serializer.py,sha256=m4Sbm8Qr3GiSNiE-Jh8gFEgfscfN1xxELb0vCa9cK70,1197
11
- iqm/station_control/client/serializers/struct_serializer.py,sha256=QztBsbRlRG_UrtpQLE3bi0WKEVn48kVB91H1g26PvqQ,3270
12
- iqm/station_control/client/serializers/sweep_serializers.py,sha256=2COfE3ewusjeIp7q3sQWEpc9nzUrfjMNTeemihtpzKA,5725
13
- iqm/station_control/client/serializers/task_serializers.py,sha256=mUi6IeNBUQnWy5U65C2fL597lcee71YAo9o8VM-aCnE,2712
14
- iqm/station_control/interface/__init__.py,sha256=MIQla-cBKPbZqBkp-LNyPfjiV0gzf-IFEwrMMhsnKlg,785
15
- iqm/station_control/interface/list_with_meta.py,sha256=GAXIDEXKeua6-2FoQ_O1tkhx-d8pBMGHaIkdvgg-cag,1185
16
- iqm/station_control/interface/pydantic_base.py,sha256=MVzcsH7wG1DON-qTw6KLpUDay7_b_9CDQgymVzg9HwI,1303
17
- iqm/station_control/interface/models/__init__.py,sha256=PbENb1YhX2OLGTiJNvj70e5Acj7f8jMP3hp5ncH5U-0,1527
18
- iqm/station_control/interface/models/dut.py,sha256=dd1SpcsBe4P057jvcPqv39SjzekewwP07hThFe5ulNA,1216
19
- iqm/station_control/interface/models/observation.py,sha256=Jce4lIsUtHRIFT3nr-cbKvh3dbR2Y_yM5x0yyvUdjF8,3261
20
- iqm/station_control/interface/models/observation_set.py,sha256=Ko2o3-9I38NfjNF2IQPcwfbwpkTQ3PIU7fUiSaDleX8,3031
21
- iqm/station_control/interface/models/run.py,sha256=m-iE3QMPQUOF7bsw8JCAM1Bd6bDVhAgxrtc_AC7rCkc,4097
22
- iqm/station_control/interface/models/sequence.py,sha256=uOqMwF1x-vW6UHs2WnPD3PsuSgV3a8OTAsgn_4UENLw,2723
23
- iqm/station_control/interface/models/sweep.py,sha256=6SQ4Ty4_Rm1KTeR7YfrLmwyD-AnNE495LMxYu8dM4Ko,2947
24
- iqm/station_control/interface/models/type_aliases.py,sha256=3LB9viZVi8osavY5kKF8TH1crayG7-MLjgBqXDCqL2s,1018
25
- iqm_station_control_client-3.11.dist-info/LICENSE.txt,sha256=R6Q7eUrLyoCQgWYorQ8WJmVmWKYU3dxA3jYUp0wwQAw,11332
26
- iqm_station_control_client-3.11.dist-info/METADATA,sha256=nxvBOAcawccoBYwoGBw54gB3M2GWmfQe-miB7bgtwPw,13972
27
- iqm_station_control_client-3.11.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
28
- iqm_station_control_client-3.11.dist-info/top_level.txt,sha256=NB4XRfyDS6_wG9gMsyX-9LTU7kWnTQxNvkbzIxGv3-c,4
29
- iqm_station_control_client-3.11.dist-info/RECORD,,