quelware-client 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. quelware_client/__init__.py +3 -0
  2. quelware_client/client/__init__.py +4 -0
  3. quelware_client/client/_grpc.py +61 -0
  4. quelware_client/client/_standalone_grpc.py +48 -0
  5. quelware_client/client/helpers/__init__.py +0 -0
  6. quelware_client/client/helpers/instrument_resolver/__init__.py +58 -0
  7. quelware_client/client/helpers/sequencer/__init__.py +248 -0
  8. quelware_client/core/__init__.py +5 -0
  9. quelware_client/core/_agent_container.py +72 -0
  10. quelware_client/core/_client.py +99 -0
  11. quelware_client/core/_session.py +151 -0
  12. quelware_client/core/_utils.py +17 -0
  13. quelware_client/core/exceptions/__init__.py +74 -0
  14. quelware_client/core/instrument_driver/__init__.py +103 -0
  15. quelware_client/core/interfaces/__init__.py +0 -0
  16. quelware_client/core/interfaces/instrument_agent/__init__.py +55 -0
  17. quelware_client/core/interfaces/resource_agent/__init__.py +34 -0
  18. quelware_client/core/interfaces/session_agent/__init__.py +21 -0
  19. quelware_client/core/interfaces/system_configuration_agent/__init__.py +11 -0
  20. quelware_client/core/trigger_count_proposer/__init__.py +26 -0
  21. quelware_client/infra/__init__.py +0 -0
  22. quelware_client/infra/instrument_agent_grpc/__init__.py +111 -0
  23. quelware_client/infra/resource_agent_grpc/__init__.py +74 -0
  24. quelware_client/infra/session_agent_grpc/__init__.py +82 -0
  25. quelware_client/infra/system_configuration_agent_grpc/__init__.py +22 -0
  26. quelware_client/py.typed +0 -0
  27. quelware_client/testing/__init__.py +3 -0
  28. quelware_client/testing/instrument_agent_mock/__init__.py +64 -0
  29. quelware_client/testing/mock_client.py +34 -0
  30. quelware_client/testing/resource_agent_mock/__init__.py +125 -0
  31. quelware_client/testing/session_agent_mock/__init__.py +32 -0
  32. quelware_client/testing/system_configuration_agent_mock/__init__.py +16 -0
  33. quelware_client-0.1.0.dist-info/METADATA +63 -0
  34. quelware_client-0.1.0.dist-info/RECORD +36 -0
  35. quelware_client-0.1.0.dist-info/WHEEL +4 -0
  36. quelware_client-0.1.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,3 @@
1
+ from .client import create_quelware_client
2
+
3
+ __all__ = ["create_quelware_client"]
@@ -0,0 +1,4 @@
1
+ from ._grpc import create_quelware_client
2
+ from ._standalone_grpc import create_standalone_client
3
+
4
+ __all__ = ["create_quelware_client", "create_standalone_client"]
@@ -0,0 +1,61 @@
1
+ from grpclib.client import Channel
2
+ from quelware_core.entities.unit import UnitLabel
3
+
4
+ from quelware_client.core import AgentContainer, AgentFactory, QuelwareClient
5
+ from quelware_client.core.interfaces.instrument_agent import InstrumentAgent
6
+ from quelware_client.core.interfaces.resource_agent import ResourceAgent
7
+ from quelware_client.core.interfaces.session_agent import SessionAgent
8
+ from quelware_client.core.interfaces.system_configuration_agent import (
9
+ SystemConfigurationAgent,
10
+ )
11
+ from quelware_client.infra.instrument_agent_grpc import InstrumentAgentGrpc
12
+ from quelware_client.infra.resource_agent_grpc import (
13
+ ResourceAgentGrpc,
14
+ )
15
+ from quelware_client.infra.session_agent_grpc import SessionAgentGrpc
16
+ from quelware_client.infra.system_configuration_agent_grpc import (
17
+ SystemConfigurationAgentGrpc,
18
+ )
19
+
20
+
21
+ def _create_default_resource_agent_factory(channel):
22
+ def _default_resource_agent_factory(ul: UnitLabel):
23
+ return ResourceAgentGrpc(channel, metadata={"x-unit-label": str(ul)})
24
+
25
+ return _default_resource_agent_factory
26
+
27
+
28
+ def _create_default_instrument_agent_factory(channel):
29
+ def _default_command_agent_factory(ul: UnitLabel):
30
+ return InstrumentAgentGrpc(channel, metadata={"x-unit-label": str(ul)})
31
+
32
+ return _default_command_agent_factory
33
+
34
+
35
+ def create_quelware_client( # noqa: PLR0913
36
+ endpoint: str = "localhost",
37
+ port: int = 50051,
38
+ session_agent: SessionAgent | None = None,
39
+ configuration_agent: SystemConfigurationAgent | None = None,
40
+ resource_agent_factory: AgentFactory[ResourceAgent] | None = None,
41
+ instrument_agent_factory: AgentFactory[InstrumentAgent] | None = None,
42
+ ):
43
+ channel = Channel(endpoint, port)
44
+
45
+ if resource_agent_factory is None:
46
+ resource_agent_factory = _create_default_resource_agent_factory(channel)
47
+
48
+ if instrument_agent_factory is None:
49
+ instrument_agent_factory = _create_default_instrument_agent_factory(channel)
50
+
51
+ agent_container = AgentContainer()
52
+ if session_agent is None:
53
+ agent_container.session = SessionAgentGrpc(channel)
54
+ if configuration_agent is None:
55
+ agent_container.system_configuration = SystemConfigurationAgentGrpc(channel)
56
+ return QuelwareClient(
57
+ agent=agent_container,
58
+ resource_agent_factory=resource_agent_factory,
59
+ instrument_agent_factory=instrument_agent_factory,
60
+ close_handlers=[channel.close],
61
+ )
@@ -0,0 +1,48 @@
1
+ import logging
2
+
3
+ from grpclib.client import Channel
4
+ from quelware_core.entities.unit import UnitLabel, UnitStatus
5
+
6
+ from quelware_client.core import AgentContainer, QuelwareClient
7
+ from quelware_client.infra.instrument_agent_grpc import InstrumentAgentGrpc
8
+ from quelware_client.infra.resource_agent_grpc import ResourceAgentGrpc
9
+ from quelware_client.testing.session_agent_mock import SessionAgentMock
10
+ from quelware_client.testing.system_configuration_agent_mock import (
11
+ SystemConfigurationAgentMock,
12
+ )
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ def create_standalone_client(
18
+ endpoint: str = "localhost", port: int = 50051, unit_label: str = "mock-unit"
19
+ ):
20
+ """Create a standalone client.
21
+
22
+ Note that the target worker server must be running with lock checking
23
+ disabled to use this client.
24
+ """
25
+ channel = Channel(endpoint, port)
26
+ target_unit = UnitLabel(unit_label)
27
+
28
+ conf_agent = SystemConfigurationAgentMock({target_unit: UnitStatus.ACTIVE})
29
+ session_agent = SessionAgentMock()
30
+
31
+ def resource_agent_factory(ul: UnitLabel):
32
+ return ResourceAgentGrpc(channel, metadata={"x-unit-label": str(ul)})
33
+
34
+ def instrument_agent_factory(ul: UnitLabel):
35
+ return InstrumentAgentGrpc(channel, metadata={"x-unit-label": str(ul)})
36
+
37
+ agent_container = AgentContainer()
38
+ agent_container.session = session_agent
39
+ agent_container.system_configuration = conf_agent
40
+
41
+ logger.warning("NOTE: Standalone client is for testing purposes.")
42
+
43
+ return QuelwareClient(
44
+ agent=agent_container,
45
+ resource_agent_factory=resource_agent_factory,
46
+ instrument_agent_factory=instrument_agent_factory,
47
+ close_handlers=[channel.close],
48
+ )
File without changes
@@ -0,0 +1,58 @@
1
+ import asyncio
2
+ import logging
3
+
4
+ from quelware_core.entities.instrument import InstrumentInfo
5
+ from quelware_core.entities.resource import (
6
+ ResourceCategory,
7
+ ResourceId,
8
+ )
9
+
10
+ from quelware_client.core import QuelwareClient
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class InstrumentResolver:
16
+ def __init__(self):
17
+ self._id_to_inst_info: dict[ResourceId, InstrumentInfo] = {}
18
+ self._alias_to_id: dict[str, ResourceId] = {}
19
+
20
+ async def refresh(self, client: QuelwareClient):
21
+ resource_infos = list(
22
+ rinfo
23
+ for rinfo in await client.list_resource_infos()
24
+ if rinfo.category is ResourceCategory.INSTRUMENT
25
+ )
26
+
27
+ coros = [client.get_instrument_info(rinfo.id) for rinfo in resource_infos]
28
+
29
+ inst_infos: list[InstrumentInfo] = await asyncio.gather(*coros)
30
+
31
+ new_id_to_inst_info = {}
32
+ new_alias_to_id = {}
33
+ for inst_info in inst_infos:
34
+ new_id_to_inst_info[inst_info.id] = inst_info
35
+ new_alias_to_id[inst_info.definition.alias] = inst_info.id
36
+
37
+ self._id_to_inst_info = new_id_to_inst_info
38
+ self._alias_to_id = new_alias_to_id
39
+ logger.info(f"{len(self._id_to_inst_info)} instruments has been registered.")
40
+
41
+ def find_inst_info_by_id(self, instrument_id: ResourceId) -> InstrumentInfo:
42
+ if instrument_id not in self._id_to_inst_info:
43
+ raise ValueError(f"Instrument with id '{instrument_id}' not found.")
44
+ return self._id_to_inst_info[instrument_id]
45
+
46
+ def find_inst_info_by_alias(self, alias: str) -> InstrumentInfo:
47
+ return self.find_inst_info_by_id(self._resolve_single(alias))
48
+
49
+ def _resolve_single(self, alias) -> ResourceId:
50
+ if alias not in self._alias_to_id:
51
+ raise ValueError(f"Instrument with alias '{alias}' not found.")
52
+ return self._alias_to_id[alias]
53
+
54
+ def resolve(self, aliases: list[str]) -> list[ResourceId]:
55
+ return list(self._resolve_single(alias) for alias in aliases)
56
+
57
+
58
+ __all__ = ["InstrumentResolver"]
@@ -0,0 +1,248 @@
1
+ import logging
2
+ import math
3
+ from collections import defaultdict
4
+ from dataclasses import dataclass
5
+
6
+ import numpy as np
7
+ import numpy.typing as npt
8
+ from quelware_core.entities.directives import (
9
+ CaptureWindow,
10
+ SetFixedTimeline,
11
+ WaveformEvent,
12
+ )
13
+ from quelware_core.entities.waveform.sampled import IqArray, IqWaveform
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ @dataclass
19
+ class _SequencerEvent:
20
+ waveform_name: str
21
+ start_offset_ns: float
22
+ gain: float
23
+ phase_offset_deg: float
24
+
25
+
26
+ @dataclass
27
+ class _SequencerCaptureWindow:
28
+ name: str
29
+ start_offset_ns: float
30
+ length_ns: float
31
+
32
+
33
+ @dataclass
34
+ class _Waveform:
35
+ sampling_period_ns: float
36
+ iq_array: IqArray
37
+
38
+
39
+ @dataclass
40
+ class _AliasBinding:
41
+ sampling_period_fs: int
42
+ step_samples: int
43
+
44
+
45
+ class Sequencer:
46
+ def __init__(
47
+ self, default_sampling_period_ns: float, enforce_sample_grid: bool = True
48
+ ):
49
+ self._waveform_library: dict[str, _Waveform] = {}
50
+ self._alias_to_events: dict[str, list[_SequencerEvent]] = defaultdict(list)
51
+ self._alias_to_capwin: dict[str, list[_SequencerCaptureWindow]] = defaultdict(
52
+ list
53
+ )
54
+
55
+ self._default_sampling_period_ns: float = default_sampling_period_ns
56
+ self._iterations: int = 1
57
+
58
+ self._bindings: dict[str, _AliasBinding] = {}
59
+ self._enforce_sample_grid: bool = enforce_sample_grid
60
+ self._length_ns: float = 0.0
61
+
62
+ def bind(self, alias: str, sampling_period_fs: int, step_samples: int):
63
+ self._bindings[alias] = _AliasBinding(
64
+ sampling_period_fs=sampling_period_fs,
65
+ step_samples=step_samples,
66
+ )
67
+
68
+ def _check_and_convert_to_samples(
69
+ self, alias: str, time_ns: float, name_for_log: str
70
+ ) -> int:
71
+ if alias not in self._bindings:
72
+ raise ValueError(
73
+ f"Alias '{alias}' is not bound to the sequencer. Call bind() first."
74
+ )
75
+
76
+ period_fs = self._bindings[alias].sampling_period_fs
77
+ time_fs = time_ns * 1e6
78
+ samples = time_fs / period_fs
79
+ rounded_samples = round(samples)
80
+
81
+ if abs(samples - rounded_samples) > 0.001:
82
+ msg = (
83
+ f"{name_for_log} ({time_ns} ns) is not a multiple of sampling period "
84
+ f"({period_fs} fs) for alias '{alias}'. Calculated samples: {samples}"
85
+ )
86
+ if self._enforce_sample_grid:
87
+ raise ValueError(msg)
88
+ else:
89
+ logger.warning(f"{msg}. Rounding to {rounded_samples} samples.")
90
+
91
+ return rounded_samples
92
+
93
+ def register_waveform(
94
+ self,
95
+ name: str,
96
+ waveform: npt.ArrayLike,
97
+ sampling_period_ns: float | None = None,
98
+ ):
99
+ if sampling_period_ns is None:
100
+ sampling_period_ns = self._default_sampling_period_ns
101
+ if np.any(np.abs(waveform) > 1):
102
+ raise ValueError("The amplitude must be in the range -1 to 1.")
103
+ self._waveform_library[name] = _Waveform(
104
+ sampling_period_ns=sampling_period_ns,
105
+ iq_array=np.array(waveform, dtype=complex),
106
+ )
107
+
108
+ def add_event(
109
+ self,
110
+ instrument_alias: str,
111
+ waveform_name: str,
112
+ start_offset_ns: float,
113
+ gain: float = 1.0,
114
+ phase_offset_deg: float = 0.0,
115
+ ):
116
+ if waveform_name not in self._waveform_library:
117
+ raise ValueError(f"waveform '{waveform_name}' is not registered.")
118
+
119
+ self._check_and_convert_to_samples(
120
+ instrument_alias, start_offset_ns, "Event start_offset_ns"
121
+ )
122
+
123
+ event = _SequencerEvent(
124
+ waveform_name=waveform_name,
125
+ start_offset_ns=start_offset_ns,
126
+ gain=gain,
127
+ phase_offset_deg=phase_offset_deg,
128
+ )
129
+ self._alias_to_events[instrument_alias].append(event)
130
+
131
+ waveform = self._waveform_library[waveform_name]
132
+ end_at_ns = (
133
+ start_offset_ns + len(waveform.iq_array) * waveform.sampling_period_ns
134
+ )
135
+ self._length_ns = max(self._length_ns, end_at_ns)
136
+
137
+ def add_capture_window(
138
+ self,
139
+ instrument_alias: str,
140
+ window_name: str,
141
+ start_offset_ns: float,
142
+ length_ns: float,
143
+ ):
144
+ self._check_and_convert_to_samples(
145
+ instrument_alias,
146
+ start_offset_ns,
147
+ f"Capture window '{window_name}' start_offset_ns",
148
+ )
149
+ self._check_and_convert_to_samples(
150
+ instrument_alias, length_ns, f"Capture window '{window_name}' length_ns"
151
+ )
152
+
153
+ capwin = _SequencerCaptureWindow(
154
+ name=window_name,
155
+ start_offset_ns=start_offset_ns,
156
+ length_ns=length_ns,
157
+ )
158
+ self._alias_to_capwin[instrument_alias].append(capwin)
159
+ end_at_ns = start_offset_ns + length_ns
160
+ self._length_ns = max(self._length_ns, end_at_ns)
161
+
162
+ def extend_length_ns(self, additional_ns: float):
163
+ self._length_ns += additional_ns
164
+
165
+ def set_iterations(self, iterations: int):
166
+ self._iterations = iterations
167
+
168
+ @property
169
+ def aligned_length_fs(self) -> int:
170
+ if not self._bindings:
171
+ return math.ceil(self._length_ns * 1e6)
172
+
173
+ lcm_step_fs = 1
174
+ for b in self._bindings.values():
175
+ step_fs = b.sampling_period_fs * b.step_samples
176
+ lcm_step_fs = math.lcm(lcm_step_fs, step_fs)
177
+
178
+ length_fs = math.ceil(self._length_ns * 1e6)
179
+ remainder = length_fs % lcm_step_fs
180
+ if remainder != 0:
181
+ length_fs += lcm_step_fs - remainder
182
+ return length_fs
183
+
184
+ def export_set_fixed_timeline_directive(
185
+ self, instrument_alias: str
186
+ ) -> SetFixedTimeline:
187
+ if instrument_alias not in self._bindings:
188
+ raise ValueError(f"Alias '{instrument_alias}' is not bound.")
189
+
190
+ sampling_period_fs = self._bindings[instrument_alias].sampling_period_fs
191
+
192
+ name_to_index: dict[str, int] = {}
193
+ counter = 0
194
+ local_library: list[IqWaveform] = []
195
+ local_events: list[WaveformEvent] = []
196
+
197
+ for event in self._alias_to_events[instrument_alias]:
198
+ if event.waveform_name in name_to_index:
199
+ index = name_to_index[event.waveform_name]
200
+ else:
201
+ waveform = self._waveform_library[event.waveform_name]
202
+ local_library.append(
203
+ IqWaveform(
204
+ sampling_period_fs=round(waveform.sampling_period_ns * 1e6),
205
+ iq_array=waveform.iq_array,
206
+ )
207
+ )
208
+ index = counter
209
+ name_to_index[event.waveform_name] = index
210
+ counter += 1
211
+
212
+ start_offset_samples = self._check_and_convert_to_samples(
213
+ instrument_alias, event.start_offset_ns, "Export event"
214
+ )
215
+ local_events.append(
216
+ WaveformEvent(
217
+ waveform_index=index,
218
+ start_offset_samples=start_offset_samples,
219
+ gain=event.gain,
220
+ phase_offset_deg=event.phase_offset_deg,
221
+ )
222
+ )
223
+
224
+ local_capwins: list[CaptureWindow] = []
225
+ for capwin in self._alias_to_capwin[instrument_alias]:
226
+ local_capwins.append(
227
+ CaptureWindow(
228
+ name=capwin.name,
229
+ start_offset_samples=self._check_and_convert_to_samples(
230
+ instrument_alias, capwin.start_offset_ns, "Export capwin start"
231
+ ),
232
+ length_samples=self._check_and_convert_to_samples(
233
+ instrument_alias, capwin.length_ns, "Export capwin length"
234
+ ),
235
+ )
236
+ )
237
+
238
+ length_sample = (
239
+ self.aligned_length_fs + sampling_period_fs - 1
240
+ ) // sampling_period_fs
241
+
242
+ return SetFixedTimeline(
243
+ waveform_library=local_library,
244
+ events=local_events,
245
+ capture_windows=local_capwins,
246
+ length=length_sample,
247
+ iterations=self._iterations,
248
+ )
@@ -0,0 +1,5 @@
1
+ from ._agent_container import AgentContainer
2
+ from ._client import AgentFactory, QuelwareClient
3
+ from ._session import Session
4
+
5
+ __all__ = ["AgentContainer", "AgentFactory", "QuelwareClient", "Session"]
@@ -0,0 +1,72 @@
1
+ import logging
2
+
3
+ from quelware_core.entities.unit import UnitLabel
4
+
5
+ from quelware_client.core.interfaces.instrument_agent import InstrumentAgent
6
+ from quelware_client.core.interfaces.resource_agent import ResourceAgent
7
+ from quelware_client.core.interfaces.session_agent import SessionAgent
8
+ from quelware_client.core.interfaces.system_configuration_agent import (
9
+ SystemConfigurationAgent,
10
+ )
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class AgentContainer:
16
+ def __init__(self):
17
+ self._session: SessionAgent | None = None
18
+ self._conf: SystemConfigurationAgent | None = None
19
+ self._rsrc: dict[UnitLabel, ResourceAgent] = {}
20
+ self._inst: dict[UnitLabel, InstrumentAgent] = {}
21
+
22
+ def update_resource_agent(self, unit_label: UnitLabel, agent: ResourceAgent | None):
23
+ if agent is None:
24
+ if unit_label in self._rsrc:
25
+ self._rsrc[unit_label]
26
+ else:
27
+ logger.warning(f"ResourceAgent for '{unit_label}' not found. Continue.")
28
+ else:
29
+ self._rsrc[unit_label] = agent
30
+
31
+ def update_instrument_agent(
32
+ self, unit_label: UnitLabel, agent: InstrumentAgent | None
33
+ ):
34
+ if agent is None:
35
+ if unit_label in self._rsrc:
36
+ self._inst[unit_label]
37
+ else:
38
+ logger.warning(
39
+ f"InstrumentAgent for '{unit_label}' not found. Continue."
40
+ )
41
+ else:
42
+ self._inst[unit_label] = agent
43
+
44
+ @property
45
+ def session(self) -> SessionAgent:
46
+ if self._session is None:
47
+ raise ValueError("SessionAgent not set.")
48
+ return self._session
49
+
50
+ @session.setter
51
+ def session(self, val: SessionAgent):
52
+ self._session = val
53
+
54
+ @property
55
+ def system_configuration(self) -> SystemConfigurationAgent:
56
+ if self._conf is None:
57
+ raise ValueError("ConfigurationAgent not set.")
58
+ return self._conf
59
+
60
+ @system_configuration.setter
61
+ def system_configuration(self, val: SystemConfigurationAgent):
62
+ self._conf = val
63
+
64
+ def resource(self, unit_label: UnitLabel) -> ResourceAgent:
65
+ if unit_label not in self._rsrc:
66
+ raise ValueError(f"ResouceAgent for '{unit_label}' not set.")
67
+ return self._rsrc[unit_label]
68
+
69
+ def instrument(self, unit_label: UnitLabel) -> InstrumentAgent:
70
+ if unit_label not in self._inst:
71
+ raise ValueError(f"InstrumentAgent for '{unit_label}' not set.")
72
+ return self._inst[unit_label]
@@ -0,0 +1,99 @@
1
+ import asyncio
2
+ from collections.abc import Callable, Collection
3
+ from typing import TypeAlias, TypeVar, cast
4
+
5
+ from quelware_core.entities.instrument import InstrumentInfo
6
+ from quelware_core.entities.port import PortInfo
7
+ from quelware_core.entities.resource import (
8
+ ResourceId,
9
+ ResourceInfo,
10
+ extract_unit_label,
11
+ )
12
+ from quelware_core.entities.unit import UnitLabel
13
+
14
+ from quelware_client.core._agent_container import AgentContainer
15
+ from quelware_client.core.interfaces.instrument_agent import InstrumentAgent
16
+
17
+ from ._session import Session
18
+ from .interfaces.resource_agent import ResourceAgent
19
+
20
+ A = TypeVar("A")
21
+ AgentFactory: TypeAlias = Callable[[UnitLabel], A]
22
+ _ResourceId: TypeAlias = ResourceId | str
23
+
24
+
25
+ class QuelwareClient:
26
+ def __init__(
27
+ self,
28
+ agent: AgentContainer,
29
+ resource_agent_factory: AgentFactory[ResourceAgent] | None = None,
30
+ instrument_agent_factory: AgentFactory[InstrumentAgent] | None = None,
31
+ close_handlers: list[Callable[[], None]] | None = None,
32
+ ):
33
+ self._agent = agent
34
+ self._rsrc_agent_factory = resource_agent_factory
35
+ self._inst_agent_factory = instrument_agent_factory
36
+ self._unit_labels: list[UnitLabel] = []
37
+ self._close_handlers = close_handlers or []
38
+
39
+ @property
40
+ def agent(self) -> AgentContainer:
41
+ return self._agent
42
+
43
+ async def start(self):
44
+ await self.initialize()
45
+
46
+ async def stop(self):
47
+ for handler in self._close_handlers:
48
+ handler()
49
+
50
+ async def __aenter__(self):
51
+ await self.start()
52
+ return self
53
+
54
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
55
+ await self.stop()
56
+
57
+ async def initialize(self):
58
+ self._unit_labels = await self._agent.system_configuration.list_units()
59
+ for ul in self._unit_labels:
60
+ if self._rsrc_agent_factory:
61
+ self._agent.update_resource_agent(ul, self._rsrc_agent_factory(ul))
62
+ if self._inst_agent_factory:
63
+ self._agent.update_instrument_agent(ul, self._inst_agent_factory(ul))
64
+
65
+ def list_unit_labels(self) -> list[UnitLabel]:
66
+ return list(self._unit_labels)
67
+
68
+ def create_session(
69
+ self,
70
+ resource_ids: Collection[_ResourceId],
71
+ ttl_ms: int = 4000,
72
+ tentative_ttl_ms: int = 1000,
73
+ ) -> Session:
74
+ return Session(
75
+ resource_ids=cast(list[ResourceId], resource_ids),
76
+ agent=self._agent,
77
+ ttl_ms=ttl_ms,
78
+ tentative_ttl_ms=tentative_ttl_ms,
79
+ )
80
+
81
+ async def list_resource_infos(self) -> list[ResourceInfo]:
82
+ coros = [
83
+ self._agent.resource(ul).list_resource_infos() for ul in self._unit_labels
84
+ ]
85
+ results = await asyncio.gather(*coros)
86
+ rsrc_infos: list[ResourceInfo] = []
87
+ for res in results:
88
+ rsrc_infos.extend(res)
89
+ return rsrc_infos
90
+
91
+ async def get_port_info(self, port_id: ResourceId) -> PortInfo:
92
+ unit_label = extract_unit_label(port_id)
93
+ port = await self._agent.resource(unit_label).get_port_info(port_id)
94
+ return port
95
+
96
+ async def get_instrument_info(self, instrument_id: ResourceId) -> InstrumentInfo:
97
+ unit_label = extract_unit_label(instrument_id)
98
+ inst = await self._agent.resource(unit_label).get_instrument_info(instrument_id)
99
+ return inst